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