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