-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(template): template command with initial docs (#644)
* feat(template): template command with initial docs * feat(template): support for merging config file data with environment variables * chore(docs): generate cli docs after updates to main * feat(template): template unit and e2e tests * fix(wip): working on a CLI testing example * fix(template): basic cli tests for template command * fix(template): fix demo test template * fix(template): revert default lula-config * fix(template): resolving comments * fix(template): override with env var test addition * fix(template): update test files after modification * fix(template): revert current template for downstream impact * fix(template): revert env override * fix(template): revert golden files
- Loading branch information
1 parent
fbe7b8f
commit 89be460
Showing
12 changed files
with
505 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
--- | ||
title: lula tools template | ||
description: Lula CLI command reference for <code>lula tools template</code>. | ||
type: docs | ||
--- | ||
## lula tools template | ||
|
||
Template an artifact | ||
|
||
### Synopsis | ||
|
||
Resolving templated artifacts with configuration data | ||
|
||
``` | ||
lula tools template [flags] | ||
``` | ||
|
||
### Examples | ||
|
||
``` | ||
To template an OSCAL Model: | ||
lula tools template -f ./oscal-component.yaml | ||
To indicate a specific output file: | ||
lula tools template -f ./oscal-component.yaml -o templated-oscal-component.yaml | ||
Data for the templating should be stored under the 'variables' configuration item in a lula-config.yaml file | ||
``` | ||
|
||
### Options | ||
|
||
``` | ||
-h, --help help for template | ||
-f, --input-file string the path to the target artifact | ||
-o, --output-file string the path to the output file. If not specified, the output file will be directed to stdout | ||
``` | ||
|
||
### Options inherited from parent commands | ||
|
||
``` | ||
-l, --log-level string Log level when running Lula. Valid options are: warn, info, debug, trace (default "info") | ||
``` | ||
|
||
### SEE ALSO | ||
|
||
* [lula tools](./lula_tools.md) - Collection of additional commands to make OSCAL easier | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package tools | ||
|
||
import ( | ||
"os" | ||
|
||
"github.com/defenseunicorns/go-oscal/src/pkg/files" | ||
"github.com/defenseunicorns/lula/src/cmd/common" | ||
"github.com/defenseunicorns/lula/src/internal/template" | ||
pkgCommon "github.com/defenseunicorns/lula/src/pkg/common" | ||
"github.com/defenseunicorns/lula/src/pkg/message" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
type templateFlags struct { | ||
InputFile string // -f --input-file | ||
OutputFile string // -o --output-file | ||
} | ||
|
||
var templateOpts = &templateFlags{} | ||
|
||
var templateHelp = ` | ||
To template an OSCAL Model: | ||
lula tools template -f ./oscal-component.yaml | ||
To indicate a specific output file: | ||
lula tools template -f ./oscal-component.yaml -o templated-oscal-component.yaml | ||
Data for the templating should be stored under the 'variables' configuration item in a lula-config.yaml file | ||
` | ||
var templateCmd = &cobra.Command{ | ||
Use: "template", | ||
Short: "Template an artifact", | ||
Long: "Resolving templated artifacts with configuration data", | ||
Args: cobra.NoArgs, | ||
Example: templateHelp, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
// Read file | ||
data, err := pkgCommon.ReadFileToBytes(templateOpts.InputFile) | ||
if err != nil { | ||
message.Fatal(err, err.Error()) | ||
} | ||
|
||
// Get current viper pointer | ||
v := common.GetViper() | ||
// Get all viper settings | ||
// This will only return config file items and resolved environment variables | ||
// that have an associated key in the config file | ||
viperData := v.AllSettings() | ||
|
||
// Handles merging viper config file data + environment variables | ||
mergedMap := template.CollectTemplatingData(viperData) | ||
|
||
templatedData, err := template.ExecuteTemplate(mergedMap, string(data)) | ||
if err != nil { | ||
message.Fatalf(err, "error templating validation: %v", err) | ||
} | ||
|
||
if templateOpts.OutputFile == "" { | ||
_, err := os.Stdout.Write(templatedData) | ||
if err != nil { | ||
message.Fatalf(err, "failed to write to stdout: %v", err) | ||
} | ||
} else { | ||
err = files.CreateFileDirs(templateOpts.OutputFile) | ||
if err != nil { | ||
message.Fatalf(err, "failed to create output file path: %s\n", err) | ||
} | ||
err = os.WriteFile(templateOpts.OutputFile, templatedData, 0644) | ||
if err != nil { | ||
message.Fatal(err, err.Error()) | ||
} | ||
} | ||
|
||
}, | ||
} | ||
|
||
func TemplateCommand() *cobra.Command { | ||
return templateCmd | ||
} | ||
|
||
func init() { | ||
common.InitViper() | ||
|
||
toolsCmd.AddCommand(templateCmd) | ||
|
||
templateCmd.Flags().StringVarP(&templateOpts.InputFile, "input-file", "f", "", "the path to the target artifact") | ||
templateCmd.MarkFlagRequired("input-file") | ||
templateCmd.Flags().StringVarP(&templateOpts.OutputFile, "output-file", "o", "", "the path to the output file. If not specified, the output file will be directed to stdout") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
package template | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
"text/template" | ||
|
||
"github.com/defenseunicorns/pkg/helpers" | ||
) | ||
|
||
const PREFIX = "LULA_" | ||
|
||
// ExecuteTemplate templates the template string with the data map | ||
func ExecuteTemplate(data map[string]interface{}, templateString string) ([]byte, error) { | ||
tmpl, err := template.New("template").Parse(templateString) | ||
if err != nil { | ||
return []byte{}, err | ||
} | ||
tmpl.Option("missingkey=default") | ||
|
||
var buffer strings.Builder | ||
err = tmpl.Execute(&buffer, data) | ||
if err != nil { | ||
return []byte{}, err | ||
} | ||
|
||
return []byte(buffer.String()), nil | ||
} | ||
|
||
// Prepare the map of data for use in templating | ||
|
||
func CollectTemplatingData(data map[string]interface{}) map[string]interface{} { | ||
|
||
// Get all environment variables with a specific prefix | ||
envMap := GetEnvVars(PREFIX) | ||
|
||
// Merge the data into a single map for use with templating | ||
mergedMap := helpers.MergeMapRecursive(envMap, data) | ||
|
||
return mergedMap | ||
|
||
} | ||
|
||
// get all environment variables with the established prefix | ||
func GetEnvVars(prefix string) map[string]interface{} { | ||
envMap := make(map[string]interface{}) | ||
|
||
// Get all environment variables | ||
envVars := os.Environ() | ||
|
||
// Iterate over environment variables | ||
for _, envVar := range envVars { | ||
// Split the environment variable into key and value | ||
pair := strings.SplitN(envVar, "=", 2) | ||
if len(pair) != 2 { | ||
continue | ||
} | ||
|
||
key := pair[0] | ||
value := pair[1] | ||
|
||
// Check if the key starts with the specified prefix | ||
if strings.HasPrefix(key, prefix) { | ||
// Remove the prefix from the key and convert to lowercase | ||
strippedKey := strings.TrimPrefix(key, prefix) | ||
envMap[strings.ToLower(strippedKey)] = value | ||
} | ||
} | ||
|
||
return envMap | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package template_test | ||
|
||
import ( | ||
"os" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/defenseunicorns/lula/src/internal/template" | ||
) | ||
|
||
func TestExecuteTemplate(t *testing.T) { | ||
|
||
test := func(t *testing.T, data map[string]interface{}, preTemplate string, expected string) { | ||
t.Helper() | ||
// templateData returned | ||
got, err := template.ExecuteTemplate(data, preTemplate) | ||
if err != nil { | ||
t.Fatalf("error templating data: %s\n", err.Error()) | ||
} | ||
|
||
if string(got) != expected { | ||
t.Fatalf("Expected %s - Got %s\n", expected, string(got)) | ||
} | ||
} | ||
|
||
t.Run("Test {{ .testVar }} with data", func(t *testing.T) { | ||
data := map[string]interface{}{ | ||
"testVar": "testing", | ||
} | ||
|
||
test(t, data, "{{ .testVar }}", "testing") | ||
}) | ||
|
||
t.Run("Test {{ .testVar }} but empty data", func(t *testing.T) { | ||
data := map[string]interface{}{} | ||
|
||
test(t, data, "{{ .testVar }}", "<no value>") | ||
}) | ||
|
||
} | ||
|
||
func TestGetEnvVars(t *testing.T) { | ||
|
||
test := func(t *testing.T, prefix string, key string, value string) { | ||
t.Helper() | ||
|
||
os.Setenv(key, value) | ||
envMap := template.GetEnvVars(prefix) | ||
|
||
// convert key to expected format | ||
strippedKey := strings.TrimPrefix(key, prefix) | ||
|
||
if envMap[strings.ToLower(strippedKey)] != value { | ||
t.Fatalf("Expected %s - Got %s\n", value, envMap[strings.ToLower(strippedKey)]) | ||
} | ||
os.Unsetenv(key) | ||
} | ||
|
||
t.Run("Test LULA_RESOURCE - Pass", func(t *testing.T) { | ||
test(t, "LULA_", "LULA_RESOURCE", "pods") | ||
}) | ||
|
||
t.Run("Test OTHER_RESOURCE - Pass", func(t *testing.T) { | ||
test(t, "OTHER_", "OTHER_RESOURCE", "deployments") | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
resources: | ||
jsoncm: configmaps | ||
yamlcm: configmaps | ||
secret: secrets | ||
pod: pods | ||
|
||
type: software | ||
title: lula |
Oops, something went wrong.