Skip to content

Commit

Permalink
Add support for custom Zarf variables in k8s resources (#474)
Browse files Browse the repository at this point in the history
Co-authored-by: Jon Perry <[email protected]>
  • Loading branch information
jeff-mccoy and YrrepNoj authored May 5, 2022
1 parent e90fa27 commit 2c08a35
Show file tree
Hide file tree
Showing 11 changed files with 125 additions and 18 deletions.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ package-example-game: ## Create the Doom example
package-example-component-scripts: ## Create component script example
cd examples/component-scripts && ../../$(ZARF_BIN) package create --confirm && mv zarf-package-* ../../build/

.PHONY: package-example-component-variables
package-example-component-variables: ## Create component script example
cd examples/component-variables && ../../$(ZARF_BIN) package create --confirm && mv zarf-package-* ../../build/

.PHONY: package-example-data-injection
package-example-data-injection: ## create the Zarf package for the data injection example
cd examples/data-injection && ../../$(ZARF_BIN) package create --confirm && mv zarf-package-* ../../build/
Expand Down Expand Up @@ -104,6 +108,9 @@ test-e2e: ## Run e2e tests. Will automatically build any required dependencies t
@if [ ! -f zarf-package-component-scripts-$(ARCH).tar.zst ]; then\
$(MAKE) package-example-component-scripts;\
fi
@if [ ! -f zarf-package-component-variables-$(ARCH).tar.zst ]; then\
$(MAKE) package-example-component-variables;\
fi
@if [ ! -f ./build/zarf-package-data-injection-demo-$(ARCH).tar ]; then\
$(MAKE) package-example-data-injection;\
fi
Expand Down
19 changes: 19 additions & 0 deletions examples/component-variables/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Component Variables

This example demonstrates how to define variables in your component that will be templated across the manifests and charts that your component uses.

With the templating feature, you can define values in the zarf.yaml file without having to define them in every manifest and chart.
This becomes really useful when you are working with an upstream chart that is often changing, or a lot of the charts you use have slightly different conventions for their values. Now you can standardize all that from your zarf.yaml file.

&nbsp;

## How to Use Component Variables
The 'placeholder' text in the yaml should have you desired key name in all caps with `###ZARF_` prepended and `###` appened at the end.

For example, if I wanted to create a template for a database username I would do something like `###ZARF_DATABASE_USERNAME###` in the yaml.

In the zarf.yaml you would add the key-value pair in the variables section. For that same example as above, I would have (note that we don't include the `###ZARF_` and `###` and the beginning and end):
```yaml
variables:
database_username: "iamdata"
```
12 changes: 12 additions & 0 deletions examples/component-variables/simple-configmap.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: simple-configmap
namespace: zarf
data:
templateme.properties: |
horse=neigh
dog=###ZARF_DOG###
cat=###ZARF_CAT###
cow=moo
zebra=###ZARF_ZEBRA###
16 changes: 16 additions & 0 deletions examples/component-variables/zarf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
kind: ZarfPackageConfig
metadata:
name: component-variables
description: "Test component to demonstrate script timeout feature"

components:
# Demonstrates injecting custom variables into a K8s resource, e.g. ###ZARF_DOG###
- name: variable-example
required: true
variables:
dog: "woof"
cat: "meow"
manifests:
- name: variable-example-configmap
files:
- simple-configmap.yaml
2 changes: 1 addition & 1 deletion src/internal/helm/post-render.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) {
}

// Run the template engine against the chart output
k8s.ProcessYamlFilesInPath(tempDir, r.options.Component.Images)
k8s.ProcessYamlFilesInPath(tempDir, r.options.Component)

// Read back the templated file contents
buff, err := os.ReadFile(path)
Expand Down
9 changes: 5 additions & 4 deletions src/internal/k8s/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/defenseunicorns/zarf/src/internal/message"
"github.com/defenseunicorns/zarf/src/internal/template"
"github.com/defenseunicorns/zarf/src/internal/utils"
"github.com/defenseunicorns/zarf/src/types"
"github.com/go-logr/logr/funcr"
kubeyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -51,8 +52,8 @@ func GetContext() (string, error) {
}

// ProcessYamlFilesInPath iterates over all yaml files in a given path and performs Zarf templating + image swapping
func ProcessYamlFilesInPath(path string, componentImages []string) []string {
message.Debugf("k8s.ProcessYamlFilesInPath(%s, %v)", path, componentImages)
func ProcessYamlFilesInPath(path string, component types.ZarfComponent) []string {
message.Debugf("k8s.ProcessYamlFilesInPath(%s, %v)", path, component)

// Only pull in yml and yaml files
pattern := regexp.MustCompile(`(?mi)\.ya?ml$`)
Expand All @@ -61,7 +62,7 @@ func ProcessYamlFilesInPath(path string, componentImages []string) []string {

// Match images in the given list and replace if found in the given files
var imageSwap []ImageSwap
for _, image := range componentImages {
for _, image := range component.Images {
imageSwap = append(imageSwap, ImageSwap{
find: image,
replace: utils.SwapHost(image, valueTemplate.GetRegistry()),
Expand All @@ -74,7 +75,7 @@ func ProcessYamlFilesInPath(path string, componentImages []string) []string {
for _, swap := range imageSwap {
utils.ReplaceText(manifest, swap.find, swap.replace)
}
valueTemplate.Apply(manifest)
valueTemplate.Apply(component.Variables, manifest)
}

return manifests
Expand Down
2 changes: 1 addition & 1 deletion src/internal/packager/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ func deployComponents(tempPath tempPaths, component types.ZarfComponent) {
// zarf magic for the value file
for idx := range chart.ValuesFiles {
chartValueName := helm.StandardName(componentPath.values, chart) + "-" + strconv.Itoa(idx)
valueTemplate.Apply(chartValueName)
valueTemplate.Apply(component.Variables, chartValueName)
}

// Generate helm templates to pass to gitops engine
Expand Down
15 changes: 11 additions & 4 deletions src/internal/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package template

import (
"fmt"
"strings"

"github.com/defenseunicorns/zarf/src/types"

Expand Down Expand Up @@ -64,15 +65,15 @@ func (values Values) GetRegistry() string {
return values.registry
}

func (values Values) Apply(path string) {
message.Debugf("template.Apply(%s)", path)
func (values Values) Apply(variables types.ZarfComponentVariables, path string) {
message.Debugf("template.Apply(%v, %s)", variables, path)

if !values.Ready() {
// This should only occur if the state couldn't be pulled or on init if a template is attempted before the pre-seed stage
message.Fatalf(nil, "template.Apply() called before template.Generate()")
}

mappings := map[string]string{
mappings := types.ZarfComponentVariables{
"STORAGE_CLASS": values.state.StorageClass,
"SEED_REGISTRY": values.seedRegistry,
"REGISTRY": values.registry,
Expand All @@ -86,10 +87,16 @@ func (values Values) Apply(path string) {
"HTPASSWD": values.secret.htpasswd,
}

// Iterate over any custom variables and add them to the mappings for templating
for key, value := range variables {
mappings[key] = value
}

message.Debug(mappings)

for template, value := range mappings {
template = fmt.Sprintf("###ZARF_%s###", template)
// Keys are always uppercase in the format ###ZARF_KEY###
template = strings.ToUpper(fmt.Sprintf("###ZARF_%s###", template))
utils.ReplaceText(path, template, value)
}
}
31 changes: 31 additions & 0 deletions src/test/e2e/e2e_component_variables_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package test

import (
"fmt"
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestE2eComponentVariables(t *testing.T) {
defer e2e.cleanupAfterTest(t)

path := fmt.Sprintf("../../../build/zarf-package-component-variables-%s.tar.zst", e2e.arch)

//run `zarf init`
output, err := e2e.execZarfCommand("init", "--confirm")
require.NoError(t, err, output)

// Deploy the simple configmap
output, err = e2e.execZarfCommand("package", "deploy", path, "--confirm")
require.NoError(t, err, output)

// Verify the configmap was properly templated
kubectlOut, _ := exec.Command("kubectl", "-n", "zarf", "get", "configmap", "simple-configmap", "-o", "jsonpath='{.data.templateme\\.properties}' ").Output()
assert.Contains(t, string(kubectlOut), "dog=woof")
assert.Contains(t, string(kubectlOut), "cat=meow")
// zebra should remain unset as it is not a component variable
assert.Contains(t, string(kubectlOut), "zebra=###ZARF_ZEBRA###")
}
16 changes: 11 additions & 5 deletions src/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,21 @@ type ZarfComponent struct {

//Path to cosign publickey for signed online resources
CosignKeyPath string `yaml:"cosignKeyPath,omitempty"`

// Dynamic template values for K8s resources
Variables ZarfComponentVariables `yaml:"variables,omitempty"`
}

// ZarfComponentVariables are variables that can be used to dynaically template K8s resources
type ZarfComponentVariables map[string]string

// ZarfManifest defines raw manifests Zarf will deploy as a helm chart
type ZarfManifest struct {
Name string `yaml:"name"`
DefaultNamespace string `yaml:"namespace,omitempty"`
Files []string `yaml:"files,omitempty"`
KustomizeAllowAnyDirectory bool `yaml:"kustomizeAllowAnyDirectory,omitempty"`
Kustomizations []string `yaml:"kustomizations,omitempty"`
Name string `yaml:"name"`
DefaultNamespace string `yaml:"namespace,omitempty"`
Files []string `yaml:"files,omitempty"`
KustomizeAllowAnyDirectory bool `yaml:"kustomizeAllowAnyDirectory,omitempty"`
Kustomizations []string `yaml:"kustomizations,omitempty"`
}

// ZarfComponentScripts are scripts that run before or after a component is deployed
Expand Down
14 changes: 11 additions & 3 deletions zarf.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@
},
"cosignKeyPath": {
"type": "string"
},
"variables": {
"patternProperties": {
".*": {
"type": "string"
}
},
"type": "object"
}
},
"additionalProperties": false,
Expand Down Expand Up @@ -256,9 +264,6 @@
"name": {
"type": "string"
},
"kustomizeAllowAnyDirectory": {
"type": "boolean"
},
"namespace": {
"type": "string"
},
Expand All @@ -268,6 +273,9 @@
},
"type": "array"
},
"kustomizeAllowAnyDirectory": {
"type": "boolean"
},
"kustomizations": {
"items": {
"type": "string"
Expand Down

0 comments on commit 2c08a35

Please sign in to comment.