func loadBrickMetadata(context *pongo2.Context, baseDir *string, brick *string) (*data.BrickMetadata, *string, error) { // Check if simple metadata file exists brickMetadataFile := filepath.Join(*baseDir, fmt.Sprintf("%s.%s", *brick, brickExt)) log.Debug(" Search brick metadata file:\n %s", brickMetadataFile) if exists, _ := util.FileExists(brickMetadataFile); !exists { // Check if complex module metadata file exists brickMetadataFile = filepath.Join(*baseDir, *brick, brickModuleName) log.Debug(" Search brick metadata file:\n %s", brickMetadataFile) if exists, _ := util.FileExists(brickMetadataFile); !exists { return nil, nil, errors.New(fmt.Sprintf("Could not load brick module:\n %s\nfrom path:\n %s", *brick, brickMetadataFile)) } } log.Debug(" Loading brick metadata from file:\n %s", brickMetadataFile) brickBaseDir := filepath.Dir(brickMetadataFile) log.Debug(" Brick base dir:\n %s", brickMetadataFile) // Compile template if compiled, err := compileTemplateFromFile(context, &brickMetadataFile); err == nil { var metadata data.BrickMetadata if err := util.LoadYAMLFromString(*compiled, &metadata); err != nil { return nil, nil, err } return &metadata, &brickBaseDir, nil } else { return nil, nil, err } }
func createContext(envData *map[string]interface{}, provider *data.Provider, dataFile *string) (*pongo2.Context, error) { // Create context context := pongo2.Context{ "env": map[string]interface{}{ "provider": provider.Name, }, } // Default values log.Debug(" Applying default data to context") context["private_ipv4"] = "$private_ipv4" // Provided by CoreOS context["public_ipv4"] = "$public_ipv4" // Provided by CoreOS // Merge environment/provider values var data map[string]interface{} log.Debug(" Applying environment data to context") if err := mergo.Merge(&data, *envData); err != nil { return nil, err } log.Debug(" Applying provider data to context") if err := mergo.Merge(&data, provider.Data); err != nil { return nil, err } // Add/Override values from extra data if dataFile != nil { log.Debug(" Applying extra data to context") var extraData map[string]interface{} if err := util.LoadYAMLFromFile(*dataFile, &extraData); err != nil { log.Error("Error loading/parsing extra data from file:\n %s", *dataFile) return nil, err } if err := mergo.Merge(&data, extraData); err != nil { return nil, err } } // Set result into context for k, v := range data { context[k] = v } return &context, nil }
func validateAndRunBuildCommand(flagProjectFile string, flagDataFile string) error { // Validate command if len(strings.TrimSpace(flagProjectFile)) == 0 { return errors.New("Error: missing project file") } // Check if project file exists if _, err := os.Stat(flagProjectFile); os.IsNotExist(err) { return errors.New("Error: Project file doesn't exist") } // Workdir workDir, _ := os.Getwd() workDir, _ = filepath.Abs(workDir) log.Debug("Work dir: %s", workDir) // Get absolute paths var projectRootDir string if projectAbsolutePath, err := filepath.Abs(flagProjectFile); err == nil { projectRootDir = filepath.Dir(projectAbsolutePath) log.Debug("Project root:\n %s", projectRootDir) log.Debug("Project file:\n %s", projectAbsolutePath) var dataAbsolutePath *string if len(flagDataFile) > 0 { if dataPath, err := filepath.Abs(flagDataFile); err == nil { dataAbsolutePath = &dataPath log.Debug("Extra data file:\n %s", dataAbsolutePath) } else { return err } } log.Notice("▶ Building project from file:\n %s", projectAbsolutePath) compiler.ProjectCompiler{}.Compile(&projectAbsolutePath, dataAbsolutePath, &projectRootDir, &workDir) return nil } else { return err } }
func compileTemplateFromFile(context *pongo2.Context, file *string) (*string, error) { log.Debug(" Compiling template:\n %s", *file) source, err := util.LoadFileAsString(*file) if err != nil { log.Error("Error loading template source:\n %s", *file) return nil, err } log.Trace("-- %s --\n%s\n----", "source", *source) return compileTemplateFromString(context, source) }
func loadEnvironment(projectPath string) (*data.Environment, *string, error) { // YAML doesn't like the Pongo2 syntax, so we need to do the project file parsing in 2 steps log.Debug(" Loading project file as plain string") source, err := util.LoadFileAsString(projectPath) if err != nil { log.Debug("Error loading project from:\n %s", projectPath) return nil, nil, err } log.Trace("-- %s --\n%s\n----", "project source", *source) // Extract data section from project file log.Debug(" Extracting environment data") environmentSource := regexExtractEnvironment.FindString(*source) log.Trace("-- %s --\n%s\n----", "environment source", environmentSource) var tmpProject data.Project if err := util.LoadYAMLFromString(environmentSource, &tmpProject); err != nil { log.Debug("Error parsing environment data from file:\n %s", projectPath) return nil, nil, err } return &tmpProject.Environment, source, nil }
func printContextData(context *pongo2.Context, isDebug bool) { // Pretty print the available data as YAML if serializedData, err := yaml.Marshal(context); err == nil { source := util.IndentString(string(serializedData), " ") if isDebug { log.Debug(source) } else { log.Info(source) } } else { // Fallback for k, v := range *context { log.Info(" %s = %v", k, v) } } }
func (c ProjectCompiler) Compile(projectPath *string, dataFile *string, baseDir *string, workDir *string) { log.Notice("▶ Loading environment data from file:\n %s", *projectPath) environment, source, err := loadEnvironment(*projectPath) if err != nil { util.HandleFatalError(err) } for _, provider := range environment.Providers { log.Notice("▶ Switching provider to: %s", provider.Name) log.Debug(" Creating template context") if context, err := createContext(&environment.Data, &provider, dataFile); err == nil { log.Notice("▶ Context data:") printContextData(context, false) log.Notice("▶ Compiling artifacts") if compiled, err := compileTemplateFromString(context, source); err == nil { var project data.Project if err := util.LoadYAMLFromString(*compiled, &project); err == nil { // Compile each artifact for _, artifact := range project.Artifacts { //log.Info(" Compiling:\n artifact: %s\n provider: %s", artifact.Name, provider.Name) if err := compileArtifact(context, &artifact, provider.Name, baseDir, workDir); err != nil { util.HandleFatalError(err) } } } else { err := errors.New(fmt.Sprintf("Error parsing project artifacts\nerror:\n %s", err.Error())) util.HandleFatalError(err) } } else { util.HandleFatalError(err) } } else { util.HandleFatalError(err) } } log.Notice("▶ Completed successfully") }
func compileArtifact(context *pongo2.Context, artifact *data.Artifact, providerName string, baseDir *string, workDir *string) error { outputFile := filepath.Join(*workDir, "build", providerName, artifact.Name, "user-data") outputDir := filepath.Dir(outputFile) // Create output directory if missing if exists, _ := util.FileExists(outputDir); !exists { if err := os.MkdirAll(outputDir, 0755); err != nil { return errors.New(fmt.Sprintf("Could not create output directory:\n %s\n\nnested error:\n %s", outputDir, err.Error())) } } log.Notice("▶ Compiling artifact [%s] to:\n %s", artifact.Name, outputFile) log.Debug(" Setting artifact name to the context") c := pongo2.Context{ "env": map[string]interface{}{ "provider": providerName, "artifact": artifact.Name, }, } if err := mergo.MergeWithOverwrite(context, c); err != nil { return err } printContextData(context, true) // Create the initial user data objects buffer := bytes.NewBufferString("#cloud-config\n---\n") writeFiles := make([]data.WriteFile, 0, 10) units := make([]data.Unit, 0, 10) // Process bricks bricks := artifact.Bricks for _, brick := range bricks { log.Info(" Processing brick:\n %s", brick) // Load metadata if metadata, brickBaseDir, err := loadBrickMetadata(context, baseDir, &brick); err == nil { // Process brick files log.Debug(" Processing brick files") if brickWriteFiles, err := processMetadataFiles(context, metadata, brickBaseDir); err != nil { return err } else { writeFiles = append(writeFiles, brickWriteFiles...) } // Process brick units log.Debug(" Processing brick unit") if brickUnit, err := processMetadataUnits(context, metadata, brickBaseDir); err != nil { return err } else if brickUnit != nil { units = append(units, *brickUnit) } } else { return err } } // Write compiled write-files section if len(writeFiles) > 0 { writeFilesMap := make(map[string]interface{}) writeFilesMap["write-files"] = writeFiles if output, err := yaml.Marshal(writeFilesMap); err != nil { return err } else { buffer.Write(output) } } // Write rest of config file cloudConfig := artifact.CloudConfig // Custom CoreOS if cloudConfig.CoreOS != nil { coreOs := cloudConfig.CoreOS // Add units // if coreOs["units"] != nil { coreOs["units"] = units // } // Write CoreOS section coreOsMap := make(map[string]interface{}) coreOsMap["coreos"] = coreOs if output, err := yaml.Marshal(coreOsMap); err != nil { return err } else { buffer.Write(output) } } // Append everything else to the end of the file if cloudConfig.Generic != nil { if output, err := yaml.Marshal(cloudConfig.Generic); err != nil { return err } else { buffer.Write(output) } } log.Info(" Writing output file to:\n %s", outputFile) if err := ioutil.WriteFile(outputFile, buffer.Bytes(), 0755); err != nil { return err } return nil }