1package releasedir 2 3import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "code.cloudfoundry.org/clock" 10 bosherr "github.com/cloudfoundry/bosh-utils/errors" 11 boshsys "github.com/cloudfoundry/bosh-utils/system" 12 semver "github.com/cppforlife/go-semi-semantic/version" 13 14 boshrel "github.com/cloudfoundry/bosh-cli/release" 15 boshpkg "github.com/cloudfoundry/bosh-cli/release/pkg" 16 boshpkgman "github.com/cloudfoundry/bosh-cli/release/pkg/manifest" 17) 18 19var ( 20 DefaultFinalVersion = semver.MustNewVersionFromString("1") 21 DefaultDevVersion = semver.MustNewVersionFromString("0+dev.0") 22 DefaultDevPostRelease = semver.MustNewVersionSegmentFromString("dev.1") 23) 24 25type FSReleaseDir struct { 26 dirPath string 27 28 config Config 29 gitRepo GitRepo 30 blobsDir BlobsDir 31 generator Generator 32 33 devReleases ReleaseIndex 34 finalReleases ReleaseIndex 35 finalIndicies boshrel.ArchiveIndicies 36 37 releaseReader boshrel.Reader 38 releaseArchiveWriter boshrel.Writer 39 40 timeService clock.Clock 41 fs boshsys.FileSystem 42 43 parallel int 44} 45 46func NewFSReleaseDir( 47 dirPath string, 48 config Config, 49 gitRepo GitRepo, 50 blobsDir BlobsDir, 51 generator Generator, 52 devReleases ReleaseIndex, 53 finalReleases ReleaseIndex, 54 finalIndicies boshrel.ArchiveIndicies, 55 releaseReader boshrel.Reader, 56 timeService clock.Clock, 57 fs boshsys.FileSystem, 58 parallel int, 59) FSReleaseDir { 60 return FSReleaseDir{ 61 dirPath: dirPath, 62 63 config: config, 64 gitRepo: gitRepo, 65 blobsDir: blobsDir, 66 generator: generator, 67 68 devReleases: devReleases, 69 finalReleases: finalReleases, 70 finalIndicies: finalIndicies, 71 72 releaseReader: releaseReader, 73 74 timeService: timeService, 75 fs: fs, 76 77 parallel: parallel, 78 } 79} 80 81func (d FSReleaseDir) Init(git bool) error { 82 for _, name := range []string{"jobs", "packages", "src"} { 83 err := d.fs.MkdirAll(filepath.Join(d.dirPath, name), os.ModePerm) 84 if err != nil { 85 return bosherr.WrapErrorf(err, "Creating %s/", name) 86 } 87 } 88 89 name := strings.TrimSuffix(filepath.Base(d.dirPath), "-release") 90 91 err := d.config.SaveName(name) 92 if err != nil { 93 return err 94 } 95 96 err = d.blobsDir.Init() 97 if err != nil { 98 return bosherr.WrapErrorf(err, "Initing blobs") 99 } 100 101 if git { 102 err = d.gitRepo.Init() 103 if err != nil { 104 return err 105 } 106 } 107 108 return nil 109} 110 111func (d FSReleaseDir) GenerateJob(name string) error { 112 return d.generator.GenerateJob(name) 113} 114 115func (d FSReleaseDir) GeneratePackage(name string) error { 116 return d.generator.GeneratePackage(name) 117} 118 119func (d FSReleaseDir) Reset() error { 120 for _, name := range []string{".dev_builds", "dev_releases", ".blobs", "blobs"} { 121 err := d.fs.RemoveAll(filepath.Join(d.dirPath, name)) 122 if err != nil { 123 return bosherr.WrapErrorf(err, "Removing %s/", name) 124 } 125 } 126 127 return nil 128} 129 130func (d FSReleaseDir) DefaultName() (string, error) { 131 return d.config.Name() 132} 133 134func (d FSReleaseDir) NextFinalVersion(name string) (semver.Version, error) { 135 lastVer, err := d.finalReleases.LastVersion(name) 136 if err != nil { 137 return semver.Version{}, err 138 } else if lastVer == nil { 139 return DefaultFinalVersion, nil 140 } 141 142 incVer, err := lastVer.IncrementRelease() 143 if err != nil { 144 return semver.Version{}, bosherr.WrapErrorf(err, "Incrementing last final version") 145 } 146 147 return incVer, nil 148} 149 150func (d FSReleaseDir) NextDevVersion(name string, timestamp bool) (semver.Version, error) { 151 lastVer, _, err := d.lastDevOrFinalVersion(name) 152 if err != nil { 153 return semver.Version{}, err 154 } else if lastVer == nil { 155 lastVer = &DefaultDevVersion 156 } 157 158 incVer, err := lastVer.IncrementPostRelease(DefaultDevPostRelease) 159 if err != nil { 160 return semver.Version{}, bosherr.WrapErrorf(err, "Incrementing last dev version") 161 } 162 163 if timestamp { 164 ts := d.timeService.Now().Unix() 165 166 postRelease, err := semver.NewVersionSegmentFromString(fmt.Sprintf("dev.%d", ts)) 167 if err != nil { 168 panic(fmt.Sprintf("Failed to build post release version segment from timestamp (%d): %s", ts, err)) 169 } 170 171 incVer, err = semver.NewVersion(incVer.Release.Copy(), incVer.PreRelease.Copy(), postRelease) 172 if err != nil { 173 panic(fmt.Sprintf("Failed to build version: %s", err)) 174 } 175 } 176 177 return incVer, nil 178} 179 180func (d FSReleaseDir) FindRelease(name string, version semver.Version) (boshrel.Release, error) { 181 if len(name) == 0 { 182 defaultName, err := d.DefaultName() 183 if err != nil { 184 return nil, err 185 } 186 name = defaultName 187 } 188 189 relIndex := d.finalReleases 190 191 if version.Empty() { 192 lastVer, lastRelIndex, err := d.lastDevOrFinalVersion(name) 193 if err != nil { 194 return nil, err 195 } else if lastVer == nil { 196 return nil, bosherr.Errorf("Expected to find at least one dev or final version") 197 } 198 version = *lastVer 199 relIndex = lastRelIndex 200 } 201 202 return d.releaseReader.Read(relIndex.ManifestPath(name, version.AsString())) 203} 204 205func (d FSReleaseDir) BuildRelease(name string, version semver.Version, force bool) (boshrel.Release, error) { 206 dirty, err := d.gitRepo.MustNotBeDirty(force) 207 if err != nil { 208 return nil, err 209 } 210 211 commitSHA, err := d.gitRepo.LastCommitSHA() 212 if err != nil { 213 return nil, err 214 } 215 216 err = d.blobsDir.SyncBlobs(1) 217 if err != nil { 218 return nil, err 219 } 220 221 release, err := d.releaseReader.Read(d.dirPath) 222 if err != nil { 223 return nil, bosherr.WrapErrorf(err, "Building a release from directory '%s'", d.dirPath) 224 } 225 226 release.SetName(name) 227 release.SetVersion(version.AsString()) 228 release.SetCommitHash(commitSHA) 229 release.SetUncommittedChanges(dirty) 230 231 err = d.devReleases.Add(release.Manifest()) 232 if err != nil { 233 return nil, err 234 } 235 236 return release, nil 237} 238 239func (d FSReleaseDir) VendorPackage(pkg *boshpkg.Package) error { 240 allInterestingPkgs := map[*boshpkg.Package]struct{}{} 241 242 d.collectDependentPackages(pkg, allInterestingPkgs) 243 244 for pkg2, _ := range allInterestingPkgs { 245 err := pkg2.Finalize(d.finalIndicies.Packages) 246 if err != nil { 247 return bosherr.WrapErrorf(err, "Finalizing vendored package") 248 } 249 250 err = d.writeVendoredPackage(pkg2) 251 if err != nil { 252 return bosherr.WrapErrorf(err, "Writing vendored package") 253 } 254 } 255 256 return nil 257} 258 259func (d FSReleaseDir) collectDependentPackages(pkg *boshpkg.Package, allInterestingPkgs map[*boshpkg.Package]struct{}) { 260 allInterestingPkgs[pkg] = struct{}{} 261 for _, pkg2 := range pkg.Dependencies { 262 d.collectDependentPackages(pkg2, allInterestingPkgs) 263 } 264} 265 266func (d FSReleaseDir) writeVendoredPackage(pkg *boshpkg.Package) error { 267 name := pkg.Name() 268 pkgDirPath := filepath.Join(d.dirPath, "packages", name) 269 270 err := d.fs.RemoveAll(pkgDirPath) 271 if err != nil { 272 return bosherr.WrapErrorf(err, "Removing package '%s' dir", name) 273 } 274 275 err = d.fs.MkdirAll(pkgDirPath, os.ModePerm) 276 if err != nil { 277 return bosherr.WrapErrorf(err, "Creating package '%s' dir", name) 278 } 279 280 manifestLock := boshpkgman.ManifestLock{Name: name, Fingerprint: pkg.Fingerprint()} 281 282 for _, pkg2 := range pkg.Dependencies { 283 manifestLock.Dependencies = append(manifestLock.Dependencies, pkg2.Name()) 284 } 285 286 manifestLockBytes, err := manifestLock.AsBytes() 287 if err != nil { 288 return bosherr.WrapErrorf(err, "Marshaling vendored package '%s' spec lock", name) 289 } 290 291 err = d.fs.WriteFile(filepath.Join(pkgDirPath, "spec.lock"), manifestLockBytes) 292 if err != nil { 293 return bosherr.WrapErrorf(err, "Creating package '%s' spec lock file", name) 294 } 295 296 return nil 297} 298 299func (d FSReleaseDir) FinalizeRelease(release boshrel.Release, force bool) error { 300 _, err := d.gitRepo.MustNotBeDirty(force) 301 if err != nil { 302 return err 303 } 304 305 found, err := d.finalReleases.Contains(release) 306 if err != nil { 307 return err 308 } else if found { 309 return bosherr.Errorf("Release '%s' version '%s' already exists", release.Name(), release.Version()) 310 } 311 312 err = release.Finalize(d.finalIndicies, d.parallel) 313 if err != nil { 314 return err 315 } 316 317 return d.finalReleases.Add(release.Manifest()) 318} 319 320func (d FSReleaseDir) lastDevOrFinalVersion(name string) (*semver.Version, ReleaseIndex, error) { 321 lastDevVer, err := d.devReleases.LastVersion(name) 322 if err != nil { 323 return nil, nil, err 324 } 325 326 lastFinalVer, err := d.finalReleases.LastVersion(name) 327 if err != nil { 328 return nil, nil, err 329 } 330 331 switch { 332 case lastDevVer != nil && lastFinalVer != nil: 333 if lastFinalVer.IsGt(*lastDevVer) { 334 return lastFinalVer, d.finalReleases, nil 335 } else { 336 return lastDevVer, d.devReleases, nil 337 } 338 case lastDevVer != nil && lastFinalVer == nil: 339 return lastDevVer, d.devReleases, nil 340 case lastDevVer == nil && lastFinalVer != nil: 341 return lastFinalVer, d.finalReleases, nil 342 default: 343 return nil, nil, nil 344 } 345} 346