Skip to content

Commit 4db43cc

Browse files
authored
Merge pull request #77 from cloud66-oss/feature/3517-bundle-flag
Creation of Formation Bundle
2 parents 2978df9 + 540d1d7 commit 4db43cc

File tree

13 files changed

+699
-87
lines changed

13 files changed

+699
-87
lines changed

bundle/bundle-writer.go

Lines changed: 398 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,398 @@
1+
package bundle
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/cloud66-oss/starter/common"
7+
"io/ioutil"
8+
"os"
9+
"path/filepath"
10+
"time"
11+
)
12+
13+
type ManifestBundle struct {
14+
Version string `json:"version"`
15+
Metadata *Metadata `json:"metadata"`
16+
UID string `json:"uid"`
17+
Name string `json:"name"`
18+
StencilGroups []*BundleStencilGroup `json:"stencil_groups"`
19+
BaseTemplates []*BundleBaseTemplates `json:"base_template"`
20+
Policies []*BundlePolicy `json:"policies"`
21+
Tags []string `json:"tags"`
22+
HelmReleases []*BundleHelmRelease `json:"helm_releases"`
23+
Configurations []string `json:"configuration"`
24+
}
25+
26+
type BundleHelmRelease struct {
27+
Name string `json:"repo"`
28+
Version string `json:"version"`
29+
RepositoryURL string `json:"repository_url"`
30+
ValuesFile string `json:"values_file"`
31+
}
32+
33+
type BundleConfiguration struct {
34+
Repo string `json:"repo"`
35+
Branch string `json:"branch"`
36+
}
37+
38+
type BundleBaseTemplates struct {
39+
Repo string `json:"repo"`
40+
Branch string `json:"branch"`
41+
Stencils []*BundleStencil `json:"stencils"`
42+
}
43+
44+
type Metadata struct {
45+
App string `json:"app"`
46+
Timestamp time.Time `json:"timestamp"`
47+
Annotations []string `json:"annotations"`
48+
}
49+
50+
type BundleStencil struct {
51+
UID string `json:"uid"`
52+
Filename string `json:"filename"`
53+
TemplateFilename string `json:"template_filename"`
54+
ContextID string `json:"context_id"`
55+
Status int `json:"status"`
56+
Tags []string `json:"tags"`
57+
Sequence int `json:"sequence"`
58+
}
59+
60+
type BundleStencilGroup struct {
61+
UID string `json:"uid"`
62+
Name string `json:"name"`
63+
Tags []string `json:"tags"`
64+
}
65+
66+
type BundlePolicy struct {
67+
UID string `json:"uid"`
68+
Name string `json:"name"`
69+
Selector string `json:"selector"`
70+
Tags []string `json:"tags"`
71+
}
72+
73+
type TemplateJSON struct {
74+
Version string `json:"version"`
75+
Public bool `json:"public"`
76+
Name string `json:"name"`
77+
Icon string `json:"icon"`
78+
LongName string `json:"long_name"`
79+
Description string `json:"description"`
80+
Templates *TemplatesStruct `json:"templates"`
81+
}
82+
83+
type TemplatesStruct struct {
84+
Stencils []*StencilTemplate `json:"stencils"`
85+
Policies []*PolicyTemplate `json:"policies"`
86+
Transformers []*TransformersTemplate `json:"transformers"`
87+
}
88+
89+
type StencilTemplate struct {
90+
Name string `json:"name"`
91+
FilenamePattern string `json:"filename_pattern"`
92+
Filename string `json:"filename"`
93+
Description string `json:"description"`
94+
ContextType string `json:"context_type"`
95+
Tags []string `json:"tags"`
96+
PreferredSequence int `json:"preferred_sequence"`
97+
Suggested bool `json:"suggested"`
98+
MinUsage int `json:"min_usage"`
99+
MaxUsage int `json:"max_usage"`
100+
}
101+
102+
type PolicyTemplate struct{}
103+
104+
type TransformersTemplate struct{}
105+
106+
func CreateSkycapFiles(outputDir string,
107+
templateRepository string,
108+
branch string,
109+
packName string,
110+
githubURL string,
111+
services []*common.Service,
112+
databases []common.Database) error {
113+
114+
if templateRepository == "" {
115+
//no stencil template defined for this pack, print an error and do nothing
116+
fmt.Printf("Sorry but there is no stencil template for this language/framework yet\n")
117+
return nil
118+
}
119+
//Create .bundle directory structure if it doesn't exist
120+
tempFolder := os.TempDir()
121+
skycapFolder := filepath.Join(tempFolder, "skycap")
122+
defer os.RemoveAll(skycapFolder)
123+
err := createBundleFolderStructure(skycapFolder)
124+
if err != nil {
125+
return err
126+
}
127+
//create manifest.json file and start filling
128+
manifestFile, err := loadManifest()
129+
if err != nil {
130+
return err
131+
}
132+
133+
manifestFile, err = saveEnvVars(packName, getEnvVars(services, databases), manifestFile, skycapFolder)
134+
if err != nil {
135+
return err
136+
}
137+
138+
manifestFile, err = addDatabase(manifestFile, databases)
139+
140+
manifestFile, err = getRequiredStencils(
141+
templateRepository,
142+
branch,
143+
outputDir,
144+
services,
145+
skycapFolder,
146+
manifestFile,
147+
githubURL)
148+
149+
if err != nil {
150+
return err
151+
}
152+
153+
manifestFile, err = addMetadata(manifestFile)
154+
155+
if err != nil {
156+
return err
157+
}
158+
159+
err = saveManifest(skycapFolder, manifestFile)
160+
if err != nil {
161+
return err
162+
}
163+
164+
// tarball
165+
err = os.RemoveAll(filepath.Join(skycapFolder, "temp"))
166+
if err != nil {
167+
common.PrintError(err.Error())
168+
}
169+
170+
err = common.Tar(skycapFolder, filepath.Join(outputDir, "starter.bundle"))
171+
if err != nil {
172+
common.PrintError(err.Error())
173+
}
174+
fmt.Printf("Bundle is saved to starter.bundle\n")
175+
176+
return err
177+
}
178+
179+
// downloading templates from github and putting them into homedir
180+
func getStencilTemplateFile(templateRepository string, tempFolder string, filename string, branch string) (string, error) {
181+
182+
//Download templates.json file
183+
manifestPath := templateRepository + filename // don't need to use filepath since it's a URL
184+
downErr := common.DownloadSingleFile(tempFolder, common.DownloadFile{URL: manifestPath, Name: filename}, branch)
185+
if downErr != nil {
186+
return "", downErr
187+
}
188+
return filepath.Join(tempFolder, filename), nil
189+
}
190+
191+
func getEnvVars(servs []*common.Service, databases []common.Database) map[string]string {
192+
var envas = make(map[string]string)
193+
for _, envVarArray := range servs {
194+
for _, envs := range envVarArray.EnvVars {
195+
envas[envs.Key] = envs.Value
196+
}
197+
}
198+
return envas
199+
}
200+
201+
func createBundleFolderStructure(baseFolder string) error {
202+
var folders = [5]string{"stencils", "policies", "stencil-group", "helm-releases", "configurations"}
203+
for _, subfolder := range folders {
204+
folder := filepath.Join(baseFolder, subfolder)
205+
err := os.MkdirAll(folder, 0777)
206+
if err != nil {
207+
return err
208+
}
209+
}
210+
return nil
211+
}
212+
213+
func getRequiredStencils(templateRepository string,
214+
branch string,
215+
outputDir string,
216+
services []*common.Service,
217+
skycapFolder string,
218+
manifestFile *ManifestBundle,
219+
githubURL string) (*ManifestBundle, error) {
220+
221+
templateFolder := filepath.Join(os.TempDir(), "temp")
222+
err := os.MkdirAll(templateFolder, 0777)
223+
defer os.RemoveAll(templateFolder)
224+
if err != nil {
225+
return nil, err
226+
}
227+
//start download the template.json file
228+
tjPathfile, err := getStencilTemplateFile(templateRepository, templateFolder, "templates.json", branch)
229+
if err != nil {
230+
fmt.Printf("Error while downloading the templates.json. err: %s", err)
231+
return nil, err
232+
}
233+
// open the template.json file and start downloading the stencils
234+
templateJSON, err := os.Open(tjPathfile)
235+
if err != nil {
236+
return nil, err
237+
}
238+
239+
templatesJSONData, err := ioutil.ReadAll(templateJSON)
240+
if err != nil {
241+
return nil, err
242+
}
243+
244+
var templJSON TemplateJSON
245+
err = json.Unmarshal([]byte(templatesJSONData), &templJSON)
246+
if err != nil {
247+
return nil, err
248+
}
249+
250+
var manifestStencils = make([]*BundleStencil, 0)
251+
for _, stencil := range templJSON.Templates.Stencils {
252+
if stencil.MinUsage > 0 {
253+
if stencil.ContextType == "service" {
254+
for _, service := range services {
255+
manifestFile, manifestStencils, err = downloadAndAddStencil(
256+
service.Name,
257+
stencil,
258+
manifestFile,
259+
skycapFolder,
260+
templateRepository,
261+
branch,
262+
manifestStencils)
263+
// create entry in manifest file with formatted name
264+
// download and rename stencil file
265+
}
266+
} else {
267+
manifestFile, manifestStencils, err = downloadAndAddStencil(
268+
"",
269+
stencil,
270+
manifestFile,
271+
skycapFolder,
272+
templateRepository,
273+
branch,
274+
manifestStencils)
275+
}
276+
}
277+
}
278+
var newTemplate BundleBaseTemplates
279+
newTemplate.Repo = githubURL
280+
newTemplate.Branch = branch
281+
newTemplate.Stencils = manifestStencils
282+
283+
manifestFile.BaseTemplates = append(manifestFile.BaseTemplates, &newTemplate)
284+
285+
return manifestFile, nil
286+
}
287+
288+
func loadManifest() (*ManifestBundle, error) {
289+
manifest := &ManifestBundle{
290+
Version: "1",
291+
Metadata: nil,
292+
UID: "",
293+
Name: "",
294+
StencilGroups: make([]*BundleStencilGroup, 0),
295+
BaseTemplates: make([]*BundleBaseTemplates, 0),
296+
Policies: make([]*BundlePolicy, 0),
297+
Tags: make([]string, 0),
298+
HelmReleases: make([]*BundleHelmRelease, 0),
299+
Configurations: make([]string, 0),
300+
}
301+
return manifest, nil
302+
}
303+
304+
func saveManifest(skycapFolder string, content *ManifestBundle) error {
305+
out, err := json.MarshalIndent(content, "", " ")
306+
if err != nil {
307+
return err
308+
}
309+
manifestPath := filepath.Join(skycapFolder, "manifest.json")
310+
return ioutil.WriteFile(manifestPath, out, 0600)
311+
}
312+
313+
func saveEnvVars(prefix string, envVars map[string]string, manifestFile *ManifestBundle, skycapFolder string) (*ManifestBundle, error) {
314+
filename := prefix + "-config"
315+
varsPath := filepath.Join(filepath.Join(skycapFolder, "configurations"), prefix+"-config")
316+
var fileOut string
317+
for key, value := range envVars {
318+
fileOut = fileOut + key + "=" + value + "\n"
319+
}
320+
err := ioutil.WriteFile(varsPath, []byte(fileOut), 0600)
321+
if err != nil {
322+
return nil, err
323+
}
324+
var configs = manifestFile.Configurations
325+
manifestFile.Configurations = append(configs, filename)
326+
return manifestFile, nil
327+
}
328+
329+
func downloadAndAddStencil(context string,
330+
stencil *StencilTemplate,
331+
manifestFile *ManifestBundle,
332+
skycapFolder string,
333+
templateRepository string,
334+
branch string,
335+
manifestStencils []*BundleStencil) (*ManifestBundle, []*BundleStencil, error) {
336+
var filename = ""
337+
if context != "" {
338+
filename = context + "_"
339+
}
340+
filename = filename + stencil.Filename
341+
342+
//download the stencil file
343+
stencilPath := templateRepository + "stencils/" + stencil.Filename // don't need to use filepath since it's a URL
344+
stencilsFolder := filepath.Join(skycapFolder, "stencils")
345+
downErr := common.DownloadSingleFile(stencilsFolder, common.DownloadFile{URL: stencilPath, Name: filename}, branch)
346+
if downErr != nil {
347+
return nil, nil, downErr
348+
}
349+
350+
// Add the entry to the manifest file
351+
var tempStencil BundleStencil
352+
tempStencil.UID = ""
353+
tempStencil.Filename = filename
354+
tempStencil.TemplateFilename = stencil.Filename
355+
tempStencil.ContextID = context
356+
tempStencil.Status = 2 // it means that the stencils still need to be deployed
357+
tempStencil.Tags = []string{"starter"}
358+
tempStencil.Sequence = stencil.PreferredSequence
359+
360+
manifestStencils = append(manifestStencils, &tempStencil)
361+
362+
return manifestFile, manifestStencils, nil
363+
}
364+
365+
func addMetadata(manifestFile *ManifestBundle) (*ManifestBundle, error) {
366+
var metadata = &Metadata{
367+
Annotations: []string{"Generated by Cloud 66 starter"},
368+
App: "starter",
369+
Timestamp: time.Now().UTC(),
370+
}
371+
manifestFile.Metadata = metadata
372+
manifestFile.Name = "starter-formation"
373+
manifestFile.Tags = []string{"starter"}
374+
return manifestFile, nil
375+
}
376+
377+
func addDatabase(manifestFile *ManifestBundle, databases []common.Database) (*ManifestBundle, error) {
378+
var helmReleases = make([]*BundleHelmRelease, 0)
379+
var release BundleHelmRelease
380+
for _, db := range databases {
381+
switch db.Name {
382+
case "mysql":
383+
release.Name = db.Name
384+
release.Version = "0.10.2"
385+
case "postgresql":
386+
release.Name = "postgresql"
387+
release.Version = "3.1.0"
388+
default:
389+
common.PrintlnWarning("Database %s not supported\n", db.Name)
390+
continue
391+
}
392+
release.RepositoryURL = "https://kubernetes-charts.storage.googleapis.com/"
393+
release.ValuesFile = ""
394+
helmReleases = append(helmReleases, &release)
395+
}
396+
manifestFile.HelmReleases = helmReleases
397+
return manifestFile, nil
398+
}

0 commit comments

Comments
 (0)