1package releasedir 2 3import ( 4 "fmt" 5 "path/filepath" 6 "sort" 7 8 bosherr "github.com/cloudfoundry/bosh-utils/errors" 9 boshsys "github.com/cloudfoundry/bosh-utils/system" 10 boshuuid "github.com/cloudfoundry/bosh-utils/uuid" 11 semver "github.com/cppforlife/go-semi-semantic/version" 12 "gopkg.in/yaml.v2" 13 14 boshrel "github.com/cloudfoundry/bosh-cli/release" 15 boshrelman "github.com/cloudfoundry/bosh-cli/release/manifest" 16) 17 18type FSReleaseIndex struct { 19 name string 20 dirPath string 21 reporter ReleaseIndexReporter 22 23 uuidGen boshuuid.Generator 24 fs boshsys.FileSystem 25} 26 27/* 28--- 29builds: 30 70b9ea8efb83b882021792517b1164550b41bc27: 31 version: "1" 32format-version: "2" 33*/ 34 35type fsReleaseIndexSchema struct { 36 Builds fsReleaseIndexSchema_SortedEntries `yaml:"builds"` 37 38 FormatVersion string `yaml:"format-version"` 39} 40 41type fsReleaseIndexSchema_SortedEntries map[string]fsReleaseIndexSchema_Entry 42 43var _ yaml.Marshaler = fsReleaseIndexSchema_SortedEntries{} 44 45func (e fsReleaseIndexSchema_SortedEntries) MarshalYAML() (interface{}, error) { 46 var keys []string 47 for k, _ := range e { 48 keys = append(keys, k) 49 } 50 sort.Sort(sort.StringSlice(keys)) 51 var sortedEntries []yaml.MapItem 52 for _, k := range keys { 53 sortedEntries = append(sortedEntries, yaml.MapItem{Key: k, Value: e[k]}) 54 } 55 return sortedEntries, nil 56} 57 58type fsReleaseIndexSchema_Entry struct { 59 Version string `yaml:"version"` 60} 61 62type releaseIndexEntry struct { 63 Version semver.Version 64} 65 66func NewFSReleaseIndex( 67 name string, 68 dirPath string, 69 reporter ReleaseIndexReporter, 70 uuidGen boshuuid.Generator, 71 fs boshsys.FileSystem, 72) FSReleaseIndex { 73 return FSReleaseIndex{ 74 name: name, 75 dirPath: dirPath, 76 reporter: reporter, 77 uuidGen: uuidGen, 78 fs: fs, 79 } 80} 81 82func (i FSReleaseIndex) LastVersion(name string) (*semver.Version, error) { 83 if len(name) == 0 { 84 return nil, bosherr.Error("Expected non-empty release name") 85 } 86 87 schema, err := i.read(name) 88 if err != nil { 89 return nil, err 90 } 91 92 var versions []semver.Version 93 94 for _, entry := range schema.Builds { 95 ver, err := semver.NewVersionFromString(entry.Version) 96 if err != nil { 97 return nil, bosherr.WrapErrorf(err, "Parsing release versions") 98 } 99 100 versions = append(versions, ver) 101 } 102 103 if len(versions) == 0 { 104 return nil, nil 105 } 106 107 sort.Sort(semver.AscSorting(versions)) 108 109 return &versions[len(versions)-1], nil 110} 111 112func (i FSReleaseIndex) Contains(release boshrel.Release) (bool, error) { 113 if len(release.Name()) == 0 { 114 return false, bosherr.Error("Expected non-empty release name") 115 } 116 117 if len(release.Version()) == 0 { 118 return false, bosherr.Error("Expected non-empty release version") 119 } 120 121 schema, err := i.read(release.Name()) 122 if err != nil { 123 return false, err 124 } 125 126 for _, entry := range schema.Builds { 127 if entry.Version == release.Version() { 128 return true, nil 129 } 130 } 131 132 return false, nil 133} 134 135func (i FSReleaseIndex) Add(manifest boshrelman.Manifest) error { 136 if len(manifest.Name) == 0 { 137 return bosherr.Error("Expected non-empty release name") 138 } 139 140 if len(manifest.Version) == 0 { 141 return bosherr.Error("Expected non-empty release version") 142 } 143 144 schema, err := i.read(manifest.Name) 145 if err != nil { 146 return err 147 } 148 149 for _, entry := range schema.Builds { 150 if entry.Version == manifest.Version { 151 return bosherr.Errorf("Release version '%s' already exists", manifest.Version) 152 } 153 } 154 155 uuid, err := i.uuidGen.Generate() 156 if err != nil { 157 return bosherr.WrapErrorf(err, "Generating key for release index entry") 158 } 159 160 schema.Builds[uuid] = fsReleaseIndexSchema_Entry{Version: manifest.Version} 161 162 desc := fmt.Sprintf("%s/%s", manifest.Name, manifest.Version) 163 164 err = i.saveManifest(manifest) 165 if err != nil { 166 i.reporter.ReleaseIndexAdded(i.name, desc, err) 167 return err 168 } 169 170 err = i.save(manifest.Name, schema) 171 if err != nil { 172 i.reporter.ReleaseIndexAdded(i.name, desc, err) 173 return err 174 } 175 176 i.reporter.ReleaseIndexAdded(i.name, desc, nil) 177 178 return nil 179} 180 181func (i FSReleaseIndex) ManifestPath(name, version string) string { 182 fileName := fmt.Sprintf("%s-%s.yml", name, version) 183 184 return filepath.Join(i.dirPath, name, fileName) 185} 186 187func (i FSReleaseIndex) indexPath(name string) string { 188 return filepath.Join(i.dirPath, name, "index.yml") 189} 190 191func (i FSReleaseIndex) read(name string) (fsReleaseIndexSchema, error) { 192 var schema fsReleaseIndexSchema 193 194 // Default to an empty map 195 schema.Builds = fsReleaseIndexSchema_SortedEntries{} 196 197 indexPath := i.indexPath(name) 198 199 if !i.fs.FileExists(indexPath) { 200 return schema, nil 201 } 202 203 bytes, err := i.fs.ReadFile(indexPath) 204 if err != nil { 205 return schema, bosherr.WrapErrorf(err, "Reading index") 206 } 207 208 err = yaml.Unmarshal(bytes, &schema) 209 if err != nil { 210 return schema, bosherr.WrapError(err, "Unmarshalling index") 211 } 212 213 return schema, nil 214} 215 216func (i FSReleaseIndex) save(name string, schema fsReleaseIndexSchema) error { 217 schema.FormatVersion = "2" 218 219 bytes, err := yaml.Marshal(schema) 220 if err != nil { 221 return bosherr.WrapError(err, "Marshalling index") 222 } 223 224 indexPath := i.indexPath(name) 225 226 err = i.fs.WriteFile(indexPath, bytes) 227 if err != nil { 228 return bosherr.WrapErrorf(err, "Writing index") 229 } 230 231 return nil 232} 233 234func (i FSReleaseIndex) saveManifest(manifest boshrelman.Manifest) error { 235 bytes, err := yaml.Marshal(manifest) 236 if err != nil { 237 return bosherr.WrapError(err, "Marshalling manifest") 238 } 239 240 manifestPath := i.ManifestPath(manifest.Name, manifest.Version) 241 242 err = i.fs.WriteFile(manifestPath, bytes) 243 if err != nil { 244 return bosherr.WrapErrorf(err, "Writing manifest") 245 } 246 247 return nil 248} 249