Skip to content
Closed
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
2 changes: 2 additions & 0 deletions src/api/internal/v1beta1/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ type ZarfDataInjection struct {
Target ZarfContainerTarget `json:"target"`
// Compress the data before transmitting using gzip. Note: this requires support for tar/gzip locally and in the target image.
Compress bool `json:"compress,omitempty"`
// [alpha] The type of data injection (default 'embedded' which bundles the data at create time).
Type string `json:"type,omitempty" jsonschema:"enum=embedded,enum=external"`
}

// ZarfComponentImport structure for including imported Zarf components.
Expand Down
9 changes: 9 additions & 0 deletions src/api/v1alpha1/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
// Package v1alpha1 holds the definition of the v1alpha1 Zarf Package
package v1alpha1

const (
// DataInjectionEmbedded is the default data injection type
DataInjectionEmbedded = "embedded"
// DataInjectionExternal is for external data injection
DataInjectionExternal = "external"
)

// ZarfComponent is the primary functional grouping of assets to deploy by Zarf.
type ZarfComponent struct {
// The name of the component.
Expand Down Expand Up @@ -373,6 +380,8 @@ type ZarfDataInjection struct {
Target ZarfContainerTarget `json:"target"`
// Compress the data before transmitting using gzip. Note: this requires support for tar/gzip locally and in the target image.
Compress bool `json:"compress,omitempty"`
// [alpha] The type of data injection (default 'embedded' which bundles the data at create time).
Type string `json:"type,omitempty" jsonschema:"enum=embedded,enum=external"`
}

// ZarfComponentImport structure for including imported Zarf components.
Expand Down
3 changes: 2 additions & 1 deletion src/cmd/crane.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@ func doPruneImagesForPackages(ctx context.Context, options []crane.Option, s *st

digest, err := crane.Digest(transformedImageNoCheck, options...)
if err != nil {
return err
l.Warn("unable to get digest for image, skipping prune check", "image", transformedImageNoCheck, "error", err.Error())
continue
}
pkgImages[digest] = true
}
Expand Down
7 changes: 7 additions & 0 deletions src/pkg/cluster/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,14 @@ func (c *Cluster) HandleDataInjection(ctx context.Context, data v1alpha1.ZarfDat
l.Debug("performing data injection", "target", data.Target)

source := filepath.Join(dataInjectionPath, filepath.Base(data.Target.Path))
if data.Type == v1alpha1.DataInjectionExternal {
source = dataInjectionPath
}

if helpers.InvalidPath(source) {
if data.Type == v1alpha1.DataInjectionExternal {
return fmt.Errorf("could not find the external data injection source path %s", source)
}
// The path is likely invalid because of how we compose OCI components, add an index suffix to the filename
source = filepath.Join(dataInjectionPath, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
if helpers.InvalidPath(source) {
Expand Down
30 changes: 20 additions & 10 deletions src/pkg/packager/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,17 +486,27 @@ func (d *deployer) deployComponent(ctx context.Context, pkgLayout *layout.Packag

g, gCtx := errgroup.WithContext(ctx)
for idx, data := range component.DataInjections {
tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
return nil, err
}
defer func() {
err = errors.Join(err, os.RemoveAll(tmpDir))
}()
dataInjectionsPath, err := pkgLayout.GetComponentDir(ctx, tmpDir, component.Name, layout.DataComponentDir)
if err != nil {
return nil, err
var dataInjectionsPath string
if data.Type == v1alpha1.DataInjectionExternal {
source := d.vc.ReplaceString(data.Source)
if source == "" {
return nil, fmt.Errorf("data injection source cannot be empty for external type")
}
dataInjectionsPath = source
} else {
tmpDir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
if err != nil {
return nil, err
}
defer func() {
err = errors.Join(err, os.RemoveAll(tmpDir))
}()
dataInjectionsPath, err = pkgLayout.GetComponentDir(ctx, tmpDir, component.Name, layout.DataComponentDir)
if err != nil {
return nil, err
}
}

g.Go(func() error {
return d.c.HandleDataInjection(gCtx, data, dataInjectionsPath, idx)
})
Expand Down
8 changes: 8 additions & 0 deletions src/pkg/packager/layout/assemble.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ func assemblePackageComponent(ctx context.Context, component v1alpha1.ZarfCompon
}

for dataIdx, data := range component.DataInjections {
if data.Type == v1alpha1.DataInjectionExternal {
continue
}

rel := filepath.Join(string(DataComponentDir), strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
dst := filepath.Join(compBuildPath, rel)

Expand Down Expand Up @@ -626,6 +630,10 @@ func assembleSkeletonComponent(ctx context.Context, component v1alpha1.ZarfCompo
}

for dataIdx, data := range component.DataInjections {
if data.Type == v1alpha1.DataInjectionExternal {
continue
}

rel := filepath.Join(string(DataComponentDir), strconv.Itoa(dataIdx), filepath.Base(data.Target.Path))
dst := filepath.Join(compBuildPath, rel)

Expand Down
3 changes: 3 additions & 0 deletions src/pkg/packager/layout/sbom.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ func createFileSBOM(ctx context.Context, component v1alpha1.ZarfComponent, outpu
err = errors.Join(err, os.RemoveAll(tmpDir))
}()
tarPath := filepath.Join(buildPath, ComponentsDir, component.Name) + ".tar"
if _, err := os.Stat(tarPath); os.IsNotExist(err) {
return nil, nil
}
err = archive.Decompress(ctx, tarPath, tmpDir, archive.DecompressOpts{})
if err != nil {
return nil, err
Expand Down
11 changes: 11 additions & 0 deletions src/pkg/variables/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ func (vc *VariableConfig) GetAllTemplates() map[string]*TextTemplate {
return templateMap
}

// ReplaceString replaces text templates in a string.
func (vc *VariableConfig) ReplaceString(text string) string {
templateMap := vc.GetAllTemplates()
for key, template := range templateMap {
if template != nil {
text = strings.ReplaceAll(text, key, template.Value)
}
}
return text
}

// ReplaceTextTemplate loads a file from a given path, replaces text in it and writes it back in place.
func (vc *VariableConfig) ReplaceTextTemplate(path string) (err error) {
templateRegex := fmt.Sprintf("###%s_[A-Z0-9_]+###", strings.ToUpper(vc.templatePrefix))
Expand Down
103 changes: 103 additions & 0 deletions src/test/e2e/23_data_injection_external_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2021-Present The Zarf Authors

package test

import (
"fmt"
"os"
"path/filepath"
"testing"

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

func TestExternalDataInjection(t *testing.T) {
t.Log("E2E: External Data injection")

tmpdir := t.TempDir()

image := "alpine:latest"

podYaml := fmt.Sprintf(`apiVersion: v1
kind: Pod
metadata:
name: external-data-pod
namespace: external-data-test
labels:
app: external-data-test
spec:
containers:
- name: alpine
image: %s
command: ["/bin/sh", "-c", "while true; do sleep 3600; done"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
`, image)

err := os.WriteFile(filepath.Join(tmpdir, "pod.yaml"), []byte(podYaml), 0644)
require.NoError(t, err)

zarfYaml := fmt.Sprintf(`kind: ZarfPackageConfig
metadata:
name: external-data
version: 0.0.1

components:
- name: data-pod
required: true
manifests:
- name: pod
namespace: external-data-test
files:
- pod.yaml
images:
- %s

- name: inject-data
required: true
dataInjections:
- source: "###ZARF_VAR_EXT_DATA###"
type: external
target:
namespace: external-data-test
selector: app=external-data-test
container: alpine
path: /data
`, image)

err = os.WriteFile(filepath.Join(tmpdir, "zarf.yaml"), []byte(zarfYaml), 0644)
require.NoError(t, err)

// Create data to inject
dataDir := filepath.Join(tmpdir, "my-data")
err = os.Mkdir(dataDir, 0755)
require.NoError(t, err)
err = os.WriteFile(filepath.Join(dataDir, "test.txt"), []byte("hello external world"), 0644)
require.NoError(t, err)

// Create package
stdOut, stdErr, err := e2e.Zarf(t, "package", "create", tmpdir, "-o", tmpdir, "--confirm")
require.NoError(t, err, stdOut, stdErr)

packageName := fmt.Sprintf("zarf-package-external-data-%s-0.0.1.tar.zst", e2e.Arch)
packagePath := filepath.Join(tmpdir, packageName)

// Deploy package with variable
stdOut, stdErr, err = e2e.Zarf(t, "package", "deploy", packagePath, "--confirm", "--set", fmt.Sprintf("EXT_DATA=%s", dataDir))
require.NoError(t, err, stdOut, stdErr)

// Verify injection
stdOut, stdErr, err = e2e.Kubectl(t, "-n", "external-data-test", "exec", "external-data-pod", "--", "cat", "/data/test.txt")
require.NoError(t, err, stdOut, stdErr)
require.Contains(t, stdOut, "hello external world")

// Cleanup
stdOut, stdErr, err = e2e.Zarf(t, "package", "remove", packagePath, "--confirm")
require.NoError(t, err, stdOut, stdErr)
}

8 changes: 8 additions & 0 deletions zarf.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,14 @@
"target": {
"$ref": "#/$defs/ZarfContainerTarget",
"description": "The target pod + container to inject the data into."
},
"type": {
"description": "[alpha] The type of data injection (default 'embedded' which bundles the data at create time).",
"enum": [
"embedded",
"external"
],
"type": "string"
}
},
"required": [
Expand Down