Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dev): allow templating validations in dev workflows #777

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/cli-commands/lula_dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/cli-commands/lula_dev_get-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/cli-commands/lula_dev_lint.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/cli-commands/lula_dev_validate.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 52 additions & 16 deletions src/cmd/dev/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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
}
37 changes: 27 additions & 10 deletions src/cmd/dev/get-resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,38 +33,52 @@ 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
},
}

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)")
Expand All @@ -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
}

Expand Down
20 changes: 15 additions & 5 deletions src/cmd/dev/lint.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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")
}
15 changes: 12 additions & 3 deletions src/cmd/dev/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
2 changes: 1 addition & 1 deletion src/test/e2e/dev_lint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down