|
| 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