201 lines
6.6 KiB
Go
201 lines
6.6 KiB
Go
//go:build client || all
|
|
// +build client all
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/OpenCHAMI/configurator/pkg/config"
|
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
tokenFetchRetries int
|
|
templatePaths []string
|
|
pluginPath string
|
|
useCompression bool
|
|
)
|
|
|
|
var generateCmd = &cobra.Command{
|
|
Use: "generate",
|
|
Short: "Generate a config file from state management",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
// make sure that we have a token present before trying to make request
|
|
if conf.AccessToken == "" {
|
|
// check if OCHAMI_ACCESS_TOKEN env var is set if no access token is provided and use that instead
|
|
accessToken := os.Getenv("ACCESS_TOKEN")
|
|
if accessToken != "" {
|
|
conf.AccessToken = accessToken
|
|
} else {
|
|
// TODO: try and fetch token first if it is needed
|
|
if verbose {
|
|
fmt.Printf("No token found. Attempting to generate conf without one...\n")
|
|
}
|
|
}
|
|
}
|
|
|
|
// use cert path from cobra if empty
|
|
if conf.CertPath == "" {
|
|
conf.CertPath = cacertPath
|
|
}
|
|
|
|
// show conf as JSON and generators if verbose
|
|
if verbose {
|
|
b, err := json.MarshalIndent(conf, "", " ")
|
|
if err != nil {
|
|
fmt.Printf("failed to marshal conf: %v\n", err)
|
|
}
|
|
fmt.Printf("%v\n", string(b))
|
|
}
|
|
|
|
// run all of the target recursively until completion if provided
|
|
if len(targets) > 0 {
|
|
RunTargets(&conf, args, targets...)
|
|
} else {
|
|
if pluginPath == "" {
|
|
fmt.Printf("no plugin path specified")
|
|
return
|
|
}
|
|
|
|
// load the templates to use
|
|
templates := map[string]generator.Template{}
|
|
for _, path := range templatePaths {
|
|
template := generator.Template{}
|
|
template.LoadFromFile(path)
|
|
if !template.IsEmpty() {
|
|
templates[path] = template
|
|
}
|
|
}
|
|
|
|
params := generator.Params{
|
|
Templates: templates,
|
|
}
|
|
|
|
// run generator.Generate() with just plugin path and templates provided
|
|
outputBytes, err := generator.Generate(pluginPath, params)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to generate files")
|
|
}
|
|
|
|
// if we have more than one target and output is set, create configs in directory
|
|
outputMap := generator.ConvertContentsToString(outputBytes)
|
|
writeOutput(outputBytes, len(targets), len(outputMap))
|
|
}
|
|
},
|
|
}
|
|
|
|
// Generate files by supplying a list of targets as string values. Currently,
|
|
// targets are defined statically in a conf file. Targets are ran recursively
|
|
// if more targets are nested in a defined target, but will not run additional
|
|
// child targets if it is the same as the parent.
|
|
//
|
|
// NOTE: This may be changed in the future how this is done.
|
|
func RunTargets(conf *config.Config, args []string, targets ...string) {
|
|
// generate conf with each supplied target
|
|
for _, target := range targets {
|
|
outputBytes, err := generator.GenerateWithTarget(conf, target)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to generate conf")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// if we have more than one target and output is set, create configs in directory
|
|
outputMap := generator.ConvertContentsToString(outputBytes)
|
|
writeOutput(outputBytes, len(targets), len(outputMap))
|
|
|
|
// remove any targets that are the same as current to prevent infinite loop
|
|
nextTargets := util.CopyIf(conf.Targets[target].RunTargets, func(nextTarget string) bool {
|
|
return nextTarget != target
|
|
})
|
|
|
|
// ...then, run any other targets that the current target has
|
|
RunTargets(conf, args, nextTargets...)
|
|
}
|
|
}
|
|
|
|
func writeOutput(outputBytes generator.FileMap, targetCount int, templateCount int) {
|
|
outputMap := generator.ConvertContentsToString(outputBytes)
|
|
if outputPath == "" {
|
|
// write only to stdout by default
|
|
if len(outputMap) == 1 {
|
|
for _, contents := range outputMap {
|
|
fmt.Printf("%s\n", string(contents))
|
|
}
|
|
} else {
|
|
for path, contents := range outputMap {
|
|
fmt.Printf("-- file: %s, size: %d B\n%s\n", path, len(contents), string(contents))
|
|
}
|
|
}
|
|
} else if outputPath != "" && targetCount == 1 && templateCount == 1 {
|
|
// write just a single file using provided name
|
|
for _, contents := range outputBytes {
|
|
err := os.WriteFile(outputPath, contents, 0o644)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to write conf to file")
|
|
os.Exit(1)
|
|
}
|
|
log.Info().Msgf("wrote file to '%s'\n", outputPath)
|
|
}
|
|
} else if outputPath != "" && targetCount > 1 && useCompression {
|
|
// write multiple files to archive, compress, then save to output path
|
|
out, err := os.Create(fmt.Sprintf("%s.tar.gz", outputPath))
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to write archive")
|
|
os.Exit(1)
|
|
}
|
|
files := make([]string, len(outputBytes))
|
|
i := 0
|
|
for path := range outputBytes {
|
|
files[i] = path
|
|
i++
|
|
}
|
|
err = util.CreateArchive(files, out)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to create archive")
|
|
os.Exit(1)
|
|
}
|
|
|
|
} else if outputPath != "" && targetCount > 1 || templateCount > 1 {
|
|
// write multiple files in directory using template name
|
|
err := os.MkdirAll(filepath.Clean(outputPath), 0o755)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to make output directory")
|
|
os.Exit(1)
|
|
}
|
|
for path, contents := range outputBytes {
|
|
filename := filepath.Base(path)
|
|
cleanPath := fmt.Sprintf("%s/%s", filepath.Clean(outputPath), filename)
|
|
err := os.WriteFile(cleanPath, contents, 0o755)
|
|
if err != nil {
|
|
log.Error().Err(err).Msg("failed to write conf to file")
|
|
os.Exit(1)
|
|
}
|
|
log.Info().Msgf("wrote file to '%s'\n", cleanPath)
|
|
}
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
generateCmd.Flags().StringSliceVar(&targets, "target", []string{}, "set the targets to run pre-defined conf")
|
|
generateCmd.Flags().StringSliceVar(&templatePaths, "template", []string{}, "set the paths for the Jinja 2 templates to use")
|
|
generateCmd.Flags().StringVar(&pluginPath, "plugin", "", "set the generator plugin path")
|
|
generateCmd.Flags().StringVarP(&outputPath, "output", "o", "", "set the output path for conf targets")
|
|
generateCmd.Flags().IntVar(&tokenFetchRetries, "fetch-retries", 5, "set the number of retries to fetch an access token")
|
|
generateCmd.Flags().StringVar(&remoteHost, "host", "http://localhost", "set the remote host")
|
|
generateCmd.Flags().BoolVar(&useCompression, "compress", false, "set whether to archive and compress multiple file outputs")
|
|
|
|
// requires either 'target' by itself or 'plugin' and 'templates' together
|
|
// generateCmd.MarkFlagsOneRequired("target", "plugin")
|
|
generateCmd.MarkFlagsMutuallyExclusive("target", "plugin")
|
|
generateCmd.MarkFlagsMutuallyExclusive("target", "template")
|
|
generateCmd.MarkFlagsRequiredTogether("plugin", "template")
|
|
|
|
rootCmd.AddCommand(generateCmd)
|
|
}
|