diff --git a/docs/cli-commands/lula_dev.md b/docs/cli-commands/lula_dev.md index d3051357..14b25e17 100644 --- a/docs/cli-commands/lula_dev.md +++ b/docs/cli-commands/lula_dev.md @@ -10,7 +10,8 @@ Collection of dev commands to make dev life easier ### Options ``` - -h, --help help for dev + -h, --help help for dev + -s, --set strings set a value in the template data ``` ### Options inherited from parent commands diff --git a/docs/cli-commands/lula_dev_get-resources.md b/docs/cli-commands/lula_dev_get-resources.md index 95102743..87ef7d9a 100644 --- a/docs/cli-commands/lula_dev_get-resources.md +++ b/docs/cli-commands/lula_dev_get-resources.md @@ -48,6 +48,7 @@ To hang for timeout of 5 seconds: ``` -l, --log-level string Log level when running Lula. Valid options are: warn, info, debug, trace (default "info") + -s, --set strings set a value in the template data ``` ### SEE ALSO diff --git a/docs/cli-commands/lula_dev_lint.md b/docs/cli-commands/lula_dev_lint.md index 8ba7082a..aeba3f78 100644 --- a/docs/cli-commands/lula_dev_lint.md +++ b/docs/cli-commands/lula_dev_lint.md @@ -36,6 +36,7 @@ To lint existing validation files: ``` -l, --log-level string Log level when running Lula. Valid options are: warn, info, debug, trace (default "info") + -s, --set strings set a value in the template data ``` ### SEE ALSO diff --git a/docs/cli-commands/lula_dev_validate.md b/docs/cli-commands/lula_dev_validate.md index 91129961..9e431e07 100644 --- a/docs/cli-commands/lula_dev_validate.md +++ b/docs/cli-commands/lula_dev_validate.md @@ -50,6 +50,7 @@ To hang for timeout of 5 seconds: ``` -l, --log-level string Log level when running Lula. Valid options are: warn, info, debug, trace (default "info") + -s, --set strings set a value in the template data ``` ### SEE ALSO diff --git a/src/cmd/dev/common.go b/src/cmd/dev/common.go index e8eaaa4f..1f5cef98 100644 --- a/src/cmd/dev/common.go +++ b/src/cmd/dev/common.go @@ -7,7 +7,9 @@ import ( "strings" "time" + cmdCommon "github.com/defenseunicorns/lula/src/cmd/common" "github.com/defenseunicorns/lula/src/config" + "github.com/defenseunicorns/lula/src/internal/template" "github.com/defenseunicorns/lula/src/pkg/common" "github.com/defenseunicorns/lula/src/pkg/message" "github.com/defenseunicorns/lula/src/types" @@ -19,17 +21,32 @@ const STDIN = "0" const NO_TIMEOUT = -1 const DEFAULT_TIMEOUT = 1 -var devCmd = &cobra.Command{ - Use: "dev", - Aliases: []string{"d"}, - Short: "Collection of dev commands to make dev life easier", - PersistentPreRun: func(cmd *cobra.Command, args []string) { - config.SkipLogFile = true - // Call the parent's (root) PersistentPreRun - if parentPreRun := cmd.Parent().PersistentPreRun; parentPreRun != nil { - parentPreRun(cmd.Parent(), args) - } - }, +func DevCommand() *cobra.Command { + + var ( + setOpts []string + ) + + cmd := &cobra.Command{ + Use: "dev", + Aliases: []string{"d"}, + Short: "Collection of dev commands to make dev life easier", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + config.SkipLogFile = true + // Call the parent's (root) PersistentPreRun + if parentPreRun := cmd.Parent().PersistentPreRun; parentPreRun != nil { + parentPreRun(cmd.Parent(), args) + } + }, + } + + cmd.PersistentFlags().StringSliceVarP(&setOpts, "set", "s", []string{}, "set a value in the template data") + + cmd.AddCommand(lintCmd) + cmd.AddCommand(validateCmd) + cmd.AddCommand(getResourcesCmd) + + return cmd } type flags struct { @@ -41,11 +58,6 @@ type flags struct { var RunInteractively bool = true // default to run dev command interactively -// Include adds the tools command to the root command. -func Include(rootCmd *cobra.Command) { - rootCmd.AddCommand(devCmd) -} - // ReadValidation reads the validation yaml file and returns the validation bytes func ReadValidation(cmd *cobra.Command, spinner *message.Spinner, path string, timeout int) ([]byte, error) { var validationBytes []byte @@ -104,3 +116,27 @@ func RunSingleValidation(ctx context.Context, validationBytes []byte, opts ...ty return lulaValidation, nil } + +// Provides basic templating wrapper for "all" render type +func DevTemplate(validationBytes []byte, setOpts []string) ([]byte, error) { + // Get overrides from --set flag + overrides, err := cmdCommon.ParseTemplateOverrides(setOpts) + if err != nil { + return nil, fmt.Errorf("error parsing template overrides: %v", err) + } + + // Handles merging viper config file data + environment variables + // Throws an error if config keys are invalid for templating + templateData, err := template.CollectTemplatingData(cmdCommon.TemplateConstants, cmdCommon.TemplateVariables, overrides) + if err != nil { + return nil, fmt.Errorf("error collecting templating data: %v", err) + } + + templateRenderer := template.NewTemplateRenderer(templateData) + output, err := templateRenderer.Render(string(validationBytes), "all") + if err != nil { + return nil, fmt.Errorf("error rendering template: %v", err) + } + + return output, nil +} diff --git a/src/cmd/dev/get-resources.go b/src/cmd/dev/get-resources.go index 52d0b61d..2936976a 100644 --- a/src/cmd/dev/get-resources.go +++ b/src/cmd/dev/get-resources.go @@ -33,29 +33,45 @@ var getResourcesCmd = &cobra.Command{ Short: "Get Resources from a Lula Validation Manifest", Long: "Get the JSON resources specified in a Lula Validation Manifest", Example: getResourcesHelp, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { + spinnerMessage := fmt.Sprintf("Getting Resources from %s", getResourcesOpts.InputFile) spinner := message.NewProgressSpinner("%s", spinnerMessage) defer spinner.Stop() ctx := context.Background() - var validationBytes []byte - var err error // Read the validation data from STDIN or provided file - validationBytes, err = ReadValidation(cmd, spinner, getResourcesOpts.InputFile, getResourcesOpts.Timeout) + validationBytes, err := ReadValidation(cmd, spinner, getResourcesOpts.InputFile, getResourcesOpts.Timeout) if err != nil { - message.Fatalf(err, "error reading validation: %v", err) + return fmt.Errorf("error reading validation: %v", err) } - collection, err := DevGetResources(ctx, validationBytes, spinner) + config, _ := cmd.Flags().GetStringSlice("set") + message.Debug("command line 'set' flags: %s", config) + + output, err := DevTemplate(validationBytes, config) if err != nil { - message.Fatalf(err, "error running dev get-resources: %v", err) + return fmt.Errorf("error templating validation: %v", err) } - writeResources(collection, getResourcesOpts.OutputFile) + // add to debug logs accepting that this will print sensitive information? + message.Debug(string(output)) + + collection, err := DevGetResources(ctx, output, spinner) + + // do not perform the write if there is nothing to write (likely error) + if collection != nil { + writeResources(collection, getResourcesOpts.OutputFile) + } + + if err != nil { + return fmt.Errorf("error running dev get-resources: %v", err) + } spinner.Success() + + return nil }, } @@ -63,8 +79,6 @@ func init() { common.InitViper() - devCmd.AddCommand(getResourcesCmd) - getResourcesCmd.Flags().StringVarP(&getResourcesOpts.InputFile, "input-file", "f", STDIN, "the path to a validation manifest file") getResourcesCmd.Flags().StringVarP(&getResourcesOpts.OutputFile, "output-file", "o", "", "the path to write the resources json") getResourcesCmd.Flags().IntVarP(&getResourcesOpts.Timeout, "timeout", "t", DEFAULT_TIMEOUT, "the timeout for stdin (in seconds, -1 for no timeout)") @@ -80,6 +94,9 @@ func DevGetResources(ctx context.Context, validationBytes []byte, spinner *messa types.GetResourcesOnly(true), ) if err != nil { + if lulaValidation.DomainResources != nil { + return *lulaValidation.DomainResources, err + } return nil, err } diff --git a/src/cmd/dev/lint.go b/src/cmd/dev/lint.go index 63bf336e..cac26062 100644 --- a/src/cmd/dev/lint.go +++ b/src/cmd/dev/lint.go @@ -34,7 +34,10 @@ var lintCmd = &cobra.Command{ message.Fatalf(nil, "No input files specified") } - validationResults := DevLintCommand(lintOpts.InputFiles) + config, _ := cmd.Flags().GetStringSlice("set") + message.Debug("command line 'set' flags: %s", config) + + validationResults := DevLintCommand(lintOpts.InputFiles, config) // If result file is specified, write the validation results to the file var err error @@ -64,7 +67,7 @@ var lintCmd = &cobra.Command{ }, } -func DevLintCommand(inputFiles []string) []oscalValidation.ValidationResult { +func DevLintCommand(inputFiles []string, setOpts []string) []oscalValidation.ValidationResult { var validationResults []oscalValidation.ValidationResult for _, inputFile := range inputFiles { @@ -88,7 +91,16 @@ func DevLintCommand(inputFiles []string) []oscalValidation.ValidationResult { break } - validations, err := pkgCommon.ReadValidationsFromYaml(validationBytes) + output, err := DevTemplate(validationBytes, setOpts) + if err != nil { + handleFail(err) + break + } + + // add to debug logs accepting that this will print sensitive information? + message.Debug(string(output)) + + validations, err := pkgCommon.ReadValidationsFromYaml(output) if err != nil { handleFail(err) break @@ -122,8 +134,6 @@ func init() { common.InitViper() - devCmd.AddCommand(lintCmd) - lintCmd.Flags().StringSliceVarP(&lintOpts.InputFiles, "input-files", "f", []string{}, "the paths to validation files (comma-separated)") lintCmd.Flags().StringVarP(&lintOpts.ResultFile, "result-file", "r", "", "the path to write the validation result") } diff --git a/src/cmd/dev/validate.go b/src/cmd/dev/validate.go index 051c9a28..fc08c4c2 100644 --- a/src/cmd/dev/validate.go +++ b/src/cmd/dev/validate.go @@ -75,7 +75,18 @@ var validateCmd = &cobra.Command{ } } - validation, err := DevValidate(ctx, validationBytes, resourcesBytes, spinner) + config, _ := cmd.Flags().GetStringSlice("set") + message.Debug("command line 'set' flags: %s", config) + + output, err := DevTemplate(validationBytes, config) + if err != nil { + message.Fatalf(err, "error templating validation: %v", err) + } + + // add to debug logs accepting that this will print sensitive information? + message.Debug(string(output)) + + validation, err := DevValidate(ctx, output, resourcesBytes, spinner) if err != nil { message.Fatalf(err, "error running dev validate: %v", err) } @@ -109,8 +120,6 @@ func init() { common.InitViper() - devCmd.AddCommand(validateCmd) - validateCmd.Flags().StringVarP(&validateOpts.InputFile, "input-file", "f", STDIN, "the path to a validation manifest file") validateCmd.Flags().StringVarP(&validateOpts.ResourcesFile, "resources-file", "r", "", "the path to an optional resources file") validateCmd.Flags().StringVarP(&validateOpts.OutputFile, "output-file", "o", "", "the path to write the validation with results") diff --git a/src/cmd/root.go b/src/cmd/root.go index e73d1dd5..785536cb 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -64,12 +64,12 @@ func init() { evaluate.EvaluateCommand(), generate.GenerateCommand(), console.ConsoleCommand(), + dev.DevCommand(), } rootCmd.AddCommand(commands...) tools.Include(rootCmd) version.Include(rootCmd) - dev.Include(rootCmd) rootCmd.PersistentFlags().StringVarP(&LogLevelCLI, "log-level", "l", v.GetString(common.VLogLevel), "Log level when running Lula. Valid options are: warn, info, debug, trace") } diff --git a/src/test/e2e/dev_lint_test.go b/src/test/e2e/dev_lint_test.go index 19646c16..251c7a47 100644 --- a/src/test/e2e/dev_lint_test.go +++ b/src/test/e2e/dev_lint_test.go @@ -48,7 +48,7 @@ func TestLintCommand(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - validationResults := dev.DevLintCommand(tc.inputFiles) + validationResults := dev.DevLintCommand(tc.inputFiles, []string{}) for i, result := range validationResults { if result.Valid != tc.valid[i] { t.Errorf("Expected valid to be %v, but got %v", tc.valid[i], result.Valid)