예제 #1
0
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
	}
}
예제 #2
0
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
}
예제 #3
0
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
	}
}
예제 #4
0
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)
}
예제 #5
0
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
}
예제 #6
0
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)
		}
	}
}
예제 #7
0
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")
}
예제 #8
0
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
}