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
29 changes: 23 additions & 6 deletions .dagger/build_publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ func (m *Cuestomize) Build(
ctx context.Context,
// +defaultPath=./
buildContext *dagger.Directory,
// +defaultPath="./.git"
git *dagger.GitRepository,
// +optional
ref string,
// +default=""
platform string,
// +default=""
Expand All @@ -29,9 +29,23 @@ func (m *Cuestomize) Build(
) *dagger.Container {
ldflags = fmt.Sprintf("-X 'main.Version=%s' %s", version, ldflags)

git := FromDirectory(buildContext)

ctx, sp := Tracer().Start(ctx, "building Cuestomize image")
defer sp.End()

if ref != "" {
git.Stash(ctx)

git.Checkout(ctx, ref)

buildContext = git.Directory()
}

commit, err := git.Head().Commit(ctx)
if err != nil {
panic("failed to get git commit: " + err.Error())
sp.RecordError(fmt.Errorf("failed to get git commit: %w", err))
return nil
}

container := buildContext.DockerBuild(dagger.DirectoryDockerBuildOpts{
Expand Down Expand Up @@ -63,8 +77,8 @@ func (m *Cuestomize) BuildAndPublish(
password *dagger.Secret,
// +defaultPath=./
buildContext *dagger.Directory,
// +defaultPath="./.git"
git *dagger.GitRepository,
// +optional
ref string,
// +default="ghcr.io"
registry string,
repository string,
Expand All @@ -87,7 +101,10 @@ func (m *Cuestomize) BuildAndPublish(

platformVariants := make([]*dagger.Container, 0, len(platforms))
for _, platform := range platforms {
container := m.Build(ctx, buildContext, git, string(platform), ldflags, version)
container := m.Build(ctx, buildContext, ref, platform, ldflags, version)
if container == nil {
return fmt.Errorf("failed to build container for platform %s", platform)
}
platformVariants = append(platformVariants, container)
}

Expand Down
2 changes: 2 additions & 0 deletions .dagger/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const (
CuelangVersion = "v0.16.0"
// GolangciLintImage is the GolangCI-Lint image used by default
GolangciLintImage = "golangci/golangci-lint:v2.11.4-alpine"
// GitImage is the image used for Git operations in Dagger
GitImage = "alpine/git:2.52.0"
)

const (
Expand Down
53 changes: 53 additions & 0 deletions .dagger/git.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package main

import (
"context"
"dagger/cuestomize/internal/dagger"
)

type GitRepository struct {
dir *dagger.Directory
}

func FromDirectory(dir *dagger.Directory) *GitRepository {
return &GitRepository{
dir: dir,
}
}

func (r *GitRepository) Directory() *dagger.Directory {
return r.dir
}

func (r *GitRepository) AsGit() *dagger.GitRepository {
return r.Directory().AsGit()
}

func (r *GitRepository) Head() *dagger.GitRef {
return r.AsGit().Head()
}

func (r *GitRepository) Stash(ctx context.Context) {
r.Run(ctx, "stash")
}

func (r *GitRepository) Checkout(ctx context.Context, ref string) {
r.Run(ctx, "checkout", ref)
}

func (r *GitRepository) output(c *dagger.Container) {
r.dir = c.Directory("/git")
}

func (r *GitRepository) Run(ctx context.Context, args ...string) *dagger.Container {
cmd := []string{"git"}
cmd = append(cmd, args...)
c := dag.Container().From(GitImage).
WithWorkdir("/git").
WithDirectory("/git", r.dir).
WithExec(cmd)

r.output(c)

return c
}
2 changes: 1 addition & 1 deletion .dagger/testing_pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (m *Cuestomize) E2E_Test(
git *dagger.GitRepository,
) error {
// build cuestomize
cuestomize := m.Build(ctx, buildContext, git, "", "", "nightly")
cuestomize := m.Build(ctx, buildContext, "", "", "", "nightly")

cuestomizeBinary := cuestomize.File("/usr/local/bin/cuestomize")

Expand Down
11 changes: 11 additions & 0 deletions pkg/cuestomize/checkers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ const (
ValidatorAnnotationKey = "config.cuestomize.io/validator"
// ValidatorAnnotationValue is the value of the annotation that marks a CUE function as a validator.
ValidatorAnnotationValue = "true"

// EditStreamAnnotationKey is the annotation key that allows Cuestomize to edit resources in place.
EditStreamAnnotationKey = "config.cuestomize.io/edit-stream"
// EditStreamAnnotationValue is the value of the annotation that allows Cuestomize to edit resources in place.
EditStreamAnnotationValue = "true"
)

// CheckInstances checks if any of the instances have an error and returns an error if so.
Expand All @@ -32,3 +37,9 @@ func ShouldActAsValidator(config *api.KRMInput) bool {
return config.Annotations != nil &&
config.Annotations[ValidatorAnnotationKey] == ValidatorAnnotationValue
}

// AllowEditResourcesInStream checks if the KRMInput configuration has the edit stream annotation set.
func AllowEditResourcesInStream(config *api.KRMInput) bool {
return config.Annotations != nil &&
config.Annotations[EditStreamAnnotationKey] == EditStreamAnnotationValue
}
4 changes: 3 additions & 1 deletion pkg/cuestomize/cuestomize.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,7 @@ func Cuestomize(ctx context.Context, items []*kyaml.RNode, config *api.KRMInput,
log.V(4).Info("cuestomize is acting in validator mode.")
return items, nil // if the function is a validator, return the original items without processing
}
return ProcessOutputs(ctx, unified, items)
return ProcessOutputs(ctx, unified, items, OutputOptions{
AllowEdit: AllowEditResourcesInStream(config),
})
}
63 changes: 57 additions & 6 deletions pkg/cuestomize/outputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,20 @@ import (
"cuelang.org/go/cue"
"cuelang.org/go/encoding/yaml"
"github.com/Workday/cuestomize/pkg/cuerrors"
"github.com/go-logr/logr"

"sigs.k8s.io/kustomize/kyaml/resid"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
)

// ProcessOutputs processes the outputs from the CUE model and appends them to the output slice.
func ProcessOutputs(ctx context.Context, unified cue.Value, items []*kyaml.RNode) ([]*kyaml.RNode, error) {
log := logr.FromContextOrDiscard(ctx)
type OutputOptions struct {
// AllowEdit allows output resources to replace existing items in the stream if a matching ResId is found.
AllowEdit bool
}

// ProcessOutputs processes the outputs from the CUE model and appends them to the output slice.
// When allowEdit is true, output resources that match an existing item by ResId (GVK + namespace + name)
// will replace the existing item in place; otherwise they are appended.
func ProcessOutputs(ctx context.Context, unified cue.Value, items []*kyaml.RNode, opts OutputOptions) ([]*kyaml.RNode, error) {
detailer := cuerrors.FromContextOrEmpty(ctx)

outputsValue := unified.LookupPath(cue.ParsePath(OutputsPath))
Expand All @@ -29,6 +34,32 @@ func ProcessOutputs(ctx context.Context, unified cue.Value, items []*kyaml.RNode
return nil, fmt.Errorf("failed to get iterator over '%s' in unified CUE instance: %v", OutputsPath, err)
}

if !opts.AllowEdit {
return appendOutputs(ctx, outputsIter, items)
}
return editOutputs(ctx, outputsIter, items)
}

// appendOutputs appends all CUE outputs to the items slice (generate-only mode).
func appendOutputs(ctx context.Context, outputsIter *cue.Iterator, items []*kyaml.RNode) ([]*kyaml.RNode, error) {
for outputsIter.Next() {
item := outputsIter.Value()

rNode, err := cueValueToRNode(&item)
if err != nil {
return nil, fmt.Errorf("failed to convert CUE value to kyaml.RNode: %w", err)
}

items = append(items, rNode)
}
return items, nil
}

// editOutputs replaces existing resources in the items stream if a matching
// resource (by ResId) is found, or appends new ones.
func editOutputs(ctx context.Context, outputsIter *cue.Iterator, items []*kyaml.RNode) ([]*kyaml.RNode, error) {
residMap := make(map[resid.ResId]*kyaml.RNode)

for outputsIter.Next() {
item := outputsIter.Value()

Expand All @@ -37,10 +68,26 @@ func ProcessOutputs(ctx context.Context, unified cue.Value, items []*kyaml.RNode
return nil, fmt.Errorf("failed to convert CUE value to kyaml.RNode: %w", err)
}

log.V(4).Info("adding item to output resources",
"kind", rNode.GetKind(), "apiVersion", rNode.GetApiVersion(), "namespace", rNode.GetNamespace(), "name", rNode.GetName())
rid := residFromRNode(rNode)

if _, found := residMap[rid]; found {
return nil, fmt.Errorf("duplicate output resource with ResId '%s' found in CUE model", rid)
}
}

for i := range items {
itemRid := residFromRNode(items[i])
if cueOutputRNode, found := residMap[itemRid]; found {
items[i] = cueOutputRNode
delete(residMap, itemRid)
}
}

// append any remaining CUE output resource
for _, rNode := range residMap {
items = append(items, rNode)
}

return items, nil
}

Expand Down Expand Up @@ -74,3 +121,7 @@ func cueValueToRNode(value *cue.Value) (*kyaml.RNode, error) {

return rNode, nil
}

func residFromRNode(rNode *kyaml.RNode) resid.ResId {
return resid.NewResIdWithNamespace(resid.GvkFromNode(rNode), rNode.GetName(), rNode.GetNamespace())
}
Loading
Loading