1package release
2
3import (
4	"os"
5	"path/filepath"
6
7	bosherr "github.com/cloudfoundry/bosh-utils/errors"
8	boshcmd "github.com/cloudfoundry/bosh-utils/fileutil"
9	boshlog "github.com/cloudfoundry/bosh-utils/logger"
10	boshsys "github.com/cloudfoundry/bosh-utils/system"
11	"gopkg.in/yaml.v2"
12
13	boshjob "github.com/cloudfoundry/bosh-cli/release/job"
14	boshlic "github.com/cloudfoundry/bosh-cli/release/license"
15	boshpkg "github.com/cloudfoundry/bosh-cli/release/pkg"
16)
17
18type ArchiveWriter struct {
19	compressor     boshcmd.Compressor
20	fs             boshsys.FileSystem
21	filesToInclude []string
22
23	logTag string
24	logger boshlog.Logger
25}
26
27func NewArchiveWriter(compressor boshcmd.Compressor, fs boshsys.FileSystem, logger boshlog.Logger) ArchiveWriter {
28	return ArchiveWriter{compressor: compressor, fs: fs, logTag: "release.ArchiveWriter", logger: logger}
29}
30
31func (w ArchiveWriter) Write(release Release, pkgFpsToSkip []string) (string, error) {
32	stagingDir, err := w.fs.TempDir("bosh-release")
33	if err != nil {
34		return "", bosherr.WrapErrorf(err, "Creating staging release dir")
35	}
36
37	defer w.cleanUp(stagingDir)
38
39	w.logger.Info(w.logTag, "Writing release tarball into '%s'", stagingDir)
40
41	manifestBytes, err := yaml.Marshal(release.Manifest())
42	if err != nil {
43		return "", bosherr.WrapError(err, "Marshalling release manifest")
44	}
45
46	manifestPath := filepath.Join(stagingDir, "release.MF")
47
48	err = w.fs.WriteFile(manifestPath, manifestBytes)
49	if err != nil {
50		return "", bosherr.WrapErrorf(err, "Writing release manifest '%s'", manifestPath)
51	}
52
53	w.filesToInclude = w.appendFile("release.MF")
54
55	jobsFiles, err := w.writeJobs(release.Jobs(), stagingDir)
56	if err != nil {
57		return "", bosherr.WrapError(err, "Writing jobs")
58	}
59
60	w.filesToInclude = w.appendFiles(jobsFiles)
61
62	packagesFiles, err := w.writePackages(release.Packages(), pkgFpsToSkip, stagingDir)
63	if err != nil {
64		return "", bosherr.WrapError(err, "Writing packages")
65	}
66
67	w.filesToInclude = w.appendFiles(packagesFiles)
68
69	compiledPackagesFiles, err := w.writeCompiledPackages(release.CompiledPackages(), pkgFpsToSkip, stagingDir)
70	if err != nil {
71		return "", bosherr.WrapError(err, "Writing compiled packages")
72	}
73
74	w.filesToInclude = w.appendFiles(compiledPackagesFiles)
75
76	licenseFiles, err := w.writeLicense(release.License(), stagingDir)
77	if err != nil {
78		return "", bosherr.WrapError(err, "Writing license")
79	}
80
81	w.filesToInclude = w.appendFiles(licenseFiles)
82
83	files := w.filesToInclude
84	path, err := w.compressor.CompressSpecificFilesInDir(stagingDir, files)
85
86	if err != nil {
87		return "", bosherr.WrapError(err, "Compressing release")
88	}
89
90	return path, nil
91}
92
93func (w ArchiveWriter) cleanUp(stagingDir string) {
94	removeErr := w.fs.RemoveAll(stagingDir)
95	if removeErr != nil {
96		w.logger.Error(w.logTag, "Failed to remove staging dir for release: %s", removeErr.Error())
97	}
98}
99
100func (w ArchiveWriter) writeJobs(jobs []*boshjob.Job, stagingDir string) ([]string, error) {
101	var jobsFiles []string
102
103	if len(jobs) == 0 {
104		return jobsFiles, nil
105	}
106
107	jobsPath := filepath.Join(stagingDir, "jobs")
108
109	err := w.fs.MkdirAll(jobsPath, os.ModePerm)
110	if err != nil {
111		return jobsFiles, bosherr.WrapError(err, "Creating jobs/")
112	}
113
114	jobsFiles = append(jobsFiles, "jobs")
115
116	for _, job := range jobs {
117		err := w.fs.CopyFile(job.ArchivePath(), filepath.Join(jobsPath, job.Name()+".tgz"))
118		if err != nil {
119			return jobsFiles, bosherr.WrapErrorf(err, "Copying job '%s' archive into staging dir", job.Name())
120		}
121	}
122
123	return jobsFiles, nil
124}
125
126func (w ArchiveWriter) writePackages(packages []*boshpkg.Package, pkgFpsToSkip []string, stagingDir string) ([]string, error) {
127	var packagesFiles []string
128
129	if len(packages) == 0 {
130		return packagesFiles, nil
131	}
132
133	pkgsPath := filepath.Join(stagingDir, "packages")
134
135	err := w.fs.MkdirAll(pkgsPath, os.ModePerm)
136	if err != nil {
137		return packagesFiles, bosherr.WrapError(err, "Creating packages/")
138	}
139
140	packagesFiles = append(packagesFiles, "packages")
141
142	for _, pkg := range packages {
143		if w.shouldSkip(pkg.Fingerprint(), pkgFpsToSkip) {
144			w.logger.Debug(w.logTag, "Package '%s' was filtered out", pkg.Name())
145		} else {
146			err := w.fs.CopyFile(pkg.ArchivePath(), filepath.Join(pkgsPath, pkg.Name()+".tgz"))
147			if err != nil {
148				return packagesFiles, bosherr.WrapErrorf(err, "Copying package '%s' archive into staging dir", pkg.Name())
149			}
150		}
151	}
152
153	return packagesFiles, nil
154}
155
156func (w ArchiveWriter) writeCompiledPackages(compiledPkgs []*boshpkg.CompiledPackage, pkgFpsToSkip []string, stagingDir string) ([]string, error) {
157	var compiledPackagesFiles []string
158
159	if len(compiledPkgs) == 0 {
160		return compiledPackagesFiles, nil
161	}
162
163	pkgsPath := filepath.Join(stagingDir, "compiled_packages")
164
165	err := w.fs.MkdirAll(pkgsPath, os.ModePerm)
166	if err != nil {
167		return compiledPackagesFiles, bosherr.WrapError(err, "Creating compiled_packages/")
168	}
169
170	compiledPackagesFiles = append(compiledPackagesFiles, "compiled_packages")
171
172	for _, compiledPkg := range compiledPkgs {
173		if w.shouldSkip(compiledPkg.Fingerprint(), pkgFpsToSkip) {
174			w.logger.Debug(w.logTag, "Compiled package '%s' was filtered out", compiledPkg.Name())
175		} else {
176			err := w.fs.CopyFile(compiledPkg.ArchivePath(), filepath.Join(pkgsPath, compiledPkg.Name()+".tgz"))
177			if err != nil {
178				return compiledPackagesFiles, bosherr.WrapErrorf(err, "Copying compiled package '%s' archive into staging dir", compiledPkg.Name())
179			}
180		}
181	}
182
183	return compiledPackagesFiles, nil
184}
185
186func (w ArchiveWriter) writeLicense(license *boshlic.License, stagingDir string) ([]string, error) {
187	var licenseFiles []string
188
189	if license == nil {
190		return licenseFiles, nil
191	}
192
193	err := w.fs.CopyFile(license.ArchivePath(), filepath.Join(stagingDir, "license.tgz"))
194	if err != nil {
195		return licenseFiles, bosherr.WrapError(err, "Copying license archive into staging dir")
196	}
197
198	licenseFiles = append(licenseFiles, "license.tgz")
199
200	err = w.compressor.DecompressFileToDir(license.ArchivePath(), stagingDir, boshcmd.CompressorOptions{})
201	if err != nil {
202		return licenseFiles, bosherr.WrapErrorf(err, "Decompressing license archive into staging dir")
203	}
204
205	licenseFiles, err = w.appendMatchedFiles(licenseFiles, stagingDir, "LICENSE*")
206	if err != nil {
207		return licenseFiles, bosherr.WrapErrorf(err, "Reading LICENSE files")
208	}
209
210	licenseFiles, err = w.appendMatchedFiles(licenseFiles, stagingDir, "NOTICE*")
211	if err != nil {
212		return licenseFiles, bosherr.WrapErrorf(err, "Reading NOTICE files")
213	}
214
215	return licenseFiles, nil
216}
217
218func (w ArchiveWriter) shouldSkip(fp string, pkgFpsToSkip []string) bool {
219	for _, pkgFp := range pkgFpsToSkip {
220		if fp == pkgFp {
221			return true
222		}
223	}
224	return false
225}
226
227func (w ArchiveWriter) appendFile(filename string) []string {
228	return append(w.filesToInclude, filename)
229}
230
231func (w ArchiveWriter) appendFiles(filenames []string) []string {
232	for _, filename := range filenames {
233		w.filesToInclude = w.appendFile(filename)
234	}
235
236	return w.filesToInclude
237}
238
239func (w ArchiveWriter) appendMatchedFiles(files []string, stagingDir string, filePattern string) ([]string, error) {
240	noticeMatches, err := w.fs.Glob(filepath.Join(stagingDir, filePattern))
241	if err != nil {
242		return files, err
243	}
244	for _, file := range noticeMatches {
245		files = append(files, filepath.Base(file))
246	}
247
248	return files, nil
249}
250