1package stemcell
2
3import (
4	"fmt"
5	"path/filepath"
6
7	bicloud "github.com/cloudfoundry/bosh-cli/cloud"
8	biconfig "github.com/cloudfoundry/bosh-cli/config"
9	biui "github.com/cloudfoundry/bosh-cli/ui"
10	bosherr "github.com/cloudfoundry/bosh-utils/errors"
11)
12
13type Manager interface {
14	FindCurrent() ([]CloudStemcell, error)
15	Upload(ExtractedStemcell, biui.Stage) (CloudStemcell, error)
16	FindUnused() ([]CloudStemcell, error)
17	DeleteUnused(biui.Stage) error
18}
19
20type manager struct {
21	repo  biconfig.StemcellRepo
22	cloud bicloud.Cloud
23}
24
25func NewManager(repo biconfig.StemcellRepo, cloud bicloud.Cloud) Manager {
26	return &manager{
27		repo:  repo,
28		cloud: cloud,
29	}
30}
31
32func (m *manager) FindCurrent() ([]CloudStemcell, error) {
33	stemcells := []CloudStemcell{}
34
35	stemcellRecord, found, err := m.repo.FindCurrent()
36	if err != nil {
37		return stemcells, bosherr.WrapError(err, "Reading stemcell record")
38	}
39
40	if found {
41		stemcell := NewCloudStemcell(stemcellRecord, m.repo, m.cloud)
42		stemcells = append(stemcells, stemcell)
43	}
44
45	return stemcells, nil
46}
47
48// Upload stemcell to an IAAS. It does the following steps:
49// 1) uploads the stemcell to the cloud (if needed),
50// 2) saves a record of the uploaded stemcell in the repo
51func (m *manager) Upload(extractedStemcell ExtractedStemcell, uploadStage biui.Stage) (cloudStemcell CloudStemcell, err error) {
52	manifest := extractedStemcell.Manifest()
53	stageName := fmt.Sprintf("Uploading stemcell '%s/%s'", manifest.Name, manifest.Version)
54	err = uploadStage.Perform(stageName, func() error {
55		foundStemcellRecord, found, err := m.repo.Find(manifest.Name, manifest.Version)
56		if err != nil {
57			return bosherr.WrapError(err, "Finding existing stemcell record in repo")
58		}
59
60		if found {
61			cloudStemcell = NewCloudStemcell(foundStemcellRecord, m.repo, m.cloud)
62			return biui.NewSkipStageError(bosherr.Errorf("Found stemcell: %#v", foundStemcellRecord), "Stemcell already uploaded")
63		}
64
65		cid, err := m.cloud.CreateStemcell(filepath.Join(extractedStemcell.GetExtractedPath(), "image"), manifest.CloudProperties)
66		if err != nil {
67			return bosherr.WrapErrorf(err, "creating stemcell (%s %s)", manifest.Name, manifest.Version)
68		}
69
70		stemcellRecord, err := m.repo.Save(manifest.Name, manifest.Version, cid, manifest.ApiVersion)
71		if err != nil {
72			//TODO: delete stemcell from cloud when saving fails
73			return bosherr.WrapErrorf(err, "saving stemcell record in repo (cid=%s, stemcell=%s)", cid, extractedStemcell)
74		}
75
76		cloudStemcell = NewCloudStemcell(stemcellRecord, m.repo, m.cloud)
77		return nil
78	})
79	if err != nil {
80		return cloudStemcell, err
81	}
82
83	return cloudStemcell, nil
84}
85
86func (m *manager) FindUnused() ([]CloudStemcell, error) {
87	unusedStemcells := []CloudStemcell{}
88
89	stemcellRecords, err := m.repo.All()
90	if err != nil {
91		return unusedStemcells, bosherr.WrapError(err, "Getting all stemcell records")
92	}
93
94	currentStemcellRecord, found, err := m.repo.FindCurrent()
95	if err != nil {
96		return unusedStemcells, bosherr.WrapError(err, "Finding current disk record")
97	}
98
99	for _, stemcellRecord := range stemcellRecords {
100		if !found || stemcellRecord.ID != currentStemcellRecord.ID {
101			stemcell := NewCloudStemcell(stemcellRecord, m.repo, m.cloud)
102			unusedStemcells = append(unusedStemcells, stemcell)
103		}
104	}
105
106	return unusedStemcells, nil
107}
108
109func (m *manager) DeleteUnused(deleteStage biui.Stage) error {
110	stemcells, err := m.FindUnused()
111	if err != nil {
112		return bosherr.WrapError(err, "Finding unused stemcells")
113	}
114
115	for _, stemcell := range stemcells {
116		stepName := fmt.Sprintf("Deleting unused stemcell '%s'", stemcell.CID())
117		err = deleteStage.Perform(stepName, func() error {
118			err := stemcell.Delete()
119			cloudErr, ok := err.(bicloud.Error)
120			if ok && cloudErr.Type() == bicloud.StemcellNotFoundError {
121				return biui.NewSkipStageError(cloudErr, "Stemcell not found")
122			}
123			return err
124		})
125		if err != nil {
126			return err
127		}
128	}
129
130	return nil
131}
132