From c55e885ede82e53f406208dd49730e60f807eeca Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 14:50:23 -0400 Subject: [PATCH 01/24] introduce api conversions Signed-off-by: Austin Abro --- src/api/convert/convert.go | 18 + src/api/convert/convert_test.go | 513 +++++++++++++++++++++++++++ src/internal/api/types/package.go | 356 +++++++++++++++++++ src/internal/api/v1alpha1/convert.go | 300 ++++++++++++++++ src/internal/api/v1beta1/convert.go | 487 +++++++++++++++++++++++++ 5 files changed, 1674 insertions(+) create mode 100644 src/api/convert/convert.go create mode 100644 src/api/convert/convert_test.go create mode 100644 src/internal/api/types/package.go create mode 100644 src/internal/api/v1alpha1/convert.go create mode 100644 src/internal/api/v1beta1/convert.go diff --git a/src/api/convert/convert.go b/src/api/convert/convert.go new file mode 100644 index 0000000000..8cde67b839 --- /dev/null +++ b/src/api/convert/convert.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package convert provides functions for converting between Zarf package API versions. +package convert + +import ( + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/api/v1beta1" + internalv1alpha1 "github.com/zarf-dev/zarf/src/internal/api/v1alpha1" + internalv1beta1 "github.com/zarf-dev/zarf/src/internal/api/v1beta1" +) + +// V1Alpha1PkgToV1Beta1 converts a v1alpha1 ZarfPackage to a v1beta1 ZarfPackage. +func V1Alpha1PkgToV1Beta1(pkg v1alpha1.ZarfPackage) v1beta1.ZarfPackage { + generic := internalv1alpha1.ConvertToGeneric(pkg) + return internalv1beta1.ConvertFromGeneric(generic) +} diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go new file mode 100644 index 0000000000..c6dec9fb3b --- /dev/null +++ b/src/api/convert/convert_test.go @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package convert + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/api/v1beta1" +) + +func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { + t.Parallel() + allowOverride := true + pkg := v1alpha1.ZarfPackage{ + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageConfig, + Metadata: v1alpha1.ZarfMetadata{ + Name: "test-pkg", + Description: "A test package", + Version: "1.0.0", + URL: "https://example.com", + Image: "https://example.com/image.png", + Authors: "Test Author", + Documentation: "https://docs.example.com", + Source: "https://github.com/example", + Vendor: "Example Corp", + AggregateChecksum: "abc123", + Architecture: "amd64", + Uncompressed: true, + AllowNamespaceOverride: &allowOverride, + Annotations: map[string]string{ + "existing": "annotation", + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + assert.Equal(t, v1beta1.APIVersion, result.APIVersion) + assert.Equal(t, v1beta1.ZarfPackageConfig, result.Kind) + assert.Equal(t, "test-pkg", result.Metadata.Name) + assert.Equal(t, "A test package", result.Metadata.Description) + assert.Equal(t, "1.0.0", result.Metadata.Version) + assert.Equal(t, "amd64", result.Metadata.Architecture) + assert.True(t, result.Metadata.Uncompressed) + assert.True(t, result.Metadata.AllowNamespaceOverride) + + // v1alpha1-only metadata fields should be migrated to annotations. + assert.Equal(t, "https://example.com", result.Metadata.Annotations["metadata.url"]) + assert.Equal(t, "https://example.com/image.png", result.Metadata.Annotations["metadata.image"]) + assert.Equal(t, "Test Author", result.Metadata.Annotations["metadata.authors"]) + assert.Equal(t, "https://docs.example.com", result.Metadata.Annotations["metadata.documentation"]) + assert.Equal(t, "https://github.com/example", result.Metadata.Annotations["metadata.source"]) + assert.Equal(t, "Example Corp", result.Metadata.Annotations["metadata.vendor"]) + // Existing annotation should be preserved. + assert.Equal(t, "annotation", result.Metadata.Annotations["existing"]) + + // AggregateChecksum should move from metadata to build. + assert.Equal(t, "abc123", result.Build.AggregateChecksum) +} + +func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { + t.Parallel() + signed := true + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfInitConfig, + Build: v1alpha1.ZarfBuildData{ + Terminal: "my-machine", + User: "test-user", + Architecture: "arm64", + Timestamp: "Mon, 02 Jan 2006 15:04:05 -0700", + Version: "v0.30.0", + Migrations: []string{"migration1"}, + RegistryOverrides: map[string]string{"docker.io": "internal.registry"}, + Differential: true, + DifferentialPackageVersion: "0.29.0", + DifferentialMissing: []string{"comp-a"}, + Flavor: "vanilla", + Signed: &signed, + VersionRequirements: []v1alpha1.VersionRequirement{ + {Version: "v0.28.0", Reason: "needs feature X"}, + }, + ProvenanceFiles: []string{"sig.json"}, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + assert.Equal(t, v1beta1.ZarfInitConfig, result.Kind) + assert.Equal(t, "my-machine", result.Build.Terminal) + assert.Equal(t, "test-user", result.Build.User) + assert.Equal(t, "arm64", result.Build.Architecture) + assert.Equal(t, "v0.30.0", result.Build.Version) + assert.True(t, result.Build.Differential) + assert.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) + assert.Equal(t, "vanilla", result.Build.Flavor) + require.NotNil(t, result.Build.Signed) + assert.True(t, *result.Build.Signed) + require.Len(t, result.Build.VersionRequirements, 1) + assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) + assert.Equal(t, "needs feature X", result.Build.VersionRequirements[0].Reason) + assert.Equal(t, []string{"sig.json"}, result.Build.ProvenanceFiles) +} + +func TestV1Alpha1PkgToV1Beta1_Variables(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Variables: []v1alpha1.InteractiveVariable{ + { + Variable: v1alpha1.Variable{ + Name: "MY_VAR", + Sensitive: true, + AutoIndent: true, + Pattern: "^[a-z]+$", + Type: v1alpha1.FileVariableType, + }, + Description: "A variable", + Default: "default-val", + Prompt: true, + }, + }, + Constants: []v1alpha1.Constant{ + { + Name: "MY_CONST", + Value: "const-val", + Description: "A constant", + AutoIndent: true, + Pattern: ".*", + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Variables, 1) + v := result.Variables[0] + assert.Equal(t, "MY_VAR", v.Name) + assert.True(t, v.Sensitive) + assert.True(t, v.AutoIndent) + assert.Equal(t, "^[a-z]+$", v.Pattern) + assert.Equal(t, v1beta1.FileVariableType, v.Type) + assert.Equal(t, "A variable", v.Description) + assert.Equal(t, "default-val", v.Default) + assert.True(t, v.Prompt) + + require.Len(t, result.Constants, 1) + c := result.Constants[0] + assert.Equal(t, "MY_CONST", c.Name) + assert.Equal(t, "const-val", c.Value) + assert.Equal(t, "A constant", c.Description) + assert.True(t, c.AutoIndent) +} + +func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { + t.Parallel() + required := true + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "my-component", + Description: "test component", + Default: true, + Required: &required, + DeprecatedGroup: "my-group", + Only: v1alpha1.ZarfComponentOnlyTarget{ + LocalOS: "linux", + Cluster: v1alpha1.ZarfComponentOnlyCluster{ + Architecture: "amd64", + Distros: []string{"k3s"}, + }, + Flavor: "vanilla", + }, + Import: v1alpha1.ZarfComponentImport{ + Name: "imported", + Path: "./path", + URL: "oci://example.com/pkg", + }, + Images: []string{"nginx:latest", "redis:7"}, + Repos: []string{"https://github.com/example/repo"}, + DataInjections: []v1alpha1.ZarfDataInjection{ + {Source: "/data", Target: v1alpha1.ZarfContainerTarget{Namespace: "default", Selector: "app=test", Container: "main", Path: "/inject"}}, + }, + HealthChecks: []v1alpha1.NamespacedObjectKindReference{ + {APIVersion: "apps/v1", Kind: "Deployment", Namespace: "default", Name: "my-deploy"}, + }, + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Components, 1) + comp := result.Components[0] + assert.Equal(t, "my-component", comp.Name) + assert.Equal(t, "test component", comp.Description) + + // Required=true → Optional=false + require.NotNil(t, comp.Optional) + assert.False(t, *comp.Optional) + + assert.Equal(t, "linux", comp.Only.LocalOS) + assert.Equal(t, "amd64", comp.Only.Cluster.Architecture) + assert.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) + assert.Equal(t, "vanilla", comp.Only.Flavor) + + // Import.Name is v1alpha1-only, should be dropped in v1beta1. + assert.Equal(t, "./path", comp.Import.Path) + assert.Equal(t, "oci://example.com/pkg", comp.Import.URL) + + // Images are converted from []string to []ZarfImage. + require.Len(t, comp.Images, 2) + assert.Equal(t, "nginx:latest", comp.Images[0].Name) + assert.Equal(t, "redis:7", comp.Images[1].Name) + + assert.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) + + // DataInjections should be preserved via the private shim. + require.Len(t, comp.GetDataInjections(), 1) + assert.Equal(t, "/data", comp.GetDataInjections()[0].Source) +} + +func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { + t.Parallel() + tests := []struct { + name string + chart v1alpha1.ZarfChart + validate func(t *testing.T, c v1beta1.ZarfChart) + }{ + { + name: "helm repo", + chart: v1alpha1.ZarfChart{ + Name: "podinfo", + URL: "https://stefanprodan.github.io/podinfo", + RepoName: "podinfo", + Version: "6.4.0", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + assert.Equal(t, "https://stefanprodan.github.io/podinfo", c.HelmRepo.URL) + assert.Equal(t, "podinfo", c.HelmRepo.Name) + assert.Equal(t, "6.4.0", c.HelmRepo.Version) + }, + }, + { + name: "oci registry", + chart: v1alpha1.ZarfChart{ + Name: "podinfo", + URL: "oci://ghcr.io/stefanprodan/charts/podinfo", + Version: "6.4.0", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + assert.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.OCI.URL) + assert.Equal(t, "6.4.0", c.OCI.Version) + }, + }, + { + name: "git repo", + chart: v1alpha1.ZarfChart{ + Name: "my-chart", + URL: "https://github.com/example/repo", + GitPath: "charts/my-chart", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + assert.Equal(t, "https://github.com/example/repo", c.Git.URL) + assert.Equal(t, "charts/my-chart", c.Git.Path) + }, + }, + { + name: "local path", + chart: v1alpha1.ZarfChart{ + Name: "local-chart", + LocalPath: "./charts/my-chart", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + assert.Equal(t, "./charts/my-chart", c.Local.Path) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "chart-comp", + Charts: []v1alpha1.ZarfChart{tt.chart}, + }, + }, + } + result := V1Alpha1PkgToV1Beta1(pkg) + require.Len(t, result.Components, 1) + require.Len(t, result.Components[0].Charts, 1) + tt.validate(t, result.Components[0].Charts[0]) + }) + } +} + +func TestV1Alpha1PkgToV1Beta1_ManifestNoWaitInversion(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "manifest-comp", + Manifests: []v1alpha1.ZarfManifest{ + { + Name: "with-no-wait", + NoWait: true, + }, + { + Name: "default-wait", + }, + }, + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Components[0].Manifests, 2) + + // NoWait=true → Wait=false + m0 := result.Components[0].Manifests[0] + require.NotNil(t, m0.Wait) + assert.False(t, *m0.Wait) + + // NoWait=false (default) → Wait=nil (v1beta1 defaults to true) + m1 := result.Components[0].Manifests[1] + assert.Nil(t, m1.Wait) +} + +func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { + t.Parallel() + mute := true + maxSeconds := 30 + maxRetries := 3 + dir := "/tmp" + + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "action-comp", + Actions: v1alpha1.ZarfComponentActions{ + OnDeploy: v1alpha1.ZarfComponentActionSet{ + Defaults: v1alpha1.ZarfComponentActionDefaults{ + Mute: true, + MaxTotalSeconds: 60, + MaxRetries: 2, + Dir: "/work", + Env: []string{"FOO=bar"}, + Shell: v1alpha1.Shell{ + Linux: "bash", + }, + }, + Before: []v1alpha1.ZarfComponentAction{ + { + Cmd: "echo before", + Mute: &mute, + MaxTotalSeconds: &maxSeconds, + MaxRetries: &maxRetries, + Dir: &dir, + Description: "run before", + SetVariables: []v1alpha1.Variable{ + {Name: "OUT_VAR", Sensitive: true}, + }, + DeprecatedSetVariable: "OLD_VAR", + }, + }, + After: []v1alpha1.ZarfComponentAction{ + {Cmd: "echo after"}, + }, + OnSuccess: []v1alpha1.ZarfComponentAction{ + {Cmd: "echo success"}, + }, + OnFailure: []v1alpha1.ZarfComponentAction{ + {Cmd: "echo failure"}, + }, + }, + }, + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Components, 1) + actions := result.Components[0].Actions + + // Defaults + assert.True(t, actions.OnDeploy.Defaults.Mute) + require.NotNil(t, actions.OnDeploy.Defaults.Timeout) + assert.Equal(t, 60*time.Second, actions.OnDeploy.Defaults.Timeout.Duration) + assert.Equal(t, 2, actions.OnDeploy.Defaults.Retries) + assert.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) + assert.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) + assert.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) + + // Before action + require.Len(t, actions.OnDeploy.Before, 1) + before := actions.OnDeploy.Before[0] + assert.Equal(t, "echo before", before.Cmd) + require.NotNil(t, before.Mute) + assert.True(t, *before.Mute) + require.NotNil(t, before.Timeout) + assert.Equal(t, 30*time.Second, before.Timeout.Duration) + assert.Equal(t, 3, before.Retries) + assert.Equal(t, "run before", before.Description) + // SetVariables should include both the explicit one and the deprecated one. + require.Len(t, before.SetVariables, 2) + assert.Equal(t, "OUT_VAR", before.SetVariables[0].Name) + assert.True(t, before.SetVariables[0].Sensitive) + assert.Equal(t, "OLD_VAR", before.SetVariables[1].Name) + + // After should include original After + OnSuccess (merged). + require.Len(t, actions.OnDeploy.After, 2) + assert.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) + assert.Equal(t, "echo success", actions.OnDeploy.After[1].Cmd) + + // OnFailure + require.Len(t, actions.OnDeploy.OnFailure, 1) + assert.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) +} + +func TestV1Alpha1PkgToV1Beta1_Files(t *testing.T) { + t.Parallel() + tmpl := true + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "file-comp", + Files: []v1alpha1.ZarfFile{ + { + Source: "https://example.com/file.tar.gz", + Shasum: "deadbeef", + Target: "/opt/file.tar.gz", + Executable: true, + Symlinks: []string{"/usr/local/bin/file"}, + ExtractPath: "bin/file", + Template: &tmpl, + }, + }, + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Components[0].Files, 1) + f := result.Components[0].Files[0] + assert.Equal(t, "https://example.com/file.tar.gz", f.Source) + assert.Equal(t, "deadbeef", f.Shasum) + assert.Equal(t, "/opt/file.tar.gz", f.Target) + assert.True(t, f.Executable) + assert.Equal(t, []string{"/usr/local/bin/file"}, f.Symlinks) + assert.Equal(t, "bin/file", f.ExtractPath) + require.NotNil(t, f.Template) + assert.True(t, *f.Template) +} + +func TestV1Alpha1PkgToV1Beta1_ValuesAndDocumentation(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Values: v1alpha1.ZarfValues{ + Files: []string{"values.yaml"}, + Schema: "values.schema.json", + }, + Documentation: map[string]string{ + "readme": "# Hello", + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + assert.Equal(t, []string{"values.yaml"}, result.Values.Files) + assert.Equal(t, "values.schema.json", result.Values.Schema) + assert.Equal(t, "# Hello", result.Documentation["readme"]) +} + +func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + { + Name: "chart-comp", + Charts: []v1alpha1.ZarfChart{ + { + Name: "my-chart", + URL: "https://charts.example.com", + Version: "1.2.3", + }, + }, + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Len(t, result.Components[0].Charts, 1) + chart := result.Components[0].Charts[0] + assert.Equal(t, "1.2.3", chart.GetDeprecatedVersion()) +} diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go new file mode 100644 index 0000000000..44c5eefedb --- /dev/null +++ b/src/internal/api/types/package.go @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package types holds the internal generic representation of a Zarf package used for lossless conversions between API versions. +// This type is never exposed publicly. Each API version converts to/from this type, giving N conversion functions instead of N². +package types + +import ( + "github.com/zarf-dev/zarf/src/api/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ZarfPackage is the internal superset representation used for conversions between API versions. +type ZarfPackage struct { + APIVersion string + Kind string + Metadata ZarfMetadata + Build ZarfBuildData + Components []ZarfComponent + Constants []Constant + Variables []InteractiveVariable + Values ZarfValues + Documentation map[string]string +} + +// ZarfMetadata is a superset of all metadata fields across API versions. +type ZarfMetadata struct { + Name string + Description string + Version string + Uncompressed bool + Architecture string + Annotations map[string]string + AllowNamespaceOverride *bool + + // v1alpha1-only fields + URL string + Image string + YOLO bool + Authors string + Documentation string + Source string + Vendor string + // AggregateChecksum lives in metadata in v1alpha1, build in v1beta1 + AggregateChecksum string +} + +// ZarfBuildData is a superset of all build fields across API versions. +type ZarfBuildData struct { + Terminal string + User string + Architecture string + Timestamp string + Version string + Migrations []string + RegistryOverrides map[string]string + Differential bool + DifferentialPackageVersion string + Flavor string + Signed *bool + VersionRequirements []VersionRequirement + ProvenanceFiles []string + AggregateChecksum string + + // v1alpha1-only + DifferentialMissing []string +} + +// VersionRequirement specifies a minimum Zarf version needed. +type VersionRequirement struct { + Version string + Reason string +} + +// Constant represents a template constant. +type Constant struct { + Name string + Value string + Description string + AutoIndent bool + Pattern string +} + +// Variable represents a variable. +type Variable struct { + Name string + Sensitive bool + AutoIndent bool + Pattern string + Type string +} + +// InteractiveVariable is a variable that can prompt the user. +type InteractiveVariable struct { + Variable + Description string + Default string + Prompt bool +} + +// SetVariable tracks a variable that has been set. +type SetVariable struct { + Variable + Value string +} + +// SetValue declares a value that can be set during deploy. +type SetValue struct { + Key string + Value any + Type string +} + +// ZarfValues defines values files and schema. +type ZarfValues struct { + Files []string + Schema string +} + +// ZarfComponent is the internal superset of component fields. +type ZarfComponent struct { + Name string + Description string + Only ZarfComponentOnlyTarget + Import ZarfComponentImport + Manifests []ZarfManifest + Charts []ZarfChart + Files []ZarfFile + Images []ZarfImage + ImageArchives []ImageArchive + Repos []string + Actions ZarfComponentActions + Features ZarfComponentFeatures + + // v1alpha1-only fields preserved for lossless conversion + Default bool + Required *bool + Optional *bool + Group string + DataInjections []v1alpha1.ZarfDataInjection + HealthChecks []v1alpha1.NamespacedObjectKindReference + // v1alpha1 chart variables are dropped (no shim needed, per proposal) +} + +// ZarfComponentOnlyTarget filters a component. +type ZarfComponentOnlyTarget struct { + LocalOS string + Cluster ZarfComponentOnlyCluster + Flavor string +} + +// ZarfComponentOnlyCluster represents architecture and distro filters. +type ZarfComponentOnlyCluster struct { + Architecture string + Distros []string +} + +// ZarfComponentImport defines an imported component. +type ZarfComponentImport struct { + // v1alpha1-only + Name string + Path string + URL string +} + +// ZarfFile defines a file to deploy. +type ZarfFile struct { + Source string + Shasum string + Target string + Executable bool + Symlinks []string + ExtractPath string + Template *bool +} + +// ZarfImage represents an OCI image. +type ZarfImage struct { + Name string + Source string +} + +// ImageArchive defines a tar archive of images. +type ImageArchive struct { + Path string + Images []string +} + +// ZarfChart is the internal superset of chart fields. +type ZarfChart struct { + Name string + Namespace string + ReleaseName string + ValuesFiles []string + Values []ZarfChartValue + SchemaValidation *bool + ServerSideApply string + Wait *bool + + // v1beta1 structured sources + HelmRepo HelmRepoSource + Git GitRepoSource + Local LocalRepoSource + OCI OCISource + + // v1alpha1 flat fields (used during conversion to populate structured sources) + URL string + RepoName string + GitPath string + LocalPath string + Version string + NoWait bool +} + +// ZarfChartValue maps a source path to a target path. +type ZarfChartValue struct { + SourcePath string + TargetPath string +} + +// HelmRepoSource represents a Helm chart in a repository. +type HelmRepoSource struct { + Name string + URL string + Version string +} + +// GitRepoSource represents a Helm chart in a Git repo. +type GitRepoSource struct { + URL string + Path string +} + +// LocalRepoSource represents a local Helm chart. +type LocalRepoSource struct { + Path string +} + +// OCISource represents a Helm chart in an OCI registry. +type OCISource struct { + URL string + Version string +} + +// ZarfManifest defines raw manifests deployed as a Helm chart. +type ZarfManifest struct { + Name string + Namespace string + Files []string + KustomizeAllowAnyDirectory bool + Kustomizations []string + ServerSideApply string + Template *bool + Wait *bool + NoWait bool + + // v1alpha1-only + EnableKustomizePlugins bool +} + +// ZarfComponentFeatures defines CLI features for a component. +type ZarfComponentFeatures struct { + IsRegistry bool + Injector *Injector + IsAgent bool +} + +// Injector defines the Zarf injector configuration. +type Injector struct { + Enabled bool + Values *InjectorValues +} + +// InjectorValues defines configurable values for the injector. +type InjectorValues struct { + Tolerations string +} + +// ZarfComponentActions are action sets mapped to lifecycle operations. +type ZarfComponentActions struct { + OnCreate ZarfComponentActionSet + OnDeploy ZarfComponentActionSet + OnRemove ZarfComponentActionSet +} + +// ZarfComponentActionSet is a set of actions for a lifecycle operation. +type ZarfComponentActionSet struct { + Defaults ZarfComponentActionDefaults + Before []ZarfComponentAction + After []ZarfComponentAction + OnSuccess []ZarfComponentAction // v1alpha1-only, merged into After for v1beta1 + OnFailure []ZarfComponentAction +} + +// ZarfComponentActionDefaults sets default configs for child actions. +type ZarfComponentActionDefaults struct { + Mute bool + Timeout *metav1.Duration + Retries int + Dir string + Env []string + Shell Shell + + // v1alpha1 fields + MaxTotalSeconds int + MaxRetries int +} + +// ZarfComponentAction represents a single action. +type ZarfComponentAction struct { + Mute *bool + Timeout *metav1.Duration + Retries int + Dir *string + Env []string + Cmd string + Shell *Shell + SetVariables []Variable + SetValues []SetValue + Description string + Wait *ZarfComponentActionWait + Template *bool + + // v1alpha1 fields + MaxTotalSeconds *int + MaxRetries *int + DeprecatedSetVariable string +} + +// ZarfComponentActionWait specifies a wait condition. +type ZarfComponentActionWait struct { + Cluster *ZarfComponentActionWaitCluster + Network *ZarfComponentActionWaitNetwork +} + +// ZarfComponentActionWaitCluster specifies a cluster wait condition. +type ZarfComponentActionWaitCluster struct { + Kind string + Name string + Namespace string + Condition string +} + +// ZarfComponentActionWaitNetwork specifies a network wait condition. +type ZarfComponentActionWaitNetwork struct { + Protocol string + Address string + Code int +} + +// Shell represents shell preferences per OS. +type Shell struct { + Windows string + Linux string + Darwin string +} diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go new file mode 100644 index 0000000000..55685cd2d8 --- /dev/null +++ b/src/internal/api/v1alpha1/convert.go @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package v1alpha1 + +import ( + "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/internal/api/types" +) + +// ConvertToGeneric converts a v1alpha1 ZarfPackage to the internal generic representation. +func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { + g := types.ZarfPackage{ + APIVersion: pkg.APIVersion, + Kind: string(pkg.Kind), + Metadata: types.ZarfMetadata{ + Name: pkg.Metadata.Name, + Description: pkg.Metadata.Description, + Version: pkg.Metadata.Version, + Uncompressed: pkg.Metadata.Uncompressed, + Architecture: pkg.Metadata.Architecture, + Annotations: pkg.Metadata.Annotations, + AllowNamespaceOverride: pkg.Metadata.AllowNamespaceOverride, + URL: pkg.Metadata.URL, + Image: pkg.Metadata.Image, + YOLO: pkg.Metadata.YOLO, + Authors: pkg.Metadata.Authors, + Documentation: pkg.Metadata.Documentation, + Source: pkg.Metadata.Source, + Vendor: pkg.Metadata.Vendor, + AggregateChecksum: pkg.Metadata.AggregateChecksum, + }, + Build: types.ZarfBuildData{ + Terminal: pkg.Build.Terminal, + User: pkg.Build.User, + Architecture: pkg.Build.Architecture, + Timestamp: pkg.Build.Timestamp, + Version: pkg.Build.Version, + Migrations: pkg.Build.Migrations, + RegistryOverrides: pkg.Build.RegistryOverrides, + Differential: pkg.Build.Differential, + DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, + Flavor: pkg.Build.Flavor, + Signed: pkg.Build.Signed, + DifferentialMissing: pkg.Build.DifferentialMissing, + ProvenanceFiles: pkg.Build.ProvenanceFiles, + }, + Values: types.ZarfValues{ + Files: pkg.Values.Files, + Schema: pkg.Values.Schema, + }, + Documentation: pkg.Documentation, + } + + for _, vr := range pkg.Build.VersionRequirements { + g.Build.VersionRequirements = append(g.Build.VersionRequirements, types.VersionRequirement{ + Version: vr.Version, + Reason: vr.Reason, + }) + } + + for _, c := range pkg.Constants { + g.Constants = append(g.Constants, types.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + + for _, v := range pkg.Variables { + g.Variables = append(g.Variables, types.InteractiveVariable{ + Variable: types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: string(v.Type), + }, + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + + for _, c := range pkg.Components { + g.Components = append(g.Components, convertV1Alpha1Component(c)) + } + + return g +} + +func convertV1Alpha1Component(c v1alpha1.ZarfComponent) types.ZarfComponent { + gc := types.ZarfComponent{ + Name: c.Name, + Description: c.Description, + Default: c.Default, + Required: c.Required, + Group: c.DeprecatedGroup, + DataInjections: c.DataInjections, + HealthChecks: c.HealthChecks, + Repos: c.Repos, + Only: types.ZarfComponentOnlyTarget{ + LocalOS: c.Only.LocalOS, + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: c.Only.Cluster.Architecture, + Distros: c.Only.Cluster.Distros, + }, + Flavor: c.Only.Flavor, + }, + Import: types.ZarfComponentImport{ + Name: c.Import.Name, + Path: c.Import.Path, + URL: c.Import.URL, + }, + Actions: convertV1Alpha1Actions(c.Actions), + } + + for _, m := range c.Manifests { + gc.Manifests = append(gc.Manifests, types.ZarfManifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, + Kustomizations: m.Kustomizations, + ServerSideApply: m.ServerSideApply, + Template: m.Template, + NoWait: m.NoWait, + EnableKustomizePlugins: m.EnableKustomizePlugins, + }) + } + + for _, ch := range c.Charts { + gc.Charts = append(gc.Charts, types.ZarfChart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SchemaValidation: ch.SchemaValidation, + ServerSideApply: ch.ServerSideApply, + NoWait: ch.NoWait, + URL: ch.URL, + RepoName: ch.RepoName, + GitPath: ch.GitPath, + LocalPath: ch.LocalPath, + Version: ch.Version, + Values: convertV1Alpha1ChartValues(ch.Values), + }) + } + + for _, f := range c.Files { + gc.Files = append(gc.Files, types.ZarfFile{ + Source: f.Source, + Shasum: f.Shasum, + Target: f.Target, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + Template: f.Template, + }) + } + + for _, img := range c.Images { + gc.Images = append(gc.Images, types.ZarfImage{ + Name: img, + }) + } + + for _, ia := range c.ImageArchives { + gc.ImageArchives = append(gc.ImageArchives, types.ImageArchive{ + Path: ia.Path, + Images: ia.Images, + }) + } + + return gc +} + +func convertV1Alpha1ChartValues(vals []v1alpha1.ZarfChartValue) []types.ZarfChartValue { + var out []types.ZarfChartValue + for _, v := range vals { + out = append(out, types.ZarfChartValue{ + SourcePath: v.SourcePath, + TargetPath: v.TargetPath, + }) + } + return out +} + +func convertV1Alpha1Actions(a v1alpha1.ZarfComponentActions) types.ZarfComponentActions { + return types.ZarfComponentActions{ + OnCreate: convertV1Alpha1ActionSet(a.OnCreate), + OnDeploy: convertV1Alpha1ActionSet(a.OnDeploy), + OnRemove: convertV1Alpha1ActionSet(a.OnRemove), + } +} + +func convertV1Alpha1ActionSet(s v1alpha1.ZarfComponentActionSet) types.ZarfComponentActionSet { + return types.ZarfComponentActionSet{ + Defaults: types.ZarfComponentActionDefaults{ + Mute: s.Defaults.Mute, + MaxTotalSeconds: s.Defaults.MaxTotalSeconds, + MaxRetries: s.Defaults.MaxRetries, + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: types.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, + }, + }, + Before: convertV1Alpha1ActionSlice(s.Before), + After: convertV1Alpha1ActionSlice(s.After), + OnSuccess: convertV1Alpha1ActionSlice(s.OnSuccess), + OnFailure: convertV1Alpha1ActionSlice(s.OnFailure), + } +} + +func convertV1Alpha1ActionSlice(actions []v1alpha1.ZarfComponentAction) []types.ZarfComponentAction { + var out []types.ZarfComponentAction + for _, a := range actions { + out = append(out, convertV1Alpha1Action(a)) + } + return out +} + +func convertV1Alpha1Action(a v1alpha1.ZarfComponentAction) types.ZarfComponentAction { + ga := types.ZarfComponentAction{ + Mute: a.Mute, + Retries: derefIntOr(a.MaxRetries, 0), + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: convertV1Alpha1Wait(a.Wait), + Template: a.Template, + MaxTotalSeconds: a.MaxTotalSeconds, + MaxRetries: a.MaxRetries, + DeprecatedSetVariable: a.DeprecatedSetVariable, + } + + for _, v := range a.SetVariables { + ga.SetVariables = append(ga.SetVariables, types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: string(v.Type), + }) + } + + for _, sv := range a.SetValues { + ga.SetValues = append(ga.SetValues, types.SetValue{ + Key: sv.Key, + Value: sv.Value, + Type: string(sv.Type), + }) + } + + if a.Shell != nil { + ga.Shell = &types.Shell{ + Windows: a.Shell.Windows, + Linux: a.Shell.Linux, + Darwin: a.Shell.Darwin, + } + } + + return ga +} + +func convertV1Alpha1Wait(w *v1alpha1.ZarfComponentActionWait) *types.ZarfComponentActionWait { + if w == nil { + return nil + } + gw := &types.ZarfComponentActionWait{} + if w.Cluster != nil { + gw.Cluster = &types.ZarfComponentActionWaitCluster{ + Kind: w.Cluster.Kind, + Name: w.Cluster.Name, + Namespace: w.Cluster.Namespace, + Condition: w.Cluster.Condition, + } + } + if w.Network != nil { + gw.Network = &types.ZarfComponentActionWaitNetwork{ + Protocol: w.Network.Protocol, + Address: w.Network.Address, + Code: w.Network.Code, + } + } + return gw +} + +func derefIntOr(p *int, def int) int { + if p != nil { + return *p + } + return def +} diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go new file mode 100644 index 0000000000..8191ffabd9 --- /dev/null +++ b/src/internal/api/v1beta1/convert.go @@ -0,0 +1,487 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package v1beta1 contains functions for validating and converting the public v1beta1 Zarf package +package v1beta1 + +import ( + "strings" + "time" + + "github.com/zarf-dev/zarf/src/api/v1beta1" + "github.com/zarf-dev/zarf/src/internal/api/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// ConvertFromGeneric converts the internal generic representation to a v1beta1 ZarfPackage. +func ConvertFromGeneric(g types.ZarfPackage) v1beta1.ZarfPackage { + pkg := v1beta1.ZarfPackage{ + APIVersion: v1beta1.APIVersion, + Kind: v1beta1.ZarfPackageKind(g.Kind), + Metadata: convertMetadata(g.Metadata), + Build: convertBuild(g.Build, g.Metadata), + Values: v1beta1.ZarfValues{ + Files: g.Values.Files, + Schema: g.Values.Schema, + }, + Documentation: g.Documentation, + } + + for _, c := range g.Constants { + pkg.Constants = append(pkg.Constants, v1beta1.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + + for _, v := range g.Variables { + pkg.Variables = append(pkg.Variables, v1beta1.InteractiveVariable{ + Variable: v1beta1.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: v1beta1.VariableType(v.Type), + }, + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + + for _, c := range g.Components { + pkg.Components = append(pkg.Components, convertComponent(c)) + } + + return pkg +} + +func convertMetadata(m types.ZarfMetadata) v1beta1.ZarfMetadata { + meta := v1beta1.ZarfMetadata{ + Name: m.Name, + Description: m.Description, + Version: m.Version, + Uncompressed: m.Uncompressed, + Architecture: m.Architecture, + Annotations: m.Annotations, + AllowNamespaceOverride: derefBoolOr(m.AllowNamespaceOverride, true), + } + + // Migrate v1alpha1-only metadata fields into annotations. + extras := map[string]string{ + "metadata.url": m.URL, + "metadata.image": m.Image, + "metadata.authors": m.Authors, + "metadata.documentation": m.Documentation, + "metadata.source": m.Source, + "metadata.vendor": m.Vendor, + } + for k, v := range extras { + if v == "" { + continue + } + if meta.Annotations == nil { + meta.Annotations = make(map[string]string) + } + meta.Annotations[k] = v + } + + return meta +} + +func convertBuild(b types.ZarfBuildData, m types.ZarfMetadata) v1beta1.ZarfBuildData { + out := v1beta1.ZarfBuildData{ + Terminal: b.Terminal, + User: b.User, + Architecture: b.Architecture, + Timestamp: b.Timestamp, + Version: b.Version, + Migrations: b.Migrations, + RegistryOverrides: b.RegistryOverrides, + Differential: b.Differential, + DifferentialPackageVersion: b.DifferentialPackageVersion, + Flavor: b.Flavor, + Signed: b.Signed, + ProvenanceFiles: b.ProvenanceFiles, + } + + // AggregateChecksum lives in metadata in v1alpha1, build in v1beta1. + if b.AggregateChecksum != "" { + out.AggregateChecksum = b.AggregateChecksum + } else if m.AggregateChecksum != "" { + out.AggregateChecksum = m.AggregateChecksum + } + + for _, vr := range b.VersionRequirements { + out.VersionRequirements = append(out.VersionRequirements, v1beta1.VersionRequirement{ + Version: vr.Version, + Reason: vr.Reason, + }) + } + + return out +} + +func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { + gc := v1beta1.ZarfComponent{ + Name: c.Name, + Description: c.Description, + Optional: convertRequiredToOptional(c.Required), + Only: v1beta1.ZarfComponentOnlyTarget{ + LocalOS: c.Only.LocalOS, + Cluster: v1beta1.ZarfComponentOnlyCluster{ + Architecture: c.Only.Cluster.Architecture, + Distros: c.Only.Cluster.Distros, + }, + Flavor: c.Only.Flavor, + }, + Import: v1beta1.ZarfComponentImport{ + Path: c.Import.Path, + URL: c.Import.URL, + }, + Features: v1beta1.ZarfComponentFeatures{ + IsRegistry: c.Features.IsRegistry, + IsAgent: c.Features.IsAgent, + }, + Repos: c.Repos, + Actions: convertActions(c.Actions), + } + + if c.Features.Injector != nil { + gc.Features.Injector = &v1beta1.Injector{ + Enabled: c.Features.Injector.Enabled, + } + if c.Features.Injector.Values != nil { + gc.Features.Injector.Values = &v1beta1.InjectorValues{ + Tolerations: c.Features.Injector.Values.Tolerations, + } + } + } + + for _, m := range c.Manifests { + gc.Manifests = append(gc.Manifests, convertManifest(m)) + } + + for _, ch := range c.Charts { + gc.Charts = append(gc.Charts, convertChart(ch)) + } + + for _, f := range c.Files { + gc.Files = append(gc.Files, v1beta1.ZarfFile{ + Source: f.Source, + Shasum: f.Shasum, + Target: f.Target, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + Template: f.Template, + }) + } + + for _, img := range c.Images { + gc.Images = append(gc.Images, v1beta1.ZarfImage{ + Name: img.Name, + Source: img.Source, + }) + } + + for _, ia := range c.ImageArchives { + gc.ImageArchives = append(gc.ImageArchives, v1beta1.ImageArchive{ + Path: ia.Path, + Images: ia.Images, + }) + } + + // Preserve v1alpha1-only fields via private shims for lossless round-tripping. + gc.SetDataInjections(c.DataInjections) + + return gc +} + +// convertRequiredToOptional inverts the v1alpha1 Required *bool into v1beta1 Optional *bool. +// v1alpha1 Required=nil → not required → Optional=nil (default false in v1beta1 means required) +// Wait — v1alpha1 Required=nil means "not required" but v1beta1 Optional=nil means "not optional" (required). +// So Required=nil needs to become Optional=true if the component was truly optional. +// However, the v1alpha1 default is Required=nil meaning "not required" only when Default is false. +// The safest mapping: Required=true → Optional=nil (required), Required=false/nil → Optional=true (optional). +// But actually the semantics differ: in v1alpha1, Required=nil + Default=false means the component +// prompts the user. In v1beta1, Optional=nil means required (no prompt). We preserve Required +// directly and let the caller interpret the v1alpha1 Default/Required/Group semantics. +func convertRequiredToOptional(required *bool) *bool { + if required == nil { + return nil + } + inverted := !*required + return &inverted +} + +func convertManifest(m types.ZarfManifest) v1beta1.ZarfManifest { + bm := v1beta1.ZarfManifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, + Kustomizations: m.Kustomizations, + ServerSideApply: m.ServerSideApply, + Template: m.Template, + } + + // Invert NoWait → Wait. If the user explicitly set Wait already, prefer that. + if m.Wait != nil { + bm.Wait = m.Wait + } else if m.NoWait { + f := false + bm.Wait = &f + } + + return bm +} + +func convertChart(ch types.ZarfChart) v1beta1.ZarfChart { + bc := v1beta1.ZarfChart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SchemaValidation: ch.SchemaValidation, + ServerSideApply: ch.ServerSideApply, + Values: convertChartValues(ch.Values), + } + + // Invert NoWait → Wait. + if ch.Wait != nil { + bc.Wait = ch.Wait + } else if ch.NoWait { + f := false + bc.Wait = &f + } + + // Convert flat v1alpha1 chart source fields into structured v1beta1 sources. + // If structured sources are already populated (from a v1beta1 origin), use them directly. + if ch.HelmRepo != (types.HelmRepoSource{}) { + bc.HelmRepo = v1beta1.HelmRepoSource{ + Name: ch.HelmRepo.Name, + URL: ch.HelmRepo.URL, + Version: ch.HelmRepo.Version, + } + } else if ch.Git != (types.GitRepoSource{}) { + bc.Git = v1beta1.GitRepoSource{ + URL: ch.Git.URL, + Path: ch.Git.Path, + } + } else if ch.Local != (types.LocalRepoSource{}) { + bc.Local = v1beta1.LocalRepoSource{ + Path: ch.Local.Path, + } + } else if ch.OCI != (types.OCISource{}) { + bc.OCI = v1beta1.OCISource{ + URL: ch.OCI.URL, + Version: ch.OCI.Version, + } + } else if ch.URL != "" { + // Infer source type from v1alpha1 flat fields. + switch { + case ch.LocalPath != "": + bc.Local = v1beta1.LocalRepoSource{ + Path: ch.LocalPath, + } + case strings.HasPrefix(ch.URL, "oci://"): + bc.OCI = v1beta1.OCISource{ + URL: ch.URL, + Version: ch.Version, + } + case ch.GitPath != "" || isGitURL(ch.URL): + bc.Git = v1beta1.GitRepoSource{ + URL: ch.URL, + Path: ch.GitPath, + } + default: + bc.HelmRepo = v1beta1.HelmRepoSource{ + Name: ch.RepoName, + URL: ch.URL, + Version: ch.Version, + } + } + } else if ch.LocalPath != "" { + bc.Local = v1beta1.LocalRepoSource{ + Path: ch.LocalPath, + } + } + + // Preserve the v1alpha1 flat version via the private shim for lossless round-tripping. + if ch.Version != "" { + bc.SetDeprecatedVersion(ch.Version) + } + + return bc +} + +func convertChartValues(vals []types.ZarfChartValue) []v1beta1.ZarfChartValue { + var out []v1beta1.ZarfChartValue + for _, v := range vals { + out = append(out, v1beta1.ZarfChartValue{ + SourcePath: v.SourcePath, + TargetPath: v.TargetPath, + }) + } + return out +} + +func convertActions(a types.ZarfComponentActions) v1beta1.ZarfComponentActions { + return v1beta1.ZarfComponentActions{ + OnCreate: convertActionSet(a.OnCreate), + OnDeploy: convertActionSet(a.OnDeploy), + OnRemove: convertActionSet(a.OnRemove), + } +} + +func convertActionSet(s types.ZarfComponentActionSet) v1beta1.ZarfComponentActionSet { + after := convertActionSlice(s.After) + // Merge v1alpha1 OnSuccess into After. + after = append(after, convertActionSlice(s.OnSuccess)...) + + return v1beta1.ZarfComponentActionSet{ + Defaults: convertActionDefaults(s.Defaults), + Before: convertActionSlice(s.Before), + After: after, + OnFailure: convertActionSlice(s.OnFailure), + } +} + +func convertActionDefaults(d types.ZarfComponentActionDefaults) v1beta1.ZarfComponentActionDefaults { + out := v1beta1.ZarfComponentActionDefaults{ + Mute: d.Mute, + Dir: d.Dir, + Env: d.Env, + Shell: v1beta1.Shell{ + Windows: d.Shell.Windows, + Linux: d.Shell.Linux, + Darwin: d.Shell.Darwin, + }, + } + + // Prefer the structured Duration if present, otherwise convert MaxTotalSeconds. + if d.Timeout != nil { + out.Timeout = d.Timeout + } else if d.MaxTotalSeconds > 0 { + dur := metav1.Duration{Duration: time.Duration(d.MaxTotalSeconds) * time.Second} + out.Timeout = &dur + } + + // Prefer Retries if set, otherwise convert MaxRetries. + if d.Retries > 0 { + out.Retries = d.Retries + } else if d.MaxRetries > 0 { + out.Retries = d.MaxRetries + } + + return out +} + +func convertActionSlice(actions []types.ZarfComponentAction) []v1beta1.ZarfComponentAction { + var out []v1beta1.ZarfComponentAction + for _, a := range actions { + out = append(out, convertAction(a)) + } + return out +} + +func convertAction(a types.ZarfComponentAction) v1beta1.ZarfComponentAction { + ba := v1beta1.ZarfComponentAction{ + Mute: a.Mute, + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: convertWait(a.Wait), + Template: a.Template, + Retries: a.Retries, + } + + // Prefer structured Duration if present, otherwise convert MaxTotalSeconds. + if a.Timeout != nil { + ba.Timeout = a.Timeout + } else if a.MaxTotalSeconds != nil { + dur := metav1.Duration{Duration: time.Duration(*a.MaxTotalSeconds) * time.Second} + ba.Timeout = &dur + } + + // Prefer Retries if already set, otherwise convert MaxRetries. + if ba.Retries == 0 && a.MaxRetries != nil { + ba.Retries = *a.MaxRetries + } + + for _, v := range a.SetVariables { + ba.SetVariables = append(ba.SetVariables, v1beta1.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: v1beta1.VariableType(v.Type), + }) + } + + // Fold DeprecatedSetVariable into SetVariables if it was set. + if a.DeprecatedSetVariable != "" { + ba.SetVariables = append(ba.SetVariables, v1beta1.Variable{ + Name: a.DeprecatedSetVariable, + }) + } + + for _, sv := range a.SetValues { + ba.SetValues = append(ba.SetValues, v1beta1.SetValue{ + Key: sv.Key, + Value: sv.Value, + Type: v1beta1.SetValueType(sv.Type), + }) + } + + if a.Shell != nil { + ba.Shell = &v1beta1.Shell{ + Windows: a.Shell.Windows, + Linux: a.Shell.Linux, + Darwin: a.Shell.Darwin, + } + } + + return ba +} + +func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionWait { + if w == nil { + return nil + } + bw := &v1beta1.ZarfComponentActionWait{} + if w.Cluster != nil { + bw.Cluster = &v1beta1.ZarfComponentActionWaitCluster{ + Kind: w.Cluster.Kind, + Name: w.Cluster.Name, + Namespace: w.Cluster.Namespace, + Condition: w.Cluster.Condition, + } + } + if w.Network != nil { + bw.Network = &v1beta1.ZarfComponentActionWaitNetwork{ + Protocol: w.Network.Protocol, + Address: w.Network.Address, + Code: w.Network.Code, + } + } + return bw +} + +func isGitURL(url string) bool { + return strings.HasSuffix(url, ".git") || + strings.Contains(url, "github.com") || + strings.Contains(url, "gitlab.com") +} + +func derefBoolOr(p *bool, def bool) bool { + if p != nil { + return *p + } + return def +} From 52b264ca018b0273362ea8e041fb28c31e016c7b Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 14:59:10 -0400 Subject: [PATCH 02/24] convert v1beta1 to v1alpha1 Signed-off-by: Austin Abro --- src/api/convert/convert.go | 6 + src/api/convert/convert_test.go | 535 +++++++++++++++++++++++++++ src/internal/api/v1alpha1/convert.go | 416 +++++++++++++++++++++ src/internal/api/v1beta1/convert.go | 306 +++++++++++++++ 4 files changed, 1263 insertions(+) diff --git a/src/api/convert/convert.go b/src/api/convert/convert.go index 8cde67b839..27b03730cf 100644 --- a/src/api/convert/convert.go +++ b/src/api/convert/convert.go @@ -16,3 +16,9 @@ func V1Alpha1PkgToV1Beta1(pkg v1alpha1.ZarfPackage) v1beta1.ZarfPackage { generic := internalv1alpha1.ConvertToGeneric(pkg) return internalv1beta1.ConvertFromGeneric(generic) } + +// V1Beta1PkgToV1Alpha1 converts a v1beta1 ZarfPackage to a v1alpha1 ZarfPackage. +func V1Beta1PkgToV1Alpha1(pkg v1beta1.ZarfPackage) v1alpha1.ZarfPackage { + generic := internalv1beta1.ConvertToGeneric(pkg) + return internalv1alpha1.ConvertFromGeneric(generic) +} diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index c6dec9fb3b..f7fae8fe7c 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { @@ -511,3 +512,537 @@ func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { chart := result.Components[0].Charts[0] assert.Equal(t, "1.2.3", chart.GetDeprecatedVersion()) } + +// --- v1beta1 → v1alpha1 tests --- + +func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { + t.Parallel() + pkg := v1beta1.ZarfPackage{ + APIVersion: v1beta1.APIVersion, + Kind: v1beta1.ZarfPackageConfig, + Metadata: v1beta1.ZarfMetadata{ + Name: "test-pkg", + Description: "A test package", + Version: "1.0.0", + Architecture: "amd64", + Uncompressed: true, + AllowNamespaceOverride: true, + Annotations: map[string]string{ + "existing": "annotation", + "metadata.url": "https://example.com", + "metadata.image": "https://example.com/image.png", + "metadata.authors": "Test Author", + "metadata.documentation": "https://docs.example.com", + "metadata.source": "https://github.com/example", + "metadata.vendor": "Example Corp", + }, + }, + Build: v1beta1.ZarfBuildData{ + AggregateChecksum: "abc123", + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + assert.Equal(t, v1alpha1.APIVersion, result.APIVersion) + assert.Equal(t, v1alpha1.ZarfPackageConfig, result.Kind) + assert.Equal(t, "test-pkg", result.Metadata.Name) + assert.Equal(t, "A test package", result.Metadata.Description) + assert.Equal(t, "1.0.0", result.Metadata.Version) + assert.Equal(t, "amd64", result.Metadata.Architecture) + assert.True(t, result.Metadata.Uncompressed) + require.NotNil(t, result.Metadata.AllowNamespaceOverride) + assert.True(t, *result.Metadata.AllowNamespaceOverride) + + // v1alpha1-only metadata fields should be restored from annotations. + assert.Equal(t, "https://example.com", result.Metadata.URL) + assert.Equal(t, "https://example.com/image.png", result.Metadata.Image) + assert.Equal(t, "Test Author", result.Metadata.Authors) + assert.Equal(t, "https://docs.example.com", result.Metadata.Documentation) + assert.Equal(t, "https://github.com/example", result.Metadata.Source) + assert.Equal(t, "Example Corp", result.Metadata.Vendor) + + // Metadata-specific annotations should be consumed, regular annotations preserved. + assert.Equal(t, "annotation", result.Metadata.Annotations["existing"]) + assert.Empty(t, result.Metadata.Annotations["metadata.url"]) + + // AggregateChecksum should move from build to metadata. + assert.Equal(t, "abc123", result.Metadata.AggregateChecksum) +} + +func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { + t.Parallel() + signed := true + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfInitConfig, + Build: v1beta1.ZarfBuildData{ + Terminal: "my-machine", + User: "test-user", + Architecture: "arm64", + Timestamp: "Mon, 02 Jan 2006 15:04:05 -0700", + Version: "v0.30.0", + Migrations: []string{"migration1"}, + RegistryOverrides: map[string]string{"docker.io": "internal.registry"}, + Differential: true, + DifferentialPackageVersion: "0.29.0", + Flavor: "vanilla", + Signed: &signed, + VersionRequirements: []v1beta1.VersionRequirement{ + {Version: "v0.28.0", Reason: "needs feature X"}, + }, + ProvenanceFiles: []string{"sig.json"}, + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + assert.Equal(t, v1alpha1.ZarfInitConfig, result.Kind) + assert.Equal(t, "my-machine", result.Build.Terminal) + assert.Equal(t, "test-user", result.Build.User) + assert.Equal(t, "arm64", result.Build.Architecture) + assert.Equal(t, "v0.30.0", result.Build.Version) + assert.True(t, result.Build.Differential) + assert.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) + assert.Equal(t, "vanilla", result.Build.Flavor) + require.NotNil(t, result.Build.Signed) + assert.True(t, *result.Build.Signed) + require.Len(t, result.Build.VersionRequirements, 1) + assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) +} + +func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { + t.Parallel() + optional := true + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfPackageConfig, + Components: []v1beta1.ZarfComponent{ + { + Name: "my-component", + Description: "test component", + Optional: &optional, + Only: v1beta1.ZarfComponentOnlyTarget{ + LocalOS: "linux", + Cluster: v1beta1.ZarfComponentOnlyCluster{ + Architecture: "amd64", + Distros: []string{"k3s"}, + }, + Flavor: "vanilla", + }, + Import: v1beta1.ZarfComponentImport{ + Path: "./path", + URL: "oci://example.com/pkg", + }, + Images: []v1beta1.ZarfImage{ + {Name: "nginx:latest"}, + {Name: "redis:7", Source: "daemon"}, + }, + Repos: []string{"https://github.com/example/repo"}, + }, + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + require.Len(t, result.Components, 1) + comp := result.Components[0] + assert.Equal(t, "my-component", comp.Name) + assert.Equal(t, "test component", comp.Description) + + // Optional=true → Required=false + require.NotNil(t, comp.Required) + assert.False(t, *comp.Required) + + assert.Equal(t, "linux", comp.Only.LocalOS) + assert.Equal(t, "amd64", comp.Only.Cluster.Architecture) + assert.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) + assert.Equal(t, "vanilla", comp.Only.Flavor) + + assert.Equal(t, "./path", comp.Import.Path) + assert.Equal(t, "oci://example.com/pkg", comp.Import.URL) + + // Images are converted from []ZarfImage to []string. + require.Len(t, comp.Images, 2) + assert.Equal(t, "nginx:latest", comp.Images[0]) + assert.Equal(t, "redis:7", comp.Images[1]) + + assert.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) +} + +func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { + t.Parallel() + tests := []struct { + name string + chart v1beta1.ZarfChart + validate func(t *testing.T, c v1alpha1.ZarfChart) + }{ + { + name: "helm repo", + chart: v1beta1.ZarfChart{ + Name: "podinfo", + HelmRepo: v1beta1.HelmRepoSource{ + URL: "https://stefanprodan.github.io/podinfo", + Name: "podinfo", + Version: "6.4.0", + }, + }, + validate: func(t *testing.T, c v1alpha1.ZarfChart) { + assert.Equal(t, "https://stefanprodan.github.io/podinfo", c.URL) + assert.Equal(t, "podinfo", c.RepoName) + assert.Equal(t, "6.4.0", c.Version) + }, + }, + { + name: "oci registry", + chart: v1beta1.ZarfChart{ + Name: "podinfo", + OCI: v1beta1.OCISource{ + URL: "oci://ghcr.io/stefanprodan/charts/podinfo", + Version: "6.4.0", + }, + }, + validate: func(t *testing.T, c v1alpha1.ZarfChart) { + assert.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.URL) + assert.Equal(t, "6.4.0", c.Version) + }, + }, + { + name: "git repo", + chart: v1beta1.ZarfChart{ + Name: "my-chart", + Git: v1beta1.GitRepoSource{ + URL: "https://github.com/example/repo", + Path: "charts/my-chart", + }, + }, + validate: func(t *testing.T, c v1alpha1.ZarfChart) { + assert.Equal(t, "https://github.com/example/repo", c.URL) + assert.Equal(t, "charts/my-chart", c.GitPath) + }, + }, + { + name: "local path", + chart: v1beta1.ZarfChart{ + Name: "local-chart", + Local: v1beta1.LocalRepoSource{ + Path: "./charts/my-chart", + }, + }, + validate: func(t *testing.T, c v1alpha1.ZarfChart) { + assert.Equal(t, "./charts/my-chart", c.LocalPath) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfPackageConfig, + Components: []v1beta1.ZarfComponent{ + { + Name: "chart-comp", + Charts: []v1beta1.ZarfChart{tt.chart}, + }, + }, + } + result := V1Beta1PkgToV1Alpha1(pkg) + require.Len(t, result.Components, 1) + require.Len(t, result.Components[0].Charts, 1) + tt.validate(t, result.Components[0].Charts[0]) + }) + } +} + +func TestV1Beta1PkgToV1Alpha1_ManifestWaitInversion(t *testing.T) { + t.Parallel() + waitFalse := false + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfPackageConfig, + Components: []v1beta1.ZarfComponent{ + { + Name: "manifest-comp", + Manifests: []v1beta1.ZarfManifest{ + { + Name: "wait-false", + Wait: &waitFalse, + }, + { + Name: "default-wait", + }, + }, + }, + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + require.Len(t, result.Components[0].Manifests, 2) + + // Wait=false → NoWait=true + assert.True(t, result.Components[0].Manifests[0].NoWait) + + // Wait=nil → NoWait=false (default) + assert.False(t, result.Components[0].Manifests[1].NoWait) +} + +func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { + t.Parallel() + mute := true + dir := "/tmp" + + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfPackageConfig, + Components: []v1beta1.ZarfComponent{ + { + Name: "action-comp", + Actions: v1beta1.ZarfComponentActions{ + OnDeploy: v1beta1.ZarfComponentActionSet{ + Defaults: v1beta1.ZarfComponentActionDefaults{ + Mute: true, + Timeout: &metav1.Duration{Duration: 60 * time.Second}, + Retries: 2, + Dir: "/work", + Env: []string{"FOO=bar"}, + Shell: v1beta1.Shell{ + Linux: "bash", + }, + }, + Before: []v1beta1.ZarfComponentAction{ + { + Cmd: "echo before", + Mute: &mute, + Timeout: &metav1.Duration{Duration: 30 * time.Second}, + Retries: 3, + Dir: &dir, + SetVariables: []v1beta1.Variable{ + {Name: "OUT_VAR", Sensitive: true}, + }, + }, + }, + After: []v1beta1.ZarfComponentAction{ + {Cmd: "echo after"}, + }, + OnFailure: []v1beta1.ZarfComponentAction{ + {Cmd: "echo failure"}, + }, + }, + }, + }, + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + require.Len(t, result.Components, 1) + actions := result.Components[0].Actions + + // Defaults + assert.True(t, actions.OnDeploy.Defaults.Mute) + assert.Equal(t, 60, actions.OnDeploy.Defaults.MaxTotalSeconds) + assert.Equal(t, 2, actions.OnDeploy.Defaults.MaxRetries) + assert.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) + assert.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) + assert.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) + + // Before action + require.Len(t, actions.OnDeploy.Before, 1) + before := actions.OnDeploy.Before[0] + assert.Equal(t, "echo before", before.Cmd) + require.NotNil(t, before.Mute) + assert.True(t, *before.Mute) + require.NotNil(t, before.MaxTotalSeconds) + assert.Equal(t, 30, *before.MaxTotalSeconds) + require.NotNil(t, before.MaxRetries) + assert.Equal(t, 3, *before.MaxRetries) + require.Len(t, before.SetVariables, 1) + assert.Equal(t, "OUT_VAR", before.SetVariables[0].Name) + assert.True(t, before.SetVariables[0].Sensitive) + + // After + require.Len(t, actions.OnDeploy.After, 1) + assert.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) + + // OnFailure + require.Len(t, actions.OnDeploy.OnFailure, 1) + assert.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) +} + +func TestV1Beta1PkgToV1Alpha1_Variables(t *testing.T) { + t.Parallel() + pkg := v1beta1.ZarfPackage{ + Kind: v1beta1.ZarfPackageConfig, + Variables: []v1beta1.InteractiveVariable{ + { + Variable: v1beta1.Variable{ + Name: "MY_VAR", + Sensitive: true, + AutoIndent: true, + Pattern: "^[a-z]+$", + Type: v1beta1.FileVariableType, + }, + Description: "A variable", + Default: "default-val", + Prompt: true, + }, + }, + Constants: []v1beta1.Constant{ + { + Name: "MY_CONST", + Value: "const-val", + Description: "A constant", + AutoIndent: true, + Pattern: ".*", + }, + }, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + require.Len(t, result.Variables, 1) + v := result.Variables[0] + assert.Equal(t, "MY_VAR", v.Name) + assert.True(t, v.Sensitive) + assert.True(t, v.AutoIndent) + assert.Equal(t, "^[a-z]+$", v.Pattern) + assert.Equal(t, v1alpha1.FileVariableType, v.Type) + assert.Equal(t, "A variable", v.Description) + assert.Equal(t, "default-val", v.Default) + assert.True(t, v.Prompt) + + require.Len(t, result.Constants, 1) + c := result.Constants[0] + assert.Equal(t, "MY_CONST", c.Name) + assert.Equal(t, "const-val", c.Value) +} + +func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { + t.Parallel() + required := true + allowOverride := true + mute := true + maxSeconds := 30 + maxRetries := 3 + dir := "/tmp" + + original := v1alpha1.ZarfPackage{ + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageConfig, + Metadata: v1alpha1.ZarfMetadata{ + Name: "round-trip-pkg", + Description: "round trip test", + Version: "2.0.0", + Architecture: "arm64", + URL: "https://example.com", + Authors: "Test Author", + AllowNamespaceOverride: &allowOverride, + }, + Build: v1alpha1.ZarfBuildData{ + Terminal: "my-machine", + Architecture: "arm64", + Timestamp: "Mon, 02 Jan 2006 15:04:05 -0700", + Version: "v0.30.0", + }, + Components: []v1alpha1.ZarfComponent{ + { + Name: "test-comp", + Required: &required, + Images: []string{"nginx:latest"}, + Repos: []string{"https://github.com/example/repo"}, + Charts: []v1alpha1.ZarfChart{ + { + Name: "my-chart", + URL: "https://charts.example.com", + RepoName: "my-chart", + Version: "1.2.3", + Namespace: "default", + }, + }, + Manifests: []v1alpha1.ZarfManifest{ + { + Name: "my-manifest", + Namespace: "default", + Files: []string{"manifest.yaml"}, + NoWait: true, + }, + }, + Actions: v1alpha1.ZarfComponentActions{ + OnDeploy: v1alpha1.ZarfComponentActionSet{ + Defaults: v1alpha1.ZarfComponentActionDefaults{ + MaxTotalSeconds: 60, + MaxRetries: 2, + }, + Before: []v1alpha1.ZarfComponentAction{ + { + Cmd: "echo before", + Mute: &mute, + MaxTotalSeconds: &maxSeconds, + MaxRetries: &maxRetries, + Dir: &dir, + }, + }, + }, + }, + DataInjections: []v1alpha1.ZarfDataInjection{ + {Source: "/data", Target: v1alpha1.ZarfContainerTarget{Namespace: "default", Selector: "app=test", Container: "main", Path: "/inject"}}, + }, + }, + }, + Constants: []v1alpha1.Constant{ + {Name: "MY_CONST", Value: "val"}, + }, + Variables: []v1alpha1.InteractiveVariable{ + { + Variable: v1alpha1.Variable{Name: "MY_VAR"}, + Description: "a var", + Default: "default", + }, + }, + } + + // Round-trip: v1alpha1 → v1beta1 → v1alpha1 + beta := V1Alpha1PkgToV1Beta1(original) + result := V1Beta1PkgToV1Alpha1(beta) + + assert.Equal(t, v1alpha1.APIVersion, result.APIVersion) + assert.Equal(t, original.Kind, result.Kind) + assert.Equal(t, original.Metadata.Name, result.Metadata.Name) + assert.Equal(t, original.Metadata.Description, result.Metadata.Description) + assert.Equal(t, original.Metadata.Version, result.Metadata.Version) + assert.Equal(t, original.Metadata.Architecture, result.Metadata.Architecture) + assert.Equal(t, original.Metadata.URL, result.Metadata.URL) + assert.Equal(t, original.Metadata.Authors, result.Metadata.Authors) + + require.Len(t, result.Components, 1) + comp := result.Components[0] + assert.Equal(t, "test-comp", comp.Name) + require.NotNil(t, comp.Required) + assert.True(t, *comp.Required) + assert.Equal(t, []string{"nginx:latest"}, comp.Images) + + // Chart should round-trip via structured source → flat fields. + require.Len(t, comp.Charts, 1) + assert.Equal(t, "https://charts.example.com", comp.Charts[0].URL) + assert.Equal(t, "my-chart", comp.Charts[0].RepoName) + assert.Equal(t, "1.2.3", comp.Charts[0].Version) + + // Manifest NoWait should survive round-trip. + require.Len(t, comp.Manifests, 1) + assert.True(t, comp.Manifests[0].NoWait) + + // Action timeouts should round-trip through Duration conversion. + assert.Equal(t, 60, comp.Actions.OnDeploy.Defaults.MaxTotalSeconds) + assert.Equal(t, 2, comp.Actions.OnDeploy.Defaults.MaxRetries) + require.Len(t, comp.Actions.OnDeploy.Before, 1) + require.NotNil(t, comp.Actions.OnDeploy.Before[0].MaxTotalSeconds) + assert.Equal(t, 30, *comp.Actions.OnDeploy.Before[0].MaxTotalSeconds) + + // DataInjections should survive round-trip via private shim. + require.Len(t, comp.DataInjections, 1) + assert.Equal(t, "/data", comp.DataInjections[0].Source) + + // Constants and variables. + require.Len(t, result.Constants, 1) + assert.Equal(t, "MY_CONST", result.Constants[0].Name) + require.Len(t, result.Variables, 1) + assert.Equal(t, "MY_VAR", result.Variables[0].Name) +} diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index 55685cd2d8..93263b0d2d 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -4,8 +4,11 @@ package v1alpha1 import ( + "math" + "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/internal/api/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ConvertToGeneric converts a v1alpha1 ZarfPackage to the internal generic representation. @@ -298,3 +301,416 @@ func derefIntOr(p *int, def int) int { } return def } + +// ConvertFromGeneric converts the internal generic representation to a v1alpha1 ZarfPackage. +func ConvertFromGeneric(g types.ZarfPackage) v1alpha1.ZarfPackage { + pkg := v1alpha1.ZarfPackage{ + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageKind(g.Kind), + Metadata: convertGenericToV1Alpha1Metadata(g.Metadata, g.Build), + Build: convertGenericToV1Alpha1Build(g.Build), + Values: v1alpha1.ZarfValues{ + Files: g.Values.Files, + Schema: g.Values.Schema, + }, + Documentation: g.Documentation, + } + + for _, c := range g.Constants { + pkg.Constants = append(pkg.Constants, v1alpha1.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + + for _, v := range g.Variables { + pkg.Variables = append(pkg.Variables, v1alpha1.InteractiveVariable{ + Variable: v1alpha1.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: v1alpha1.VariableType(v.Type), + }, + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + + for _, c := range g.Components { + pkg.Components = append(pkg.Components, convertGenericToV1Alpha1Component(c)) + } + + return pkg +} + +func convertGenericToV1Alpha1Metadata(m types.ZarfMetadata, b types.ZarfBuildData) v1alpha1.ZarfMetadata { + meta := v1alpha1.ZarfMetadata{ + Name: m.Name, + Description: m.Description, + Version: m.Version, + Uncompressed: m.Uncompressed, + Architecture: m.Architecture, + AllowNamespaceOverride: m.AllowNamespaceOverride, + URL: m.URL, + Image: m.Image, + YOLO: m.YOLO, + Authors: m.Authors, + Documentation: m.Documentation, + Source: m.Source, + Vendor: m.Vendor, + } + + // AggregateChecksum: prefer metadata (v1alpha1 native location), fall back to build (v1beta1 location). + if m.AggregateChecksum != "" { + meta.AggregateChecksum = m.AggregateChecksum + } else if b.AggregateChecksum != "" { + meta.AggregateChecksum = b.AggregateChecksum + } + + // Restore v1alpha1-only metadata fields from annotations if the generic fields are empty. + // This handles the case where data originated from v1beta1 and the fields were stored as annotations. + if m.Annotations != nil { + restore := map[string]*string{ + "metadata.url": &meta.URL, + "metadata.image": &meta.Image, + "metadata.authors": &meta.Authors, + "metadata.documentation": &meta.Documentation, + "metadata.source": &meta.Source, + "metadata.vendor": &meta.Vendor, + } + annotations := make(map[string]string) + for k, v := range m.Annotations { + if target, ok := restore[k]; ok { + if *target == "" { + *target = v + } + continue + } + annotations[k] = v + } + if len(annotations) > 0 { + meta.Annotations = annotations + } + } + + return meta +} + +func convertGenericToV1Alpha1Build(b types.ZarfBuildData) v1alpha1.ZarfBuildData { + out := v1alpha1.ZarfBuildData{ + Terminal: b.Terminal, + User: b.User, + Architecture: b.Architecture, + Timestamp: b.Timestamp, + Version: b.Version, + Migrations: b.Migrations, + RegistryOverrides: b.RegistryOverrides, + Differential: b.Differential, + DifferentialPackageVersion: b.DifferentialPackageVersion, + DifferentialMissing: b.DifferentialMissing, + Flavor: b.Flavor, + Signed: b.Signed, + ProvenanceFiles: b.ProvenanceFiles, + } + + for _, vr := range b.VersionRequirements { + out.VersionRequirements = append(out.VersionRequirements, v1alpha1.VersionRequirement{ + Version: vr.Version, + Reason: vr.Reason, + }) + } + + return out +} + +func convertGenericToV1Alpha1Component(c types.ZarfComponent) v1alpha1.ZarfComponent { + gc := v1alpha1.ZarfComponent{ + Name: c.Name, + Description: c.Description, + Default: c.Default, + Required: convertOptionalToRequired(c.Optional, c.Required), + DeprecatedGroup: c.Group, + DataInjections: c.DataInjections, + HealthChecks: c.HealthChecks, + Repos: c.Repos, + Only: v1alpha1.ZarfComponentOnlyTarget{ + LocalOS: c.Only.LocalOS, + Cluster: v1alpha1.ZarfComponentOnlyCluster{ + Architecture: c.Only.Cluster.Architecture, + Distros: c.Only.Cluster.Distros, + }, + Flavor: c.Only.Flavor, + }, + Import: v1alpha1.ZarfComponentImport{ + Name: c.Import.Name, + Path: c.Import.Path, + URL: c.Import.URL, + }, + Actions: convertGenericToV1Alpha1Actions(c.Actions), + } + + for _, m := range c.Manifests { + gc.Manifests = append(gc.Manifests, convertGenericToV1Alpha1Manifest(m)) + } + + for _, ch := range c.Charts { + gc.Charts = append(gc.Charts, convertGenericToV1Alpha1Chart(ch)) + } + + for _, f := range c.Files { + gc.Files = append(gc.Files, v1alpha1.ZarfFile{ + Source: f.Source, + Shasum: f.Shasum, + Target: f.Target, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + Template: f.Template, + }) + } + + for _, img := range c.Images { + gc.Images = append(gc.Images, img.Name) + } + + for _, ia := range c.ImageArchives { + gc.ImageArchives = append(gc.ImageArchives, v1alpha1.ImageArchive{ + Path: ia.Path, + Images: ia.Images, + }) + } + + return gc +} + +// convertOptionalToRequired maps back from the generic superset to v1alpha1 Required. +// If the original Required is preserved (from a v1alpha1 origin), use it directly. +// If only Optional is set (from a v1beta1 origin), invert it. +func convertOptionalToRequired(optional *bool, required *bool) *bool { + if required != nil { + return required + } + if optional == nil { + return nil + } + inverted := !*optional + return &inverted +} + +func convertGenericToV1Alpha1Manifest(m types.ZarfManifest) v1alpha1.ZarfManifest { + am := v1alpha1.ZarfManifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, + Kustomizations: m.Kustomizations, + ServerSideApply: m.ServerSideApply, + Template: m.Template, + NoWait: m.NoWait, + EnableKustomizePlugins: m.EnableKustomizePlugins, + } + + // Invert Wait → NoWait if Wait was explicitly set and NoWait wasn't already. + if !am.NoWait && m.Wait != nil { + am.NoWait = !*m.Wait + } + + return am +} + +func convertGenericToV1Alpha1Chart(ch types.ZarfChart) v1alpha1.ZarfChart { + ac := v1alpha1.ZarfChart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SchemaValidation: ch.SchemaValidation, + ServerSideApply: ch.ServerSideApply, + NoWait: ch.NoWait, + URL: ch.URL, + RepoName: ch.RepoName, + GitPath: ch.GitPath, + LocalPath: ch.LocalPath, + Version: ch.Version, + } + + // Invert Wait → NoWait if Wait was explicitly set and NoWait wasn't already. + if !ac.NoWait && ch.Wait != nil { + ac.NoWait = !*ch.Wait + } + + // If flat fields are empty but structured sources are populated, convert back to flat. + if ac.URL == "" && ac.LocalPath == "" { + switch { + case ch.HelmRepo.URL != "": + ac.URL = ch.HelmRepo.URL + ac.RepoName = ch.HelmRepo.Name + if ac.Version == "" { + ac.Version = ch.HelmRepo.Version + } + case ch.OCI.URL != "": + ac.URL = ch.OCI.URL + if ac.Version == "" { + ac.Version = ch.OCI.Version + } + case ch.Git.URL != "": + ac.URL = ch.Git.URL + ac.GitPath = ch.Git.Path + case ch.Local.Path != "": + ac.LocalPath = ch.Local.Path + } + } + + for _, v := range ch.Values { + ac.Values = append(ac.Values, v1alpha1.ZarfChartValue{ + SourcePath: v.SourcePath, + TargetPath: v.TargetPath, + }) + } + + return ac +} + +func convertGenericToV1Alpha1Actions(a types.ZarfComponentActions) v1alpha1.ZarfComponentActions { + return v1alpha1.ZarfComponentActions{ + OnCreate: convertGenericToV1Alpha1ActionSet(a.OnCreate), + OnDeploy: convertGenericToV1Alpha1ActionSet(a.OnDeploy), + OnRemove: convertGenericToV1Alpha1ActionSet(a.OnRemove), + } +} + +func convertGenericToV1Alpha1ActionSet(s types.ZarfComponentActionSet) v1alpha1.ZarfComponentActionSet { + return v1alpha1.ZarfComponentActionSet{ + Defaults: v1alpha1.ZarfComponentActionDefaults{ + Mute: s.Defaults.Mute, + MaxTotalSeconds: convertMaxTotalSeconds(s.Defaults.MaxTotalSeconds, s.Defaults.Timeout), + MaxRetries: convertMaxRetries(s.Defaults.MaxRetries, s.Defaults.Retries), + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: v1alpha1.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, + }, + }, + Before: convertGenericToV1Alpha1ActionSlice(s.Before), + After: convertGenericToV1Alpha1ActionSlice(s.After), + OnSuccess: convertGenericToV1Alpha1ActionSlice(s.OnSuccess), + OnFailure: convertGenericToV1Alpha1ActionSlice(s.OnFailure), + } +} + +// convertMaxTotalSeconds returns the v1alpha1 MaxTotalSeconds value. +// Prefers the preserved v1alpha1 value; falls back to converting the Duration. +func convertMaxTotalSeconds(v1alpha1Val int, timeout *metav1.Duration) int { + if v1alpha1Val > 0 { + return v1alpha1Val + } + if timeout != nil { + secs := timeout.Duration.Seconds() + if secs > math.MaxInt32 { + return math.MaxInt32 + } + return int(secs) + } + return 0 +} + +// convertMaxRetries returns the v1alpha1 MaxRetries value. +// Prefers the preserved v1alpha1 value; falls back to the generic Retries. +func convertMaxRetries(v1alpha1Val int, retries int) int { + if v1alpha1Val > 0 { + return v1alpha1Val + } + return retries +} + +func convertGenericToV1Alpha1ActionSlice(actions []types.ZarfComponentAction) []v1alpha1.ZarfComponentAction { + var out []v1alpha1.ZarfComponentAction + for _, a := range actions { + out = append(out, convertGenericToV1Alpha1Action(a)) + } + return out +} + +func convertGenericToV1Alpha1Action(a types.ZarfComponentAction) v1alpha1.ZarfComponentAction { + ga := v1alpha1.ZarfComponentAction{ + Mute: a.Mute, + MaxTotalSeconds: a.MaxTotalSeconds, + MaxRetries: a.MaxRetries, + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: convertGenericToV1Alpha1Wait(a.Wait), + Template: a.Template, + DeprecatedSetVariable: a.DeprecatedSetVariable, + } + + // If preserved v1alpha1 MaxTotalSeconds is nil but we have a Duration, convert it. + if ga.MaxTotalSeconds == nil && a.Timeout != nil { + secs := int(a.Timeout.Duration.Seconds()) + ga.MaxTotalSeconds = &secs + } + + // If preserved v1alpha1 MaxRetries is nil but we have Retries, convert it. + if ga.MaxRetries == nil && a.Retries > 0 { + ga.MaxRetries = &a.Retries + } + + for _, v := range a.SetVariables { + ga.SetVariables = append(ga.SetVariables, v1alpha1.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: v1alpha1.VariableType(v.Type), + }) + } + + for _, sv := range a.SetValues { + ga.SetValues = append(ga.SetValues, v1alpha1.SetValue{ + Key: sv.Key, + Value: sv.Value, + Type: v1alpha1.SetValueType(sv.Type), + }) + } + + if a.Shell != nil { + ga.Shell = &v1alpha1.Shell{ + Windows: a.Shell.Windows, + Linux: a.Shell.Linux, + Darwin: a.Shell.Darwin, + } + } + + return ga +} + +func convertGenericToV1Alpha1Wait(w *types.ZarfComponentActionWait) *v1alpha1.ZarfComponentActionWait { + if w == nil { + return nil + } + aw := &v1alpha1.ZarfComponentActionWait{} + if w.Cluster != nil { + aw.Cluster = &v1alpha1.ZarfComponentActionWaitCluster{ + Kind: w.Cluster.Kind, + Name: w.Cluster.Name, + Namespace: w.Cluster.Namespace, + Condition: w.Cluster.Condition, + } + } + if w.Network != nil { + aw.Network = &v1alpha1.ZarfComponentActionWaitNetwork{ + Protocol: w.Network.Protocol, + Address: w.Network.Address, + Code: w.Network.Code, + } + } + return aw +} diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 8191ffabd9..cd75382a05 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -13,6 +13,312 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// ConvertToGeneric converts a v1beta1 ZarfPackage to the internal generic representation. +func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { + g := types.ZarfPackage{ + APIVersion: pkg.APIVersion, + Kind: string(pkg.Kind), + Metadata: types.ZarfMetadata{ + Name: pkg.Metadata.Name, + Description: pkg.Metadata.Description, + Version: pkg.Metadata.Version, + Uncompressed: pkg.Metadata.Uncompressed, + Architecture: pkg.Metadata.Architecture, + Annotations: pkg.Metadata.Annotations, + }, + Build: types.ZarfBuildData{ + Terminal: pkg.Build.Terminal, + User: pkg.Build.User, + Architecture: pkg.Build.Architecture, + Timestamp: pkg.Build.Timestamp, + Version: pkg.Build.Version, + Migrations: pkg.Build.Migrations, + RegistryOverrides: pkg.Build.RegistryOverrides, + Differential: pkg.Build.Differential, + DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, + Flavor: pkg.Build.Flavor, + Signed: pkg.Build.Signed, + AggregateChecksum: pkg.Build.AggregateChecksum, + ProvenanceFiles: pkg.Build.ProvenanceFiles, + }, + Values: types.ZarfValues{ + Files: pkg.Values.Files, + Schema: pkg.Values.Schema, + }, + Documentation: pkg.Documentation, + } + + // v1beta1 AllowNamespaceOverride is a plain bool; store as *bool on generic. + if pkg.Metadata.AllowNamespaceOverride { + t := true + g.Metadata.AllowNamespaceOverride = &t + } + + for _, vr := range pkg.Build.VersionRequirements { + g.Build.VersionRequirements = append(g.Build.VersionRequirements, types.VersionRequirement{ + Version: vr.Version, + Reason: vr.Reason, + }) + } + + for _, c := range pkg.Constants { + g.Constants = append(g.Constants, types.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + + for _, v := range pkg.Variables { + g.Variables = append(g.Variables, types.InteractiveVariable{ + Variable: types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: string(v.Type), + }, + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + + for _, c := range pkg.Components { + g.Components = append(g.Components, convertV1Beta1Component(c)) + } + + return g +} + +func convertV1Beta1Component(c v1beta1.ZarfComponent) types.ZarfComponent { + gc := types.ZarfComponent{ + Name: c.Name, + Description: c.Description, + Optional: c.Optional, + DataInjections: c.GetDataInjections(), + Repos: c.Repos, + Only: types.ZarfComponentOnlyTarget{ + LocalOS: c.Only.LocalOS, + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: c.Only.Cluster.Architecture, + Distros: c.Only.Cluster.Distros, + }, + Flavor: c.Only.Flavor, + }, + Import: types.ZarfComponentImport{ + Path: c.Import.Path, + URL: c.Import.URL, + }, + Features: types.ZarfComponentFeatures{ + IsRegistry: c.Features.IsRegistry, + IsAgent: c.Features.IsAgent, + }, + Actions: convertV1Beta1Actions(c.Actions), + } + + if c.Features.Injector != nil { + gc.Features.Injector = &types.Injector{ + Enabled: c.Features.Injector.Enabled, + } + if c.Features.Injector.Values != nil { + gc.Features.Injector.Values = &types.InjectorValues{ + Tolerations: c.Features.Injector.Values.Tolerations, + } + } + } + + for _, m := range c.Manifests { + gc.Manifests = append(gc.Manifests, convertV1Beta1Manifest(m)) + } + + for _, ch := range c.Charts { + gc.Charts = append(gc.Charts, convertV1Beta1Chart(ch)) + } + + for _, f := range c.Files { + gc.Files = append(gc.Files, types.ZarfFile{ + Source: f.Source, + Shasum: f.Shasum, + Target: f.Target, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + Template: f.Template, + }) + } + + for _, img := range c.Images { + gc.Images = append(gc.Images, types.ZarfImage{ + Name: img.Name, + Source: img.Source, + }) + } + + for _, ia := range c.ImageArchives { + gc.ImageArchives = append(gc.ImageArchives, types.ImageArchive{ + Path: ia.Path, + Images: ia.Images, + }) + } + + return gc +} + +func convertV1Beta1Manifest(m v1beta1.ZarfManifest) types.ZarfManifest { + return types.ZarfManifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, + Kustomizations: m.Kustomizations, + ServerSideApply: m.ServerSideApply, + Template: m.Template, + Wait: m.Wait, + } +} + +func convertV1Beta1Chart(ch v1beta1.ZarfChart) types.ZarfChart { + gc := types.ZarfChart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SchemaValidation: ch.SchemaValidation, + ServerSideApply: ch.ServerSideApply, + Wait: ch.Wait, + HelmRepo: types.HelmRepoSource{ + Name: ch.HelmRepo.Name, + URL: ch.HelmRepo.URL, + Version: ch.HelmRepo.Version, + }, + Git: types.GitRepoSource{ + URL: ch.Git.URL, + Path: ch.Git.Path, + }, + Local: types.LocalRepoSource{ + Path: ch.Local.Path, + }, + OCI: types.OCISource{ + URL: ch.OCI.URL, + Version: ch.OCI.Version, + }, + Version: ch.GetDeprecatedVersion(), + } + + for _, v := range ch.Values { + gc.Values = append(gc.Values, types.ZarfChartValue{ + SourcePath: v.SourcePath, + TargetPath: v.TargetPath, + }) + } + + return gc +} + +func convertV1Beta1Actions(a v1beta1.ZarfComponentActions) types.ZarfComponentActions { + return types.ZarfComponentActions{ + OnCreate: convertV1Beta1ActionSet(a.OnCreate), + OnDeploy: convertV1Beta1ActionSet(a.OnDeploy), + OnRemove: convertV1Beta1ActionSet(a.OnRemove), + } +} + +func convertV1Beta1ActionSet(s v1beta1.ZarfComponentActionSet) types.ZarfComponentActionSet { + return types.ZarfComponentActionSet{ + Defaults: types.ZarfComponentActionDefaults{ + Mute: s.Defaults.Mute, + Timeout: s.Defaults.Timeout, + Retries: s.Defaults.Retries, + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: types.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, + }, + }, + Before: convertV1Beta1ActionSlice(s.Before), + After: convertV1Beta1ActionSlice(s.After), + OnFailure: convertV1Beta1ActionSlice(s.OnFailure), + } +} + +func convertV1Beta1ActionSlice(actions []v1beta1.ZarfComponentAction) []types.ZarfComponentAction { + var out []types.ZarfComponentAction + for _, a := range actions { + out = append(out, convertV1Beta1Action(a)) + } + return out +} + +func convertV1Beta1Action(a v1beta1.ZarfComponentAction) types.ZarfComponentAction { + ga := types.ZarfComponentAction{ + Mute: a.Mute, + Timeout: a.Timeout, + Retries: a.Retries, + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: convertV1Beta1Wait(a.Wait), + Template: a.Template, + } + + for _, v := range a.SetVariables { + ga.SetVariables = append(ga.SetVariables, types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: string(v.Type), + }) + } + + for _, sv := range a.SetValues { + ga.SetValues = append(ga.SetValues, types.SetValue{ + Key: sv.Key, + Value: sv.Value, + Type: string(sv.Type), + }) + } + + if a.Shell != nil { + ga.Shell = &types.Shell{ + Windows: a.Shell.Windows, + Linux: a.Shell.Linux, + Darwin: a.Shell.Darwin, + } + } + + return ga +} + +func convertV1Beta1Wait(w *v1beta1.ZarfComponentActionWait) *types.ZarfComponentActionWait { + if w == nil { + return nil + } + gw := &types.ZarfComponentActionWait{} + if w.Cluster != nil { + gw.Cluster = &types.ZarfComponentActionWaitCluster{ + Kind: w.Cluster.Kind, + Name: w.Cluster.Name, + Namespace: w.Cluster.Namespace, + Condition: w.Cluster.Condition, + } + } + if w.Network != nil { + gw.Network = &types.ZarfComponentActionWaitNetwork{ + Protocol: w.Network.Protocol, + Address: w.Network.Address, + Code: w.Network.Code, + } + } + return gw +} + // ConvertFromGeneric converts the internal generic representation to a v1beta1 ZarfPackage. func ConvertFromGeneric(g types.ZarfPackage) v1beta1.ZarfPackage { pkg := v1beta1.ZarfPackage{ From 2d838b08edab8af7ff52f3c27f33ed829c47d006 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 15:00:59 -0400 Subject: [PATCH 03/24] is git url Signed-off-by: Austin Abro --- src/internal/api/v1beta1/convert.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index cd75382a05..bf726c9103 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -780,9 +780,7 @@ func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionW } func isGitURL(url string) bool { - return strings.HasSuffix(url, ".git") || - strings.Contains(url, "github.com") || - strings.Contains(url, "gitlab.com") + return strings.HasSuffix(url, ".git") } func derefBoolOr(p *bool, def bool) bool { From e7bb8a3602a50947b407038c01f66a937eb9cf8d Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 15:18:11 -0400 Subject: [PATCH 04/24] features Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 39 ++++++++++++++++++++++++++++ src/internal/api/v1alpha1/convert.go | 19 ++++++++++++++ src/internal/api/v1beta1/convert.go | 38 +++++++++++++++------------ 3 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index f7fae8fe7c..8003a707b3 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -227,6 +227,45 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { assert.Equal(t, "/data", comp.GetDataInjections()[0].Source) } +func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { + t.Parallel() + tests := []struct { + name string + compName string + isRegistry bool + isAgent bool + injector bool + }{ + {"zarf-registry", "zarf-registry", true, false, false}, + {"zarf-injector", "zarf-injector", true, false, false}, + {"zarf-seed-registry", "zarf-seed-registry", true, false, true}, + {"zarf-agent", "zarf-agent", false, true, false}, + {"regular component", "my-app", false, false, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Components: []v1alpha1.ZarfComponent{ + {Name: tt.compName}, + }, + } + result := V1Alpha1PkgToV1Beta1(pkg) + require.Len(t, result.Components, 1) + comp := result.Components[0] + assert.Equal(t, tt.isRegistry, comp.Features.IsRegistry, "IsRegistry") + assert.Equal(t, tt.isAgent, comp.Features.IsAgent, "IsAgent") + if tt.injector { + require.NotNil(t, comp.Features.Injector) + assert.True(t, comp.Features.Injector.Enabled) + } else { + assert.Nil(t, comp.Features.Injector) + } + }) + } +} + func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { t.Parallel() tests := []struct { diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index 93263b0d2d..f9de98e33c 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -104,6 +104,7 @@ func convertV1Alpha1Component(c v1alpha1.ZarfComponent) types.ZarfComponent { DataInjections: c.DataInjections, HealthChecks: c.HealthChecks, Repos: c.Repos, + Features: inferFeaturesFromName(c.Name), Only: types.ZarfComponentOnlyTarget{ LocalOS: c.Only.LocalOS, Cluster: types.ZarfComponentOnlyCluster{ @@ -295,6 +296,24 @@ func convertV1Alpha1Wait(w *v1alpha1.ZarfComponentActionWait) *types.ZarfCompone return gw } +// inferFeaturesFromName infers v1beta1-style Features from v1alpha1 component names. +// v1alpha1 has no Features field; these well-known component names encode the same semantics. +func inferFeaturesFromName(name string) types.ZarfComponentFeatures { + switch name { + case "zarf-registry", "zarf-injector": + return types.ZarfComponentFeatures{IsRegistry: true} + case "zarf-seed-registry": + return types.ZarfComponentFeatures{ + IsRegistry: true, + Injector: &types.Injector{Enabled: true}, + } + case "zarf-agent": + return types.ZarfComponentFeatures{IsAgent: true} + default: + return types.ZarfComponentFeatures{} + } +} + func derefIntOr(p *int, def int) int { if p != nil { return *p diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index bf726c9103..800820fabe 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -448,23 +448,9 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { Path: c.Import.Path, URL: c.Import.URL, }, - Features: v1beta1.ZarfComponentFeatures{ - IsRegistry: c.Features.IsRegistry, - IsAgent: c.Features.IsAgent, - }, - Repos: c.Repos, - Actions: convertActions(c.Actions), - } - - if c.Features.Injector != nil { - gc.Features.Injector = &v1beta1.Injector{ - Enabled: c.Features.Injector.Enabled, - } - if c.Features.Injector.Values != nil { - gc.Features.Injector.Values = &v1beta1.InjectorValues{ - Tolerations: c.Features.Injector.Values.Tolerations, - } - } + Features: convertFeatures(c.Features), + Repos: c.Repos, + Actions: convertActions(c.Actions), } for _, m := range c.Manifests { @@ -507,6 +493,24 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { return gc } +func convertFeatures(f types.ZarfComponentFeatures) v1beta1.ZarfComponentFeatures { + out := v1beta1.ZarfComponentFeatures{ + IsRegistry: f.IsRegistry, + IsAgent: f.IsAgent, + } + if f.Injector != nil { + out.Injector = &v1beta1.Injector{ + Enabled: f.Injector.Enabled, + } + if f.Injector.Values != nil { + out.Injector.Values = &v1beta1.InjectorValues{ + Tolerations: f.Injector.Values.Tolerations, + } + } + } + return out +} + // convertRequiredToOptional inverts the v1alpha1 Required *bool into v1beta1 Optional *bool. // v1alpha1 Required=nil → not required → Optional=nil (default false in v1beta1 means required) // Wait — v1alpha1 Required=nil means "not required" but v1beta1 Optional=nil means "not optional" (required). From 107d5f5ee8a2c30c97a72141a9eb49c6c269dd7d Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 15:19:09 -0400 Subject: [PATCH 05/24] spell out names Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 8003a707b3..b167d12e85 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -236,11 +236,11 @@ func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { isAgent bool injector bool }{ - {"zarf-registry", "zarf-registry", true, false, false}, - {"zarf-injector", "zarf-injector", true, false, false}, - {"zarf-seed-registry", "zarf-seed-registry", true, false, true}, - {"zarf-agent", "zarf-agent", false, true, false}, - {"regular component", "my-app", false, false, false}, + {name: "zarf-registry", compName: "zarf-registry", isRegistry: true, isAgent: false, injector: false}, + {name: "zarf-injector", compName: "zarf-injector", isRegistry: true, isAgent: false, injector: false}, + {name: "zarf-seed-registry", compName: "zarf-seed-registry", isRegistry: true, isAgent: false, injector: true}, + {name: "zarf-agent", compName: "zarf-agent", isRegistry: false, isAgent: true, injector: false}, + {name: "regular component", compName: "my-app", isRegistry: false, isAgent: false, injector: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 537e1ec471f0732d61100db04086689ca06f6f1c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 15:36:24 -0400 Subject: [PATCH 06/24] convert corrections Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 46 ++++++++++++++++++++++++++-- src/internal/api/types/package.go | 20 +++++++++++- src/internal/api/v1alpha1/convert.go | 10 +++++- src/internal/api/v1beta1/convert.go | 17 ++++++++-- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index b167d12e85..4b1c6849dc 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -300,7 +300,20 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { }, }, { - name: "git repo", + name: "git repo with version", + chart: v1alpha1.ZarfChart{ + Name: "my-chart", + URL: "https://github.com/example/repo", + GitPath: "charts/my-chart", + Version: "6.4.0", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + assert.Equal(t, "https://github.com/example/repo@6.4.0", c.Git.URL) + assert.Equal(t, "charts/my-chart", c.Git.Path) + }, + }, + { + name: "git repo without version", chart: v1alpha1.ZarfChart{ Name: "my-chart", URL: "https://github.com/example/repo", @@ -311,6 +324,20 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { assert.Equal(t, "charts/my-chart", c.Git.Path) }, }, + { + name: "git repo with ref already in url", + chart: v1alpha1.ZarfChart{ + Name: "my-chart", + URL: "https://github.com/example/repo.git@v2.0.0", + GitPath: "charts/my-chart", + Version: "6.4.0", + }, + validate: func(t *testing.T, c v1beta1.ZarfChart) { + // URL already has @ref, should not double-append version. + assert.Equal(t, "https://github.com/example/repo.git@v2.0.0", c.Git.URL) + assert.Equal(t, "charts/my-chart", c.Git.Path) + }, + }, { name: "local path", chart: v1alpha1.ZarfChart{ @@ -745,7 +772,22 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, { - name: "git repo", + name: "git repo with version in url", + chart: v1beta1.ZarfChart{ + Name: "my-chart", + Git: v1beta1.GitRepoSource{ + URL: "https://github.com/example/repo@6.4.0", + Path: "charts/my-chart", + }, + }, + validate: func(t *testing.T, c v1alpha1.ZarfChart) { + assert.Equal(t, "https://github.com/example/repo", c.URL) + assert.Equal(t, "charts/my-chart", c.GitPath) + assert.Equal(t, "6.4.0", c.Version) + }, + }, + { + name: "git repo without version", chart: v1beta1.ZarfChart{ Name: "my-chart", Git: v1beta1.GitRepoSource{ diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go index 44c5eefedb..bab649c3e6 100644 --- a/src/internal/api/types/package.go +++ b/src/internal/api/types/package.go @@ -174,10 +174,28 @@ type ZarfFile struct { Template *bool } +// ImageSource represents where an image is pulled from. +type ImageSource string + +const ( + // ImageSourceRegistry pulls from an OCI registry. + ImageSourceRegistry ImageSource = "registry" + // ImageSourceDaemon pulls from the local Docker daemon. + ImageSourceDaemon ImageSource = "daemon" +) + // ZarfImage represents an OCI image. type ZarfImage struct { Name string - Source string + Source ImageSource +} + +// GetSource returns the image source, defaulting to ImageSourceRegistry. +func (img ZarfImage) GetSource() ImageSource { + if img.Source == "" { + return ImageSourceRegistry + } + return img.Source } // ImageArchive defines a tar archive of images. diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index f9de98e33c..876db1889c 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -5,6 +5,7 @@ package v1alpha1 import ( "math" + "strings" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/internal/api/types" @@ -578,7 +579,14 @@ func convertGenericToV1Alpha1Chart(ch types.ZarfChart) v1alpha1.ZarfChart { ac.Version = ch.OCI.Version } case ch.Git.URL != "": - ac.URL = ch.Git.URL + gitURL := ch.Git.URL + if idx := strings.LastIndex(gitURL, "@"); idx > 0 { + if ac.Version == "" { + ac.Version = gitURL[idx+1:] + } + gitURL = gitURL[:idx] + } + ac.URL = gitURL ac.GitPath = ch.Git.Path case ch.Local.Path != "": ac.LocalPath = ch.Local.Path diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 800820fabe..09b89b3ef1 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -153,7 +153,7 @@ func convertV1Beta1Component(c v1beta1.ZarfComponent) types.ZarfComponent { for _, img := range c.Images { gc.Images = append(gc.Images, types.ZarfImage{ Name: img.Name, - Source: img.Source, + Source: types.ImageSource(img.Source), }) } @@ -476,7 +476,7 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { for _, img := range c.Images { gc.Images = append(gc.Images, v1beta1.ZarfImage{ Name: img.Name, - Source: img.Source, + Source: string(img.Source), }) } @@ -604,8 +604,14 @@ func convertChart(ch types.ZarfChart) v1beta1.ZarfChart { Version: ch.Version, } case ch.GitPath != "" || isGitURL(ch.URL): + gitURL := ch.URL + // Only append @version if the URL doesn't already contain a ref, + // mirroring the runtime behavior in packager/helm/repo.go. + if ch.Version != "" && !strings.Contains(ch.URL, "@") { + gitURL += "@" + ch.Version + } bc.Git = v1beta1.GitRepoSource{ - URL: ch.URL, + URL: gitURL, Path: ch.GitPath, } default: @@ -784,6 +790,11 @@ func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionW } func isGitURL(url string) bool { + // Strip any @ref suffix before checking, matching the runtime behavior + // in packager/helm/repo.go which uses transform.GitURLSplitRef. + if idx := strings.LastIndex(url, "@"); idx > 0 { + url = url[:idx] + } return strings.HasSuffix(url, ".git") } From 86458e7556200414b2ed11d8c7c334437fa7fa30 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 13 Mar 2026 15:50:49 -0400 Subject: [PATCH 07/24] temporary convert command Signed-off-by: Austin Abro --- src/cmd/internal.go | 1 + src/cmd/internal_convert.go | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 src/cmd/internal_convert.go diff --git a/src/cmd/internal.go b/src/cmd/internal.go index 5401f74f8c..fb6ea2844b 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -39,6 +39,7 @@ func newInternalCommand(rootCmd *cobra.Command) *cobra.Command { cmd.AddCommand(newInternalUpdateGiteaPVCCommand()) cmd.AddCommand(newInternalIsValidHostnameCommand()) cmd.AddCommand(newInternalCrc32Command()) + cmd.AddCommand(newInternalConvertCommand()) return cmd } diff --git a/src/cmd/internal_convert.go b/src/cmd/internal_convert.go new file mode 100644 index 0000000000..dda8aada3c --- /dev/null +++ b/src/cmd/internal_convert.go @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + goyaml "github.com/goccy/go-yaml" + "github.com/spf13/cobra" + "github.com/zarf-dev/zarf/src/api/convert" + "github.com/zarf-dev/zarf/src/api/v1alpha1" +) + +type internalConvertOptions struct{} + +// FIXME: temporary internal convert command +func newInternalConvertCommand() *cobra.Command { + o := &internalConvertOptions{} + + cmd := &cobra.Command{ + Use: "convert [directory]", + Short: "Convert a v1alpha1 zarf.yaml to v1beta1", + Args: cobra.ExactArgs(1), + RunE: o.run, + } + + return cmd +} + +func (o *internalConvertOptions) run(_ *cobra.Command, args []string) error { + dir := args[0] + + inputPath := filepath.Join(dir, "zarf.yaml") + b, err := os.ReadFile(inputPath) + if err != nil { + return fmt.Errorf("reading %s: %w", inputPath, err) + } + + var pkg v1alpha1.ZarfPackage + if err := goyaml.Unmarshal(b, &pkg); err != nil { + return fmt.Errorf("parsing %s: %w", inputPath, err) + } + + result := convert.V1Alpha1PkgToV1Beta1(pkg) + + out, err := goyaml.Marshal(result) + if err != nil { + return fmt.Errorf("marshaling v1beta1 package: %w", err) + } + + outputPath := filepath.Join(dir, "zarf-v1beta1.yaml") + if err := os.WriteFile(outputPath, out, 0644); err != nil { + return fmt.Errorf("writing %s: %w", outputPath, err) + } + + fmt.Printf("Converted %s -> %s\n", inputPath, outputPath) + return nil +} From e888fa75d04005728845a9403e3c0fa2ba803917 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 09:31:18 -0400 Subject: [PATCH 08/24] allow namespace override Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 6 ++++-- src/internal/api/v1beta1/convert.go | 15 ++------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 4b1c6849dc..740ae440df 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -49,7 +49,8 @@ func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { assert.Equal(t, "1.0.0", result.Metadata.Version) assert.Equal(t, "amd64", result.Metadata.Architecture) assert.True(t, result.Metadata.Uncompressed) - assert.True(t, result.Metadata.AllowNamespaceOverride) + require.NotNil(t, result.Metadata.AllowNamespaceOverride) + assert.True(t, *result.Metadata.AllowNamespaceOverride) // v1alpha1-only metadata fields should be migrated to annotations. assert.Equal(t, "https://example.com", result.Metadata.Annotations["metadata.url"]) @@ -583,6 +584,7 @@ func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { t.Parallel() + allowOverride := true pkg := v1beta1.ZarfPackage{ APIVersion: v1beta1.APIVersion, Kind: v1beta1.ZarfPackageConfig, @@ -592,7 +594,7 @@ func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { Version: "1.0.0", Architecture: "amd64", Uncompressed: true, - AllowNamespaceOverride: true, + AllowNamespaceOverride: &allowOverride, Annotations: map[string]string{ "existing": "annotation", "metadata.url": "https://example.com", diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 09b89b3ef1..6e90c189fd 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -48,11 +48,7 @@ func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { Documentation: pkg.Documentation, } - // v1beta1 AllowNamespaceOverride is a plain bool; store as *bool on generic. - if pkg.Metadata.AllowNamespaceOverride { - t := true - g.Metadata.AllowNamespaceOverride = &t - } + g.Metadata.AllowNamespaceOverride = pkg.Metadata.AllowNamespaceOverride for _, vr := range pkg.Build.VersionRequirements { g.Build.VersionRequirements = append(g.Build.VersionRequirements, types.VersionRequirement{ @@ -373,7 +369,7 @@ func convertMetadata(m types.ZarfMetadata) v1beta1.ZarfMetadata { Uncompressed: m.Uncompressed, Architecture: m.Architecture, Annotations: m.Annotations, - AllowNamespaceOverride: derefBoolOr(m.AllowNamespaceOverride, true), + AllowNamespaceOverride: m.AllowNamespaceOverride, } // Migrate v1alpha1-only metadata fields into annotations. @@ -797,10 +793,3 @@ func isGitURL(url string) bool { } return strings.HasSuffix(url, ".git") } - -func derefBoolOr(p *bool, def bool) bool { - if p != nil { - return *p - } - return def -} From 577a3280d80c58699914f8b84acb97cef610a1b8 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 09:48:11 -0400 Subject: [PATCH 09/24] unnecessary path Signed-off-by: Austin Abro --- src/internal/api/v1beta1/convert.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 6e90c189fd..153ba69969 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -590,10 +590,6 @@ func convertChart(ch types.ZarfChart) v1beta1.ZarfChart { } else if ch.URL != "" { // Infer source type from v1alpha1 flat fields. switch { - case ch.LocalPath != "": - bc.Local = v1beta1.LocalRepoSource{ - Path: ch.LocalPath, - } case strings.HasPrefix(ch.URL, "oci://"): bc.OCI = v1beta1.OCISource{ URL: ch.URL, From 888d51b52cc1dfc92e935897b25c3eeabae83d49 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 10:03:04 -0400 Subject: [PATCH 10/24] add healthchecks as on deploy actions Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 8 ++++++++ src/internal/api/v1beta1/convert.go | 13 +++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 740ae440df..2e698178e6 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -226,6 +226,14 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { // DataInjections should be preserved via the private shim. require.Len(t, comp.GetDataInjections(), 1) assert.Equal(t, "/data", comp.GetDataInjections()[0].Source) + + // HealthChecks should become onDeploy after wait actions. + require.Len(t, comp.Actions.OnDeploy.After, 1) + require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait) + require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait.Cluster) + assert.Equal(t, "Deployment", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) + assert.Equal(t, "my-deploy", comp.Actions.OnDeploy.After[0].Wait.Cluster.Name) + assert.Equal(t, "default", comp.Actions.OnDeploy.After[0].Wait.Cluster.Namespace) } func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 153ba69969..e7f41159d6 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -483,6 +483,19 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { }) } + // Convert v1alpha1 HealthChecks into onDeploy after wait actions. + for _, hc := range c.HealthChecks { + gc.Actions.OnDeploy.After = append(gc.Actions.OnDeploy.After, v1beta1.ZarfComponentAction{ + Wait: &v1beta1.ZarfComponentActionWait{ + Cluster: &v1beta1.ZarfComponentActionWaitCluster{ + Kind: hc.Kind, + Name: hc.Name, + Namespace: hc.Namespace, + }, + }, + }) + } + // Preserve v1alpha1-only fields via private shims for lossless round-tripping. gc.SetDataInjections(c.DataInjections) From 9c03bab7c70cfc5a7f472b68b8e9136a2f596b7f Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 10:16:39 -0400 Subject: [PATCH 11/24] handle health checks Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 14 +++++++++++--- src/internal/api/v1beta1/convert.go | 13 ++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 2e698178e6..aee6f6da47 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -191,6 +191,7 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { }, HealthChecks: []v1alpha1.NamespacedObjectKindReference{ {APIVersion: "apps/v1", Kind: "Deployment", Namespace: "default", Name: "my-deploy"}, + {APIVersion: "v1", Kind: "Pod", Namespace: "default", Name: "my-pod"}, }, }, }, @@ -227,13 +228,20 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { require.Len(t, comp.GetDataInjections(), 1) assert.Equal(t, "/data", comp.GetDataInjections()[0].Source) - // HealthChecks should become onDeploy after wait actions. - require.Len(t, comp.Actions.OnDeploy.After, 1) + // HealthChecks should become onDeploy after wait actions with kind in .. format. + require.Len(t, comp.Actions.OnDeploy.After, 2) require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait) require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait.Cluster) - assert.Equal(t, "Deployment", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) + assert.Equal(t, "Deployment.v1.apps", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) assert.Equal(t, "my-deploy", comp.Actions.OnDeploy.After[0].Wait.Cluster.Name) assert.Equal(t, "default", comp.Actions.OnDeploy.After[0].Wait.Cluster.Namespace) + + // Core API resources (no group) keep kind as-is. + require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait) + require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait.Cluster) + assert.Equal(t, "Pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Kind) + assert.Equal(t, "my-pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Name) + assert.Equal(t, "default", comp.Actions.OnDeploy.After[1].Wait.Cluster.Namespace) } func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index e7f41159d6..ad7dba942e 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -488,7 +488,7 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { gc.Actions.OnDeploy.After = append(gc.Actions.OnDeploy.After, v1beta1.ZarfComponentAction{ Wait: &v1beta1.ZarfComponentActionWait{ Cluster: &v1beta1.ZarfComponentActionWaitCluster{ - Kind: hc.Kind, + Kind: healthCheckKind(hc.Kind, hc.APIVersion), Name: hc.Name, Namespace: hc.Namespace, }, @@ -794,6 +794,17 @@ func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionW return bw } +// healthCheckKind returns the wait-for kind string for a v1alpha1 health check. +// For resources with a group (e.g. APIVersion "apps/v1"), the format is ... +// For core resources with no group (e.g. APIVersion "v1"), the kind is returned as-is. +func healthCheckKind(kind, apiVersion string) string { + group, version, found := strings.Cut(apiVersion, "/") + if !found { + return kind + } + return kind + "." + version + "." + group +} + func isGitURL(url string) bool { // Strip any @ref suffix before checking, matching the runtime behavior // in packager/helm/repo.go which uses transform.GitURLSplitRef. From cc293fa5a2283062553c7609c33bc8df2040d400 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 10:42:10 -0400 Subject: [PATCH 12/24] hide command Signed-off-by: Austin Abro --- src/cmd/internal_convert.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/cmd/internal_convert.go b/src/cmd/internal_convert.go index dda8aada3c..1f63ce0b78 100644 --- a/src/cmd/internal_convert.go +++ b/src/cmd/internal_convert.go @@ -12,26 +12,32 @@ import ( "github.com/spf13/cobra" "github.com/zarf-dev/zarf/src/api/convert" "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/pkg/logger" ) type internalConvertOptions struct{} -// FIXME: temporary internal convert command +// This command will be unhidden and moved to dev once v1beta1 is ready for use func newInternalConvertCommand() *cobra.Command { o := &internalConvertOptions{} cmd := &cobra.Command{ - Use: "convert [directory]", - Short: "Convert a v1alpha1 zarf.yaml to v1beta1", - Args: cobra.ExactArgs(1), - RunE: o.run, + Use: "convert [directory]", + Short: "Convert zarf.yaml to the latest API version (V1beta1)", + Hidden: true, + Args: cobra.MaximumNArgs(1), + RunE: o.run, } return cmd } -func (o *internalConvertOptions) run(_ *cobra.Command, args []string) error { - dir := args[0] +func (o *internalConvertOptions) run(cmd *cobra.Command, args []string) error { + l := logger.From(cmd.Context()) + dir := "." + if len(args) > 0 { + dir = args[0] + } inputPath := filepath.Join(dir, "zarf.yaml") b, err := os.ReadFile(inputPath) @@ -56,6 +62,6 @@ func (o *internalConvertOptions) run(_ *cobra.Command, args []string) error { return fmt.Errorf("writing %s: %w", outputPath, err) } - fmt.Printf("Converted %s -> %s\n", inputPath, outputPath) + l.Info("converted", "input", inputPath, "output", outputPath) return nil } From e6d477fc63f880aaf1cbf94e05b44e0db9519566 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 10:49:37 -0400 Subject: [PATCH 13/24] add default Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 3 +++ src/api/v1beta1/component.go | 3 +++ src/internal/api/v1beta1/convert.go | 2 ++ 3 files changed, 8 insertions(+) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index aee6f6da47..b5a1b89ba0 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -203,6 +203,7 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { comp := result.Components[0] assert.Equal(t, "my-component", comp.Name) assert.Equal(t, "test component", comp.Description) + assert.True(t, comp.Default) // Required=true → Optional=false require.NotNil(t, comp.Optional) @@ -703,6 +704,7 @@ func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { { Name: "my-component", Description: "test component", + Default: true, Optional: &optional, Only: v1beta1.ZarfComponentOnlyTarget{ LocalOS: "linux", @@ -731,6 +733,7 @@ func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { comp := result.Components[0] assert.Equal(t, "my-component", comp.Name) assert.Equal(t, "test component", comp.Description) + assert.True(t, comp.Default) // Optional=true → Required=false require.NotNil(t, comp.Required) diff --git a/src/api/v1beta1/component.go b/src/api/v1beta1/component.go index 533f021ae9..1e20663afd 100644 --- a/src/api/v1beta1/component.go +++ b/src/api/v1beta1/component.go @@ -17,6 +17,9 @@ type ZarfComponent struct { // Message to include during package deploy describing the purpose of this component. Description string `json:"description,omitempty"` + // Whether this component is default. Defaults to false. + Default bool `json:"default,omitempty"` + // Do not prompt user to install this component. Defaults to false, meaning the component is required. Optional *bool `json:"optional,omitempty"` diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index ad7dba942e..d6e7fd6e78 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -93,6 +93,7 @@ func convertV1Beta1Component(c v1beta1.ZarfComponent) types.ZarfComponent { gc := types.ZarfComponent{ Name: c.Name, Description: c.Description, + Default: c.Default, Optional: c.Optional, DataInjections: c.GetDataInjections(), Repos: c.Repos, @@ -431,6 +432,7 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { gc := v1beta1.ZarfComponent{ Name: c.Name, Description: c.Description, + Default: c.Default, Optional: convertRequiredToOptional(c.Required), Only: v1beta1.ZarfComponentOnlyTarget{ LocalOS: c.Only.LocalOS, From c78647e0878393bc2dcd45faa4565b5e54240b39 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 14:12:36 -0400 Subject: [PATCH 14/24] error on removed fields Signed-off-by: Austin Abro --- src/cmd/internal_convert.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/cmd/internal_convert.go b/src/cmd/internal_convert.go index 1f63ce0b78..39300766c0 100644 --- a/src/cmd/internal_convert.go +++ b/src/cmd/internal_convert.go @@ -4,6 +4,7 @@ package cmd import ( + "errors" "fmt" "os" "path/filepath" @@ -50,6 +51,10 @@ func (o *internalConvertOptions) run(cmd *cobra.Command, args []string) error { return fmt.Errorf("parsing %s: %w", inputPath, err) } + if err := checkRemovedFields(pkg); err != nil { + return err + } + result := convert.V1Alpha1PkgToV1Beta1(pkg) out, err := goyaml.Marshal(result) @@ -65,3 +70,30 @@ func (o *internalConvertOptions) run(cmd *cobra.Command, args []string) error { l.Info("converted", "input", inputPath, "output", outputPath) return nil } + +func checkRemovedFields(pkg v1alpha1.ZarfPackage) error { + var errs []error + if pkg.Metadata.YOLO { + // TODO, add link to connected docs when available + errs = append(errs, fmt.Errorf(".metadata.yolo is removed without replacement in v1beta1 — replace it with connected deployments")) + } + for _, c := range pkg.Components { + if c.DeprecatedGroup != "" { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.group is removed without replacement in v1beta1 — consider using .components[x].only.flavor instead", c.Name)) + } + if len(c.DataInjections) > 0 { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.dataInjections is removed without replacement in v1beta1 — see https://docs.zarf.dev/best-practices/data-injections-migration/ for alternatives", c.Name)) + } + // TODO add link to example of newer import system + if c.Import.Name != "" { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.import.name is removed without replacement in v1beta1", c.Name)) + } + for _, ch := range c.Charts { + // TODO link to values docs + if len(ch.Variables) > 0 { + errs = append(errs, fmt.Errorf("can't convert chart %s in component %s, .components.charts.variables is removed without replacement in v1beta1 — consider using Zarf values instead", ch.Name, c.Name)) + } + } + } + return errors.Join(errs...) +} From 95214299964a8a3cfde24553a776094dac41899b Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 15:32:01 -0400 Subject: [PATCH 15/24] add api version Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 4 ++++ src/internal/api/types/package.go | 1 + src/internal/api/v1alpha1/convert.go | 2 ++ src/internal/api/v1beta1/convert.go | 2 ++ 4 files changed, 9 insertions(+) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index b5a1b89ba0..83b4d0b140 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -84,6 +84,7 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { DifferentialMissing: []string{"comp-a"}, Flavor: "vanilla", Signed: &signed, + APIVersion: v1alpha1.APIVersion, VersionRequirements: []v1alpha1.VersionRequirement{ {Version: "v0.28.0", Reason: "needs feature X"}, }, @@ -107,6 +108,7 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) assert.Equal(t, "needs feature X", result.Build.VersionRequirements[0].Reason) assert.Equal(t, []string{"sig.json"}, result.Build.ProvenanceFiles) + require.Equal(t, v1alpha1.APIVersion, result.Build.APIVersion) } func TestV1Alpha1PkgToV1Beta1_Variables(t *testing.T) { @@ -672,6 +674,7 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { DifferentialPackageVersion: "0.29.0", Flavor: "vanilla", Signed: &signed, + APIVersion: v1beta1.APIVersion, VersionRequirements: []v1beta1.VersionRequirement{ {Version: "v0.28.0", Reason: "needs feature X"}, }, @@ -693,6 +696,7 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { assert.True(t, *result.Build.Signed) require.Len(t, result.Build.VersionRequirements, 1) assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) + assert.Equal(t, v1beta1.APIVersion, result.Build.APIVersion) } func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go index bab649c3e6..b921d6a80a 100644 --- a/src/internal/api/types/package.go +++ b/src/internal/api/types/package.go @@ -61,6 +61,7 @@ type ZarfBuildData struct { VersionRequirements []VersionRequirement ProvenanceFiles []string AggregateChecksum string + APIVersion string // v1alpha1-only DifferentialMissing []string diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index 876db1889c..c4081fa4dd 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -46,6 +46,7 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, Flavor: pkg.Build.Flavor, Signed: pkg.Build.Signed, + APIVersion: pkg.Build.APIVersion, DifferentialMissing: pkg.Build.DifferentialMissing, ProvenanceFiles: pkg.Build.ProvenanceFiles, }, @@ -435,6 +436,7 @@ func convertGenericToV1Alpha1Build(b types.ZarfBuildData) v1alpha1.ZarfBuildData DifferentialMissing: b.DifferentialMissing, Flavor: b.Flavor, Signed: b.Signed, + APIVersion: b.APIVersion, ProvenanceFiles: b.ProvenanceFiles, } diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index d6e7fd6e78..9e1e1f8494 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -38,6 +38,7 @@ func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, Flavor: pkg.Build.Flavor, Signed: pkg.Build.Signed, + APIVersion: pkg.Build.APIVersion, AggregateChecksum: pkg.Build.AggregateChecksum, ProvenanceFiles: pkg.Build.ProvenanceFiles, }, @@ -408,6 +409,7 @@ func convertBuild(b types.ZarfBuildData, m types.ZarfMetadata) v1beta1.ZarfBuild DifferentialPackageVersion: b.DifferentialPackageVersion, Flavor: b.Flavor, Signed: b.Signed, + APIVersion: b.APIVersion, ProvenanceFiles: b.ProvenanceFiles, } From 8340440b20df483a7c728ccb9b5c6819f124c282 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 16 Mar 2026 15:34:28 -0400 Subject: [PATCH 16/24] use require instead of assert Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 411 ++++++++++++++++---------------- 1 file changed, 205 insertions(+), 206 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 83b4d0b140..4cd3c5ae23 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" @@ -42,28 +41,28 @@ func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - assert.Equal(t, v1beta1.APIVersion, result.APIVersion) - assert.Equal(t, v1beta1.ZarfPackageConfig, result.Kind) - assert.Equal(t, "test-pkg", result.Metadata.Name) - assert.Equal(t, "A test package", result.Metadata.Description) - assert.Equal(t, "1.0.0", result.Metadata.Version) - assert.Equal(t, "amd64", result.Metadata.Architecture) - assert.True(t, result.Metadata.Uncompressed) + require.Equal(t, v1beta1.APIVersion, result.APIVersion) + require.Equal(t, v1beta1.ZarfPackageConfig, result.Kind) + require.Equal(t, "test-pkg", result.Metadata.Name) + require.Equal(t, "A test package", result.Metadata.Description) + require.Equal(t, "1.0.0", result.Metadata.Version) + require.Equal(t, "amd64", result.Metadata.Architecture) + require.True(t, result.Metadata.Uncompressed) require.NotNil(t, result.Metadata.AllowNamespaceOverride) - assert.True(t, *result.Metadata.AllowNamespaceOverride) + require.True(t, *result.Metadata.AllowNamespaceOverride) // v1alpha1-only metadata fields should be migrated to annotations. - assert.Equal(t, "https://example.com", result.Metadata.Annotations["metadata.url"]) - assert.Equal(t, "https://example.com/image.png", result.Metadata.Annotations["metadata.image"]) - assert.Equal(t, "Test Author", result.Metadata.Annotations["metadata.authors"]) - assert.Equal(t, "https://docs.example.com", result.Metadata.Annotations["metadata.documentation"]) - assert.Equal(t, "https://github.com/example", result.Metadata.Annotations["metadata.source"]) - assert.Equal(t, "Example Corp", result.Metadata.Annotations["metadata.vendor"]) + require.Equal(t, "https://example.com", result.Metadata.Annotations["metadata.url"]) + require.Equal(t, "https://example.com/image.png", result.Metadata.Annotations["metadata.image"]) + require.Equal(t, "Test Author", result.Metadata.Annotations["metadata.authors"]) + require.Equal(t, "https://docs.example.com", result.Metadata.Annotations["metadata.documentation"]) + require.Equal(t, "https://github.com/example", result.Metadata.Annotations["metadata.source"]) + require.Equal(t, "Example Corp", result.Metadata.Annotations["metadata.vendor"]) // Existing annotation should be preserved. - assert.Equal(t, "annotation", result.Metadata.Annotations["existing"]) + require.Equal(t, "annotation", result.Metadata.Annotations["existing"]) // AggregateChecksum should move from metadata to build. - assert.Equal(t, "abc123", result.Build.AggregateChecksum) + require.Equal(t, "abc123", result.Build.AggregateChecksum) } func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { @@ -94,20 +93,20 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - assert.Equal(t, v1beta1.ZarfInitConfig, result.Kind) - assert.Equal(t, "my-machine", result.Build.Terminal) - assert.Equal(t, "test-user", result.Build.User) - assert.Equal(t, "arm64", result.Build.Architecture) - assert.Equal(t, "v0.30.0", result.Build.Version) - assert.True(t, result.Build.Differential) - assert.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) - assert.Equal(t, "vanilla", result.Build.Flavor) + require.Equal(t, v1beta1.ZarfInitConfig, result.Kind) + require.Equal(t, "my-machine", result.Build.Terminal) + require.Equal(t, "test-user", result.Build.User) + require.Equal(t, "arm64", result.Build.Architecture) + require.Equal(t, "v0.30.0", result.Build.Version) + require.True(t, result.Build.Differential) + require.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) + require.Equal(t, "vanilla", result.Build.Flavor) require.NotNil(t, result.Build.Signed) - assert.True(t, *result.Build.Signed) + require.True(t, *result.Build.Signed) require.Len(t, result.Build.VersionRequirements, 1) - assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) - assert.Equal(t, "needs feature X", result.Build.VersionRequirements[0].Reason) - assert.Equal(t, []string{"sig.json"}, result.Build.ProvenanceFiles) + require.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) + require.Equal(t, "needs feature X", result.Build.VersionRequirements[0].Reason) + require.Equal(t, []string{"sig.json"}, result.Build.ProvenanceFiles) require.Equal(t, v1alpha1.APIVersion, result.Build.APIVersion) } @@ -144,21 +143,21 @@ func TestV1Alpha1PkgToV1Beta1_Variables(t *testing.T) { require.Len(t, result.Variables, 1) v := result.Variables[0] - assert.Equal(t, "MY_VAR", v.Name) - assert.True(t, v.Sensitive) - assert.True(t, v.AutoIndent) - assert.Equal(t, "^[a-z]+$", v.Pattern) - assert.Equal(t, v1beta1.FileVariableType, v.Type) - assert.Equal(t, "A variable", v.Description) - assert.Equal(t, "default-val", v.Default) - assert.True(t, v.Prompt) + require.Equal(t, "MY_VAR", v.Name) + require.True(t, v.Sensitive) + require.True(t, v.AutoIndent) + require.Equal(t, "^[a-z]+$", v.Pattern) + require.Equal(t, v1beta1.FileVariableType, v.Type) + require.Equal(t, "A variable", v.Description) + require.Equal(t, "default-val", v.Default) + require.True(t, v.Prompt) require.Len(t, result.Constants, 1) c := result.Constants[0] - assert.Equal(t, "MY_CONST", c.Name) - assert.Equal(t, "const-val", c.Value) - assert.Equal(t, "A constant", c.Description) - assert.True(t, c.AutoIndent) + require.Equal(t, "MY_CONST", c.Name) + require.Equal(t, "const-val", c.Value) + require.Equal(t, "A constant", c.Description) + require.True(t, c.AutoIndent) } func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { @@ -203,48 +202,48 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { require.Len(t, result.Components, 1) comp := result.Components[0] - assert.Equal(t, "my-component", comp.Name) - assert.Equal(t, "test component", comp.Description) - assert.True(t, comp.Default) + require.Equal(t, "my-component", comp.Name) + require.Equal(t, "test component", comp.Description) + require.True(t, comp.Default) // Required=true → Optional=false require.NotNil(t, comp.Optional) - assert.False(t, *comp.Optional) + require.False(t, *comp.Optional) - assert.Equal(t, "linux", comp.Only.LocalOS) - assert.Equal(t, "amd64", comp.Only.Cluster.Architecture) - assert.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) - assert.Equal(t, "vanilla", comp.Only.Flavor) + require.Equal(t, "linux", comp.Only.LocalOS) + require.Equal(t, "amd64", comp.Only.Cluster.Architecture) + require.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) + require.Equal(t, "vanilla", comp.Only.Flavor) // Import.Name is v1alpha1-only, should be dropped in v1beta1. - assert.Equal(t, "./path", comp.Import.Path) - assert.Equal(t, "oci://example.com/pkg", comp.Import.URL) + require.Equal(t, "./path", comp.Import.Path) + require.Equal(t, "oci://example.com/pkg", comp.Import.URL) // Images are converted from []string to []ZarfImage. require.Len(t, comp.Images, 2) - assert.Equal(t, "nginx:latest", comp.Images[0].Name) - assert.Equal(t, "redis:7", comp.Images[1].Name) + require.Equal(t, "nginx:latest", comp.Images[0].Name) + require.Equal(t, "redis:7", comp.Images[1].Name) - assert.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) + require.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) // DataInjections should be preserved via the private shim. require.Len(t, comp.GetDataInjections(), 1) - assert.Equal(t, "/data", comp.GetDataInjections()[0].Source) + require.Equal(t, "/data", comp.GetDataInjections()[0].Source) // HealthChecks should become onDeploy after wait actions with kind in .. format. require.Len(t, comp.Actions.OnDeploy.After, 2) require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait) require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait.Cluster) - assert.Equal(t, "Deployment.v1.apps", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) - assert.Equal(t, "my-deploy", comp.Actions.OnDeploy.After[0].Wait.Cluster.Name) - assert.Equal(t, "default", comp.Actions.OnDeploy.After[0].Wait.Cluster.Namespace) + require.Equal(t, "Deployment.v1.apps", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) + require.Equal(t, "my-deploy", comp.Actions.OnDeploy.After[0].Wait.Cluster.Name) + require.Equal(t, "default", comp.Actions.OnDeploy.After[0].Wait.Cluster.Namespace) // Core API resources (no group) keep kind as-is. require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait) require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait.Cluster) - assert.Equal(t, "Pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Kind) - assert.Equal(t, "my-pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Name) - assert.Equal(t, "default", comp.Actions.OnDeploy.After[1].Wait.Cluster.Namespace) + require.Equal(t, "Pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Kind) + require.Equal(t, "my-pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Name) + require.Equal(t, "default", comp.Actions.OnDeploy.After[1].Wait.Cluster.Namespace) } func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { @@ -274,13 +273,13 @@ func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) require.Len(t, result.Components, 1) comp := result.Components[0] - assert.Equal(t, tt.isRegistry, comp.Features.IsRegistry, "IsRegistry") - assert.Equal(t, tt.isAgent, comp.Features.IsAgent, "IsAgent") + require.Equal(t, tt.isRegistry, comp.Features.IsRegistry, "IsRegistry") + require.Equal(t, tt.isAgent, comp.Features.IsAgent, "IsAgent") if tt.injector { require.NotNil(t, comp.Features.Injector) - assert.True(t, comp.Features.Injector.Enabled) + require.True(t, comp.Features.Injector.Enabled) } else { - assert.Nil(t, comp.Features.Injector) + require.Nil(t, comp.Features.Injector) } }) } @@ -302,9 +301,9 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { Version: "6.4.0", }, validate: func(t *testing.T, c v1beta1.ZarfChart) { - assert.Equal(t, "https://stefanprodan.github.io/podinfo", c.HelmRepo.URL) - assert.Equal(t, "podinfo", c.HelmRepo.Name) - assert.Equal(t, "6.4.0", c.HelmRepo.Version) + require.Equal(t, "https://stefanprodan.github.io/podinfo", c.HelmRepo.URL) + require.Equal(t, "podinfo", c.HelmRepo.Name) + require.Equal(t, "6.4.0", c.HelmRepo.Version) }, }, { @@ -315,8 +314,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { Version: "6.4.0", }, validate: func(t *testing.T, c v1beta1.ZarfChart) { - assert.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.OCI.URL) - assert.Equal(t, "6.4.0", c.OCI.Version) + require.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.OCI.URL) + require.Equal(t, "6.4.0", c.OCI.Version) }, }, { @@ -328,8 +327,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { Version: "6.4.0", }, validate: func(t *testing.T, c v1beta1.ZarfChart) { - assert.Equal(t, "https://github.com/example/repo@6.4.0", c.Git.URL) - assert.Equal(t, "charts/my-chart", c.Git.Path) + require.Equal(t, "https://github.com/example/repo@6.4.0", c.Git.URL) + require.Equal(t, "charts/my-chart", c.Git.Path) }, }, { @@ -340,8 +339,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { GitPath: "charts/my-chart", }, validate: func(t *testing.T, c v1beta1.ZarfChart) { - assert.Equal(t, "https://github.com/example/repo", c.Git.URL) - assert.Equal(t, "charts/my-chart", c.Git.Path) + require.Equal(t, "https://github.com/example/repo", c.Git.URL) + require.Equal(t, "charts/my-chart", c.Git.Path) }, }, { @@ -354,8 +353,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { }, validate: func(t *testing.T, c v1beta1.ZarfChart) { // URL already has @ref, should not double-append version. - assert.Equal(t, "https://github.com/example/repo.git@v2.0.0", c.Git.URL) - assert.Equal(t, "charts/my-chart", c.Git.Path) + require.Equal(t, "https://github.com/example/repo.git@v2.0.0", c.Git.URL) + require.Equal(t, "charts/my-chart", c.Git.Path) }, }, { @@ -365,7 +364,7 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { LocalPath: "./charts/my-chart", }, validate: func(t *testing.T, c v1beta1.ZarfChart) { - assert.Equal(t, "./charts/my-chart", c.Local.Path) + require.Equal(t, "./charts/my-chart", c.Local.Path) }, }, } @@ -417,11 +416,11 @@ func TestV1Alpha1PkgToV1Beta1_ManifestNoWaitInversion(t *testing.T) { // NoWait=true → Wait=false m0 := result.Components[0].Manifests[0] require.NotNil(t, m0.Wait) - assert.False(t, *m0.Wait) + require.False(t, *m0.Wait) // NoWait=false (default) → Wait=nil (v1beta1 defaults to true) m1 := result.Components[0].Manifests[1] - assert.Nil(t, m1.Wait) + require.Nil(t, m1.Wait) } func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { @@ -483,38 +482,38 @@ func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { actions := result.Components[0].Actions // Defaults - assert.True(t, actions.OnDeploy.Defaults.Mute) + require.True(t, actions.OnDeploy.Defaults.Mute) require.NotNil(t, actions.OnDeploy.Defaults.Timeout) - assert.Equal(t, 60*time.Second, actions.OnDeploy.Defaults.Timeout.Duration) - assert.Equal(t, 2, actions.OnDeploy.Defaults.Retries) - assert.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) - assert.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) - assert.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) + require.Equal(t, 60*time.Second, actions.OnDeploy.Defaults.Timeout.Duration) + require.Equal(t, 2, actions.OnDeploy.Defaults.Retries) + require.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) + require.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) + require.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) // Before action require.Len(t, actions.OnDeploy.Before, 1) before := actions.OnDeploy.Before[0] - assert.Equal(t, "echo before", before.Cmd) + require.Equal(t, "echo before", before.Cmd) require.NotNil(t, before.Mute) - assert.True(t, *before.Mute) + require.True(t, *before.Mute) require.NotNil(t, before.Timeout) - assert.Equal(t, 30*time.Second, before.Timeout.Duration) - assert.Equal(t, 3, before.Retries) - assert.Equal(t, "run before", before.Description) + require.Equal(t, 30*time.Second, before.Timeout.Duration) + require.Equal(t, 3, before.Retries) + require.Equal(t, "run before", before.Description) // SetVariables should include both the explicit one and the deprecated one. require.Len(t, before.SetVariables, 2) - assert.Equal(t, "OUT_VAR", before.SetVariables[0].Name) - assert.True(t, before.SetVariables[0].Sensitive) - assert.Equal(t, "OLD_VAR", before.SetVariables[1].Name) + require.Equal(t, "OUT_VAR", before.SetVariables[0].Name) + require.True(t, before.SetVariables[0].Sensitive) + require.Equal(t, "OLD_VAR", before.SetVariables[1].Name) // After should include original After + OnSuccess (merged). require.Len(t, actions.OnDeploy.After, 2) - assert.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) - assert.Equal(t, "echo success", actions.OnDeploy.After[1].Cmd) + require.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) + require.Equal(t, "echo success", actions.OnDeploy.After[1].Cmd) // OnFailure require.Len(t, actions.OnDeploy.OnFailure, 1) - assert.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) + require.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) } func TestV1Alpha1PkgToV1Beta1_Files(t *testing.T) { @@ -544,14 +543,14 @@ func TestV1Alpha1PkgToV1Beta1_Files(t *testing.T) { require.Len(t, result.Components[0].Files, 1) f := result.Components[0].Files[0] - assert.Equal(t, "https://example.com/file.tar.gz", f.Source) - assert.Equal(t, "deadbeef", f.Shasum) - assert.Equal(t, "/opt/file.tar.gz", f.Target) - assert.True(t, f.Executable) - assert.Equal(t, []string{"/usr/local/bin/file"}, f.Symlinks) - assert.Equal(t, "bin/file", f.ExtractPath) + require.Equal(t, "https://example.com/file.tar.gz", f.Source) + require.Equal(t, "deadbeef", f.Shasum) + require.Equal(t, "/opt/file.tar.gz", f.Target) + require.True(t, f.Executable) + require.Equal(t, []string{"/usr/local/bin/file"}, f.Symlinks) + require.Equal(t, "bin/file", f.ExtractPath) require.NotNil(t, f.Template) - assert.True(t, *f.Template) + require.True(t, *f.Template) } func TestV1Alpha1PkgToV1Beta1_ValuesAndDocumentation(t *testing.T) { @@ -569,9 +568,9 @@ func TestV1Alpha1PkgToV1Beta1_ValuesAndDocumentation(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - assert.Equal(t, []string{"values.yaml"}, result.Values.Files) - assert.Equal(t, "values.schema.json", result.Values.Schema) - assert.Equal(t, "# Hello", result.Documentation["readme"]) + require.Equal(t, []string{"values.yaml"}, result.Values.Files) + require.Equal(t, "values.schema.json", result.Values.Schema) + require.Equal(t, "# Hello", result.Documentation["readme"]) } func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { @@ -596,7 +595,7 @@ func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { require.Len(t, result.Components[0].Charts, 1) chart := result.Components[0].Charts[0] - assert.Equal(t, "1.2.3", chart.GetDeprecatedVersion()) + require.Equal(t, "1.2.3", chart.GetDeprecatedVersion()) } // --- v1beta1 → v1alpha1 tests --- @@ -631,30 +630,30 @@ func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { result := V1Beta1PkgToV1Alpha1(pkg) - assert.Equal(t, v1alpha1.APIVersion, result.APIVersion) - assert.Equal(t, v1alpha1.ZarfPackageConfig, result.Kind) - assert.Equal(t, "test-pkg", result.Metadata.Name) - assert.Equal(t, "A test package", result.Metadata.Description) - assert.Equal(t, "1.0.0", result.Metadata.Version) - assert.Equal(t, "amd64", result.Metadata.Architecture) - assert.True(t, result.Metadata.Uncompressed) + require.Equal(t, v1alpha1.APIVersion, result.APIVersion) + require.Equal(t, v1alpha1.ZarfPackageConfig, result.Kind) + require.Equal(t, "test-pkg", result.Metadata.Name) + require.Equal(t, "A test package", result.Metadata.Description) + require.Equal(t, "1.0.0", result.Metadata.Version) + require.Equal(t, "amd64", result.Metadata.Architecture) + require.True(t, result.Metadata.Uncompressed) require.NotNil(t, result.Metadata.AllowNamespaceOverride) - assert.True(t, *result.Metadata.AllowNamespaceOverride) + require.True(t, *result.Metadata.AllowNamespaceOverride) // v1alpha1-only metadata fields should be restored from annotations. - assert.Equal(t, "https://example.com", result.Metadata.URL) - assert.Equal(t, "https://example.com/image.png", result.Metadata.Image) - assert.Equal(t, "Test Author", result.Metadata.Authors) - assert.Equal(t, "https://docs.example.com", result.Metadata.Documentation) - assert.Equal(t, "https://github.com/example", result.Metadata.Source) - assert.Equal(t, "Example Corp", result.Metadata.Vendor) + require.Equal(t, "https://example.com", result.Metadata.URL) + require.Equal(t, "https://example.com/image.png", result.Metadata.Image) + require.Equal(t, "Test Author", result.Metadata.Authors) + require.Equal(t, "https://docs.example.com", result.Metadata.Documentation) + require.Equal(t, "https://github.com/example", result.Metadata.Source) + require.Equal(t, "Example Corp", result.Metadata.Vendor) // Metadata-specific annotations should be consumed, regular annotations preserved. - assert.Equal(t, "annotation", result.Metadata.Annotations["existing"]) - assert.Empty(t, result.Metadata.Annotations["metadata.url"]) + require.Equal(t, "annotation", result.Metadata.Annotations["existing"]) + require.Empty(t, result.Metadata.Annotations["metadata.url"]) // AggregateChecksum should move from build to metadata. - assert.Equal(t, "abc123", result.Metadata.AggregateChecksum) + require.Equal(t, "abc123", result.Metadata.AggregateChecksum) } func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { @@ -684,19 +683,19 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { result := V1Beta1PkgToV1Alpha1(pkg) - assert.Equal(t, v1alpha1.ZarfInitConfig, result.Kind) - assert.Equal(t, "my-machine", result.Build.Terminal) - assert.Equal(t, "test-user", result.Build.User) - assert.Equal(t, "arm64", result.Build.Architecture) - assert.Equal(t, "v0.30.0", result.Build.Version) - assert.True(t, result.Build.Differential) - assert.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) - assert.Equal(t, "vanilla", result.Build.Flavor) + require.Equal(t, v1alpha1.ZarfInitConfig, result.Kind) + require.Equal(t, "my-machine", result.Build.Terminal) + require.Equal(t, "test-user", result.Build.User) + require.Equal(t, "arm64", result.Build.Architecture) + require.Equal(t, "v0.30.0", result.Build.Version) + require.True(t, result.Build.Differential) + require.Equal(t, "0.29.0", result.Build.DifferentialPackageVersion) + require.Equal(t, "vanilla", result.Build.Flavor) require.NotNil(t, result.Build.Signed) - assert.True(t, *result.Build.Signed) + require.True(t, *result.Build.Signed) require.Len(t, result.Build.VersionRequirements, 1) - assert.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) - assert.Equal(t, v1beta1.APIVersion, result.Build.APIVersion) + require.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) + require.Equal(t, v1beta1.APIVersion, result.Build.APIVersion) } func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { @@ -735,28 +734,28 @@ func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { require.Len(t, result.Components, 1) comp := result.Components[0] - assert.Equal(t, "my-component", comp.Name) - assert.Equal(t, "test component", comp.Description) - assert.True(t, comp.Default) + require.Equal(t, "my-component", comp.Name) + require.Equal(t, "test component", comp.Description) + require.True(t, comp.Default) // Optional=true → Required=false require.NotNil(t, comp.Required) - assert.False(t, *comp.Required) + require.False(t, *comp.Required) - assert.Equal(t, "linux", comp.Only.LocalOS) - assert.Equal(t, "amd64", comp.Only.Cluster.Architecture) - assert.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) - assert.Equal(t, "vanilla", comp.Only.Flavor) + require.Equal(t, "linux", comp.Only.LocalOS) + require.Equal(t, "amd64", comp.Only.Cluster.Architecture) + require.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) + require.Equal(t, "vanilla", comp.Only.Flavor) - assert.Equal(t, "./path", comp.Import.Path) - assert.Equal(t, "oci://example.com/pkg", comp.Import.URL) + require.Equal(t, "./path", comp.Import.Path) + require.Equal(t, "oci://example.com/pkg", comp.Import.URL) // Images are converted from []ZarfImage to []string. require.Len(t, comp.Images, 2) - assert.Equal(t, "nginx:latest", comp.Images[0]) - assert.Equal(t, "redis:7", comp.Images[1]) + require.Equal(t, "nginx:latest", comp.Images[0]) + require.Equal(t, "redis:7", comp.Images[1]) - assert.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) + require.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) } func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { @@ -777,9 +776,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { - assert.Equal(t, "https://stefanprodan.github.io/podinfo", c.URL) - assert.Equal(t, "podinfo", c.RepoName) - assert.Equal(t, "6.4.0", c.Version) + require.Equal(t, "https://stefanprodan.github.io/podinfo", c.URL) + require.Equal(t, "podinfo", c.RepoName) + require.Equal(t, "6.4.0", c.Version) }, }, { @@ -792,8 +791,8 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { - assert.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.URL) - assert.Equal(t, "6.4.0", c.Version) + require.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.URL) + require.Equal(t, "6.4.0", c.Version) }, }, { @@ -806,9 +805,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { - assert.Equal(t, "https://github.com/example/repo", c.URL) - assert.Equal(t, "charts/my-chart", c.GitPath) - assert.Equal(t, "6.4.0", c.Version) + require.Equal(t, "https://github.com/example/repo", c.URL) + require.Equal(t, "charts/my-chart", c.GitPath) + require.Equal(t, "6.4.0", c.Version) }, }, { @@ -821,8 +820,8 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { - assert.Equal(t, "https://github.com/example/repo", c.URL) - assert.Equal(t, "charts/my-chart", c.GitPath) + require.Equal(t, "https://github.com/example/repo", c.URL) + require.Equal(t, "charts/my-chart", c.GitPath) }, }, { @@ -834,7 +833,7 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { - assert.Equal(t, "./charts/my-chart", c.LocalPath) + require.Equal(t, "./charts/my-chart", c.LocalPath) }, }, } @@ -885,10 +884,10 @@ func TestV1Beta1PkgToV1Alpha1_ManifestWaitInversion(t *testing.T) { require.Len(t, result.Components[0].Manifests, 2) // Wait=false → NoWait=true - assert.True(t, result.Components[0].Manifests[0].NoWait) + require.True(t, result.Components[0].Manifests[0].NoWait) // Wait=nil → NoWait=false (default) - assert.False(t, result.Components[0].Manifests[1].NoWait) + require.False(t, result.Components[0].Manifests[1].NoWait) } func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { @@ -943,34 +942,34 @@ func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { actions := result.Components[0].Actions // Defaults - assert.True(t, actions.OnDeploy.Defaults.Mute) - assert.Equal(t, 60, actions.OnDeploy.Defaults.MaxTotalSeconds) - assert.Equal(t, 2, actions.OnDeploy.Defaults.MaxRetries) - assert.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) - assert.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) - assert.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) + require.True(t, actions.OnDeploy.Defaults.Mute) + require.Equal(t, 60, actions.OnDeploy.Defaults.MaxTotalSeconds) + require.Equal(t, 2, actions.OnDeploy.Defaults.MaxRetries) + require.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) + require.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) + require.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) // Before action require.Len(t, actions.OnDeploy.Before, 1) before := actions.OnDeploy.Before[0] - assert.Equal(t, "echo before", before.Cmd) + require.Equal(t, "echo before", before.Cmd) require.NotNil(t, before.Mute) - assert.True(t, *before.Mute) + require.True(t, *before.Mute) require.NotNil(t, before.MaxTotalSeconds) - assert.Equal(t, 30, *before.MaxTotalSeconds) + require.Equal(t, 30, *before.MaxTotalSeconds) require.NotNil(t, before.MaxRetries) - assert.Equal(t, 3, *before.MaxRetries) + require.Equal(t, 3, *before.MaxRetries) require.Len(t, before.SetVariables, 1) - assert.Equal(t, "OUT_VAR", before.SetVariables[0].Name) - assert.True(t, before.SetVariables[0].Sensitive) + require.Equal(t, "OUT_VAR", before.SetVariables[0].Name) + require.True(t, before.SetVariables[0].Sensitive) // After require.Len(t, actions.OnDeploy.After, 1) - assert.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) + require.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) // OnFailure require.Len(t, actions.OnDeploy.OnFailure, 1) - assert.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) + require.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) } func TestV1Beta1PkgToV1Alpha1_Variables(t *testing.T) { @@ -1006,19 +1005,19 @@ func TestV1Beta1PkgToV1Alpha1_Variables(t *testing.T) { require.Len(t, result.Variables, 1) v := result.Variables[0] - assert.Equal(t, "MY_VAR", v.Name) - assert.True(t, v.Sensitive) - assert.True(t, v.AutoIndent) - assert.Equal(t, "^[a-z]+$", v.Pattern) - assert.Equal(t, v1alpha1.FileVariableType, v.Type) - assert.Equal(t, "A variable", v.Description) - assert.Equal(t, "default-val", v.Default) - assert.True(t, v.Prompt) + require.Equal(t, "MY_VAR", v.Name) + require.True(t, v.Sensitive) + require.True(t, v.AutoIndent) + require.Equal(t, "^[a-z]+$", v.Pattern) + require.Equal(t, v1alpha1.FileVariableType, v.Type) + require.Equal(t, "A variable", v.Description) + require.Equal(t, "default-val", v.Default) + require.True(t, v.Prompt) require.Len(t, result.Constants, 1) c := result.Constants[0] - assert.Equal(t, "MY_CONST", c.Name) - assert.Equal(t, "const-val", c.Value) + require.Equal(t, "MY_CONST", c.Name) + require.Equal(t, "const-val", c.Value) } func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { @@ -1109,46 +1108,46 @@ func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { beta := V1Alpha1PkgToV1Beta1(original) result := V1Beta1PkgToV1Alpha1(beta) - assert.Equal(t, v1alpha1.APIVersion, result.APIVersion) - assert.Equal(t, original.Kind, result.Kind) - assert.Equal(t, original.Metadata.Name, result.Metadata.Name) - assert.Equal(t, original.Metadata.Description, result.Metadata.Description) - assert.Equal(t, original.Metadata.Version, result.Metadata.Version) - assert.Equal(t, original.Metadata.Architecture, result.Metadata.Architecture) - assert.Equal(t, original.Metadata.URL, result.Metadata.URL) - assert.Equal(t, original.Metadata.Authors, result.Metadata.Authors) + require.Equal(t, v1alpha1.APIVersion, result.APIVersion) + require.Equal(t, original.Kind, result.Kind) + require.Equal(t, original.Metadata.Name, result.Metadata.Name) + require.Equal(t, original.Metadata.Description, result.Metadata.Description) + require.Equal(t, original.Metadata.Version, result.Metadata.Version) + require.Equal(t, original.Metadata.Architecture, result.Metadata.Architecture) + require.Equal(t, original.Metadata.URL, result.Metadata.URL) + require.Equal(t, original.Metadata.Authors, result.Metadata.Authors) require.Len(t, result.Components, 1) comp := result.Components[0] - assert.Equal(t, "test-comp", comp.Name) + require.Equal(t, "test-comp", comp.Name) require.NotNil(t, comp.Required) - assert.True(t, *comp.Required) - assert.Equal(t, []string{"nginx:latest"}, comp.Images) + require.True(t, *comp.Required) + require.Equal(t, []string{"nginx:latest"}, comp.Images) // Chart should round-trip via structured source → flat fields. require.Len(t, comp.Charts, 1) - assert.Equal(t, "https://charts.example.com", comp.Charts[0].URL) - assert.Equal(t, "my-chart", comp.Charts[0].RepoName) - assert.Equal(t, "1.2.3", comp.Charts[0].Version) + require.Equal(t, "https://charts.example.com", comp.Charts[0].URL) + require.Equal(t, "my-chart", comp.Charts[0].RepoName) + require.Equal(t, "1.2.3", comp.Charts[0].Version) // Manifest NoWait should survive round-trip. require.Len(t, comp.Manifests, 1) - assert.True(t, comp.Manifests[0].NoWait) + require.True(t, comp.Manifests[0].NoWait) // Action timeouts should round-trip through Duration conversion. - assert.Equal(t, 60, comp.Actions.OnDeploy.Defaults.MaxTotalSeconds) - assert.Equal(t, 2, comp.Actions.OnDeploy.Defaults.MaxRetries) + require.Equal(t, 60, comp.Actions.OnDeploy.Defaults.MaxTotalSeconds) + require.Equal(t, 2, comp.Actions.OnDeploy.Defaults.MaxRetries) require.Len(t, comp.Actions.OnDeploy.Before, 1) require.NotNil(t, comp.Actions.OnDeploy.Before[0].MaxTotalSeconds) - assert.Equal(t, 30, *comp.Actions.OnDeploy.Before[0].MaxTotalSeconds) + require.Equal(t, 30, *comp.Actions.OnDeploy.Before[0].MaxTotalSeconds) // DataInjections should survive round-trip via private shim. require.Len(t, comp.DataInjections, 1) - assert.Equal(t, "/data", comp.DataInjections[0].Source) + require.Equal(t, "/data", comp.DataInjections[0].Source) // Constants and variables. require.Len(t, result.Constants, 1) - assert.Equal(t, "MY_CONST", result.Constants[0].Name) + require.Equal(t, "MY_CONST", result.Constants[0].Name) require.Len(t, result.Variables, 1) - assert.Equal(t, "MY_VAR", result.Variables[0].Name) + require.Equal(t, "MY_VAR", result.Variables[0].Name) } From b81f3dc8f02a7feebf72037cde332c900ba40d08 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 28 May 2026 12:21:51 -0400 Subject: [PATCH 17/24] fix convert logic with latest for schema Signed-off-by: Austin Abro --- src/api/convert/convert.go | 8 +- src/api/convert/convert_test.go | 456 +++++++-------- src/internal/api/types/package.go | 449 +++++++-------- src/internal/api/v1alpha1/convert.go | 632 ++++++++++----------- src/internal/api/v1beta1/convert.go | 814 ++++++++++++--------------- 5 files changed, 1053 insertions(+), 1306 deletions(-) diff --git a/src/api/convert/convert.go b/src/api/convert/convert.go index 27b03730cf..0eaf46f26f 100644 --- a/src/api/convert/convert.go +++ b/src/api/convert/convert.go @@ -11,14 +11,14 @@ import ( internalv1beta1 "github.com/zarf-dev/zarf/src/internal/api/v1beta1" ) -// V1Alpha1PkgToV1Beta1 converts a v1alpha1 ZarfPackage to a v1beta1 ZarfPackage. -func V1Alpha1PkgToV1Beta1(pkg v1alpha1.ZarfPackage) v1beta1.ZarfPackage { +// V1Alpha1PkgToV1Beta1 converts a v1alpha1 ZarfPackage to a v1beta1 Package. +func V1Alpha1PkgToV1Beta1(pkg v1alpha1.ZarfPackage) v1beta1.Package { generic := internalv1alpha1.ConvertToGeneric(pkg) return internalv1beta1.ConvertFromGeneric(generic) } -// V1Beta1PkgToV1Alpha1 converts a v1beta1 ZarfPackage to a v1alpha1 ZarfPackage. -func V1Beta1PkgToV1Alpha1(pkg v1beta1.ZarfPackage) v1alpha1.ZarfPackage { +// V1Beta1PkgToV1Alpha1 converts a v1beta1 Package to a v1alpha1 ZarfPackage. +func V1Beta1PkgToV1Alpha1(pkg v1beta1.Package) v1alpha1.ZarfPackage { generic := internalv1beta1.ConvertToGeneric(pkg) return internalv1alpha1.ConvertFromGeneric(generic) } diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 4cd3c5ae23..e0d1c0804d 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -5,12 +5,10 @@ package convert import ( "testing" - "time" "github.com/stretchr/testify/require" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { @@ -48,8 +46,8 @@ func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { require.Equal(t, "1.0.0", result.Metadata.Version) require.Equal(t, "amd64", result.Metadata.Architecture) require.True(t, result.Metadata.Uncompressed) - require.NotNil(t, result.Metadata.AllowNamespaceOverride) - require.True(t, *result.Metadata.AllowNamespaceOverride) + // AllowNamespaceOverride=true → PreventNamespaceOverride=false. + require.False(t, result.Metadata.PreventNamespaceOverride) // v1alpha1-only metadata fields should be migrated to annotations. require.Equal(t, "https://example.com", result.Metadata.Annotations["metadata.url"]) @@ -69,7 +67,7 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { t.Parallel() signed := true pkg := v1alpha1.ZarfPackage{ - Kind: v1alpha1.ZarfInitConfig, + Kind: v1alpha1.ZarfPackageConfig, Build: v1alpha1.ZarfBuildData{ Terminal: "my-machine", User: "test-user", @@ -83,7 +81,6 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { DifferentialMissing: []string{"comp-a"}, Flavor: "vanilla", Signed: &signed, - APIVersion: v1alpha1.APIVersion, VersionRequirements: []v1alpha1.VersionRequirement{ {Version: "v0.28.0", Reason: "needs feature X"}, }, @@ -93,8 +90,8 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - require.Equal(t, v1beta1.ZarfInitConfig, result.Kind) - require.Equal(t, "my-machine", result.Build.Terminal) + require.Equal(t, v1beta1.ZarfPackageConfig, result.Kind) + require.Equal(t, "my-machine", result.Build.Hostname) require.Equal(t, "test-user", result.Build.User) require.Equal(t, "arm64", result.Build.Architecture) require.Equal(t, "v0.30.0", result.Build.Version) @@ -107,10 +104,9 @@ func TestV1Alpha1PkgToV1Beta1_Build(t *testing.T) { require.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) require.Equal(t, "needs feature X", result.Build.VersionRequirements[0].Reason) require.Equal(t, []string{"sig.json"}, result.Build.ProvenanceFiles) - require.Equal(t, v1alpha1.APIVersion, result.Build.APIVersion) } -func TestV1Alpha1PkgToV1Beta1_Variables(t *testing.T) { +func TestV1Alpha1PkgToV1Beta1_VariablesAndConstantsShim(t *testing.T) { t.Parallel() pkg := v1alpha1.ZarfPackage{ Kind: v1alpha1.ZarfPackageConfig, @@ -141,23 +137,23 @@ func TestV1Alpha1PkgToV1Beta1_Variables(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - require.Len(t, result.Variables, 1) - v := result.Variables[0] - require.Equal(t, "MY_VAR", v.Name) - require.True(t, v.Sensitive) - require.True(t, v.AutoIndent) - require.Equal(t, "^[a-z]+$", v.Pattern) - require.Equal(t, v1beta1.FileVariableType, v.Type) - require.Equal(t, "A variable", v.Description) - require.Equal(t, "default-val", v.Default) - require.True(t, v.Prompt) - - require.Len(t, result.Constants, 1) - c := result.Constants[0] - require.Equal(t, "MY_CONST", c.Name) - require.Equal(t, "const-val", c.Value) - require.Equal(t, "A constant", c.Description) - require.True(t, c.AutoIndent) + vars := result.GetDeprecatedVariables() + require.Len(t, vars, 1) + require.Equal(t, "MY_VAR", vars[0].Name) + require.True(t, vars[0].Sensitive) + require.True(t, vars[0].AutoIndent) + require.Equal(t, "^[a-z]+$", vars[0].Pattern) + require.Equal(t, v1alpha1.FileVariableType, vars[0].Type) + require.Equal(t, "A variable", vars[0].Description) + require.Equal(t, "default-val", vars[0].Default) + require.True(t, vars[0].Prompt) + + consts := result.GetDeprecatedConstants() + require.Len(t, consts, 1) + require.Equal(t, "MY_CONST", consts[0].Name) + require.Equal(t, "const-val", consts[0].Value) + require.Equal(t, "A constant", consts[0].Description) + require.True(t, consts[0].AutoIndent) } func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { @@ -167,21 +163,17 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { Kind: v1alpha1.ZarfPackageConfig, Components: []v1alpha1.ZarfComponent{ { - Name: "my-component", - Description: "test component", - Default: true, - Required: &required, - DeprecatedGroup: "my-group", + Name: "my-component", + Description: "test component", + Required: &required, Only: v1alpha1.ZarfComponentOnlyTarget{ LocalOS: "linux", Cluster: v1alpha1.ZarfComponentOnlyCluster{ Architecture: "amd64", - Distros: []string{"k3s"}, }, Flavor: "vanilla", }, Import: v1alpha1.ZarfComponentImport{ - Name: "imported", Path: "./path", URL: "oci://example.com/pkg", }, @@ -204,62 +196,61 @@ func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { comp := result.Components[0] require.Equal(t, "my-component", comp.Name) require.Equal(t, "test component", comp.Description) - require.True(t, comp.Default) - // Required=true → Optional=false - require.NotNil(t, comp.Optional) - require.False(t, *comp.Optional) + // Required=true → Optional=false. + require.False(t, comp.Optional) - require.Equal(t, "linux", comp.Only.LocalOS) - require.Equal(t, "amd64", comp.Only.Cluster.Architecture) - require.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) - require.Equal(t, "vanilla", comp.Only.Flavor) + require.Equal(t, "linux", comp.Target.OS) + require.Equal(t, "amd64", comp.Target.Architecture) + require.Equal(t, "vanilla", comp.Target.Flavor) - // Import.Name is v1alpha1-only, should be dropped in v1beta1. - require.Equal(t, "./path", comp.Import.Path) - require.Equal(t, "oci://example.com/pkg", comp.Import.URL) + // v1alpha1 Import.Path/URL get promoted into the v1beta1 Local/Remote lists. + require.Len(t, comp.Import.Local, 1) + require.Equal(t, "./path", comp.Import.Local[0].Path) + require.Len(t, comp.Import.Remote, 1) + require.Equal(t, "oci://example.com/pkg", comp.Import.Remote[0].URL) - // Images are converted from []string to []ZarfImage. + // Images are converted from []string to []Image. require.Len(t, comp.Images, 2) require.Equal(t, "nginx:latest", comp.Images[0].Name) require.Equal(t, "redis:7", comp.Images[1].Name) - require.Equal(t, []string{"https://github.com/example/repo"}, comp.Repos) + require.Equal(t, []string{"https://github.com/example/repo"}, comp.Repositories) // DataInjections should be preserved via the private shim. - require.Len(t, comp.GetDataInjections(), 1) - require.Equal(t, "/data", comp.GetDataInjections()[0].Source) - - // HealthChecks should become onDeploy after wait actions with kind in .. format. - require.Len(t, comp.Actions.OnDeploy.After, 2) - require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait) - require.NotNil(t, comp.Actions.OnDeploy.After[0].Wait.Cluster) - require.Equal(t, "Deployment.v1.apps", comp.Actions.OnDeploy.After[0].Wait.Cluster.Kind) - require.Equal(t, "my-deploy", comp.Actions.OnDeploy.After[0].Wait.Cluster.Name) - require.Equal(t, "default", comp.Actions.OnDeploy.After[0].Wait.Cluster.Namespace) + di := comp.GetDeprecatedDataInjections() + require.Len(t, di, 1) + require.Equal(t, "/data", di[0].Source) + + // HealthChecks should become onDeploy.onSuccess wait actions with kind in .. format. + require.Len(t, comp.Actions.OnDeploy.OnSuccess, 2) + require.NotNil(t, comp.Actions.OnDeploy.OnSuccess[0].Wait) + require.NotNil(t, comp.Actions.OnDeploy.OnSuccess[0].Wait.Cluster) + require.Equal(t, "Deployment.v1.apps", comp.Actions.OnDeploy.OnSuccess[0].Wait.Cluster.Kind) + require.Equal(t, "my-deploy", comp.Actions.OnDeploy.OnSuccess[0].Wait.Cluster.Name) + require.Equal(t, "default", comp.Actions.OnDeploy.OnSuccess[0].Wait.Cluster.Namespace) // Core API resources (no group) keep kind as-is. - require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait) - require.NotNil(t, comp.Actions.OnDeploy.After[1].Wait.Cluster) - require.Equal(t, "Pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Kind) - require.Equal(t, "my-pod", comp.Actions.OnDeploy.After[1].Wait.Cluster.Name) - require.Equal(t, "default", comp.Actions.OnDeploy.After[1].Wait.Cluster.Namespace) + require.NotNil(t, comp.Actions.OnDeploy.OnSuccess[1].Wait) + require.NotNil(t, comp.Actions.OnDeploy.OnSuccess[1].Wait.Cluster) + require.Equal(t, "Pod", comp.Actions.OnDeploy.OnSuccess[1].Wait.Cluster.Kind) + require.Equal(t, "my-pod", comp.Actions.OnDeploy.OnSuccess[1].Wait.Cluster.Name) + require.Equal(t, "default", comp.Actions.OnDeploy.OnSuccess[1].Wait.Cluster.Namespace) } -func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { +func TestV1Alpha1PkgToV1Beta1_ServiceInference(t *testing.T) { t.Parallel() tests := []struct { - name string - compName string - isRegistry bool - isAgent bool - injector bool + name string + compName string + service v1beta1.Service }{ - {name: "zarf-registry", compName: "zarf-registry", isRegistry: true, isAgent: false, injector: false}, - {name: "zarf-injector", compName: "zarf-injector", isRegistry: true, isAgent: false, injector: false}, - {name: "zarf-seed-registry", compName: "zarf-seed-registry", isRegistry: true, isAgent: false, injector: true}, - {name: "zarf-agent", compName: "zarf-agent", isRegistry: false, isAgent: true, injector: false}, - {name: "regular component", compName: "my-app", isRegistry: false, isAgent: false, injector: false}, + {name: "registry", compName: "zarf-registry", service: v1beta1.ServiceRegistry}, + {name: "seed registry", compName: "zarf-seed-registry", service: v1beta1.ServiceSeedRegistry}, + {name: "injector", compName: "zarf-injector", service: v1beta1.ServiceInjector}, + {name: "agent", compName: "zarf-agent", service: v1beta1.ServiceAgent}, + {name: "git server", compName: "zarf-gitea", service: v1beta1.ServiceGitServer}, + {name: "no service", compName: "my-app", service: ""}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -272,15 +263,7 @@ func TestV1Alpha1PkgToV1Beta1_FeatureInference(t *testing.T) { } result := V1Alpha1PkgToV1Beta1(pkg) require.Len(t, result.Components, 1) - comp := result.Components[0] - require.Equal(t, tt.isRegistry, comp.Features.IsRegistry, "IsRegistry") - require.Equal(t, tt.isAgent, comp.Features.IsAgent, "IsAgent") - if tt.injector { - require.NotNil(t, comp.Features.Injector) - require.True(t, comp.Features.Injector.Enabled) - } else { - require.Nil(t, comp.Features.Injector) - } + require.Equal(t, tt.service, result.Components[0].Service) }) } } @@ -290,7 +273,7 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { tests := []struct { name string chart v1alpha1.ZarfChart - validate func(t *testing.T, c v1beta1.ZarfChart) + validate func(t *testing.T, c v1beta1.Chart) }{ { name: "helm repo", @@ -300,10 +283,11 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { RepoName: "podinfo", Version: "6.4.0", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { - require.Equal(t, "https://stefanprodan.github.io/podinfo", c.HelmRepo.URL) - require.Equal(t, "podinfo", c.HelmRepo.Name) - require.Equal(t, "6.4.0", c.HelmRepo.Version) + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.HelmRepository) + require.Equal(t, "https://stefanprodan.github.io/podinfo", c.HelmRepository.URL) + require.Equal(t, "podinfo", c.HelmRepository.Name) + require.Equal(t, "6.4.0", c.HelmRepository.Version) }, }, { @@ -313,7 +297,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { URL: "oci://ghcr.io/stefanprodan/charts/podinfo", Version: "6.4.0", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.OCI) require.Equal(t, "oci://ghcr.io/stefanprodan/charts/podinfo", c.OCI.URL) require.Equal(t, "6.4.0", c.OCI.Version) }, @@ -326,7 +311,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { GitPath: "charts/my-chart", Version: "6.4.0", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.Git) require.Equal(t, "https://github.com/example/repo@6.4.0", c.Git.URL) require.Equal(t, "charts/my-chart", c.Git.Path) }, @@ -338,7 +324,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { URL: "https://github.com/example/repo", GitPath: "charts/my-chart", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.Git) require.Equal(t, "https://github.com/example/repo", c.Git.URL) require.Equal(t, "charts/my-chart", c.Git.Path) }, @@ -351,7 +338,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { GitPath: "charts/my-chart", Version: "6.4.0", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.Git) // URL already has @ref, should not double-append version. require.Equal(t, "https://github.com/example/repo.git@v2.0.0", c.Git.URL) require.Equal(t, "charts/my-chart", c.Git.Path) @@ -363,7 +351,8 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { Name: "local-chart", LocalPath: "./charts/my-chart", }, - validate: func(t *testing.T, c v1beta1.ZarfChart) { + validate: func(t *testing.T, c v1beta1.Chart) { + require.NotNil(t, c.Local) require.Equal(t, "./charts/my-chart", c.Local.Path) }, }, @@ -389,7 +378,7 @@ func TestV1Alpha1PkgToV1Beta1_ChartSources(t *testing.T) { } } -func TestV1Alpha1PkgToV1Beta1_ManifestNoWaitInversion(t *testing.T) { +func TestV1Alpha1PkgToV1Beta1_ManifestSkipWait(t *testing.T) { t.Parallel() pkg := v1alpha1.ZarfPackage{ Kind: v1alpha1.ZarfPackageConfig, @@ -413,14 +402,10 @@ func TestV1Alpha1PkgToV1Beta1_ManifestNoWaitInversion(t *testing.T) { require.Len(t, result.Components[0].Manifests, 2) - // NoWait=true → Wait=false - m0 := result.Components[0].Manifests[0] - require.NotNil(t, m0.Wait) - require.False(t, *m0.Wait) - - // NoWait=false (default) → Wait=nil (v1beta1 defaults to true) - m1 := result.Components[0].Manifests[1] - require.Nil(t, m1.Wait) + // NoWait=true → SkipWait=true. + require.True(t, result.Components[0].Manifests[0].SkipWait) + // NoWait=false → SkipWait=false. + require.False(t, result.Components[0].Manifests[1].SkipWait) } func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { @@ -482,10 +467,9 @@ func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { actions := result.Components[0].Actions // Defaults - require.True(t, actions.OnDeploy.Defaults.Mute) - require.NotNil(t, actions.OnDeploy.Defaults.Timeout) - require.Equal(t, 60*time.Second, actions.OnDeploy.Defaults.Timeout.Duration) - require.Equal(t, 2, actions.OnDeploy.Defaults.Retries) + require.True(t, actions.OnDeploy.Defaults.Silent) + require.Equal(t, int32(60), actions.OnDeploy.Defaults.MaxTotalSeconds) + require.Equal(t, int32(2), actions.OnDeploy.Defaults.Retries) require.Equal(t, "/work", actions.OnDeploy.Defaults.Dir) require.Equal(t, []string{"FOO=bar"}, actions.OnDeploy.Defaults.Env) require.Equal(t, "bash", actions.OnDeploy.Defaults.Shell.Linux) @@ -494,22 +478,24 @@ func TestV1Alpha1PkgToV1Beta1_Actions(t *testing.T) { require.Len(t, actions.OnDeploy.Before, 1) before := actions.OnDeploy.Before[0] require.Equal(t, "echo before", before.Cmd) - require.NotNil(t, before.Mute) - require.True(t, *before.Mute) - require.NotNil(t, before.Timeout) - require.Equal(t, 30*time.Second, before.Timeout.Duration) - require.Equal(t, 3, before.Retries) + require.NotNil(t, before.Silent) + require.True(t, *before.Silent) + require.NotNil(t, before.MaxTotalSeconds) + require.Equal(t, int32(30), *before.MaxTotalSeconds) + require.NotNil(t, before.Retries) + require.Equal(t, int32(3), *before.Retries) require.Equal(t, "run before", before.Description) - // SetVariables should include both the explicit one and the deprecated one. - require.Len(t, before.SetVariables, 2) - require.Equal(t, "OUT_VAR", before.SetVariables[0].Name) - require.True(t, before.SetVariables[0].Sensitive) - require.Equal(t, "OLD_VAR", before.SetVariables[1].Name) - - // After should include original After + OnSuccess (merged). - require.Len(t, actions.OnDeploy.After, 2) - require.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) - require.Equal(t, "echo success", actions.OnDeploy.After[1].Cmd) + // SetVariables should include both the explicit one and the deprecated one, surfaced via the shim. + setVars := before.GetDeprecatedSetVariables() + require.Len(t, setVars, 2) + require.Equal(t, "OUT_VAR", setVars[0].Name) + require.True(t, setVars[0].Sensitive) + require.Equal(t, "OLD_VAR", setVars[1].Name) + + // OnSuccess should be the merge of v1alpha1 After + OnSuccess. + require.Len(t, actions.OnDeploy.OnSuccess, 2) + require.Equal(t, "echo after", actions.OnDeploy.OnSuccess[0].Cmd) + require.Equal(t, "echo success", actions.OnDeploy.OnSuccess[1].Cmd) // OnFailure require.Len(t, actions.OnDeploy.OnFailure, 1) @@ -544,13 +530,12 @@ func TestV1Alpha1PkgToV1Beta1_Files(t *testing.T) { require.Len(t, result.Components[0].Files, 1) f := result.Components[0].Files[0] require.Equal(t, "https://example.com/file.tar.gz", f.Source) - require.Equal(t, "deadbeef", f.Shasum) - require.Equal(t, "/opt/file.tar.gz", f.Target) + require.Equal(t, "deadbeef", f.Checksum) + require.Equal(t, "/opt/file.tar.gz", f.Destination) require.True(t, f.Executable) require.Equal(t, []string{"/usr/local/bin/file"}, f.Symlinks) require.Equal(t, "bin/file", f.ExtractPath) - require.NotNil(t, f.Template) - require.True(t, *f.Template) + require.True(t, f.EnableValues) } func TestV1Alpha1PkgToV1Beta1_ValuesAndDocumentation(t *testing.T) { @@ -602,17 +587,16 @@ func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { t.Parallel() - allowOverride := true - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ APIVersion: v1beta1.APIVersion, Kind: v1beta1.ZarfPackageConfig, - Metadata: v1beta1.ZarfMetadata{ - Name: "test-pkg", - Description: "A test package", - Version: "1.0.0", - Architecture: "amd64", - Uncompressed: true, - AllowNamespaceOverride: &allowOverride, + Metadata: v1beta1.PackageMetadata{ + Name: "test-pkg", + Description: "A test package", + Version: "1.0.0", + Architecture: "amd64", + Uncompressed: true, + PreventNamespaceOverride: false, Annotations: map[string]string{ "existing": "annotation", "metadata.url": "https://example.com", @@ -623,7 +607,7 @@ func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { "metadata.vendor": "Example Corp", }, }, - Build: v1beta1.ZarfBuildData{ + Build: v1beta1.BuildData{ AggregateChecksum: "abc123", }, } @@ -637,6 +621,7 @@ func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { require.Equal(t, "1.0.0", result.Metadata.Version) require.Equal(t, "amd64", result.Metadata.Architecture) require.True(t, result.Metadata.Uncompressed) + // PreventNamespaceOverride=false → AllowNamespaceOverride=true. require.NotNil(t, result.Metadata.AllowNamespaceOverride) require.True(t, *result.Metadata.AllowNamespaceOverride) @@ -659,10 +644,10 @@ func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { t.Parallel() signed := true - pkg := v1beta1.ZarfPackage{ - Kind: v1beta1.ZarfInitConfig, - Build: v1beta1.ZarfBuildData{ - Terminal: "my-machine", + pkg := v1beta1.Package{ + Kind: v1beta1.ZarfPackageConfig, + Build: v1beta1.BuildData{ + Hostname: "my-machine", User: "test-user", Architecture: "arm64", Timestamp: "Mon, 02 Jan 2006 15:04:05 -0700", @@ -673,7 +658,6 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { DifferentialPackageVersion: "0.29.0", Flavor: "vanilla", Signed: &signed, - APIVersion: v1beta1.APIVersion, VersionRequirements: []v1beta1.VersionRequirement{ {Version: "v0.28.0", Reason: "needs feature X"}, }, @@ -683,7 +667,7 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { result := V1Beta1PkgToV1Alpha1(pkg) - require.Equal(t, v1alpha1.ZarfInitConfig, result.Kind) + require.Equal(t, v1alpha1.ZarfPackageConfig, result.Kind) require.Equal(t, "my-machine", result.Build.Terminal) require.Equal(t, "test-user", result.Build.User) require.Equal(t, "arm64", result.Build.Architecture) @@ -695,37 +679,31 @@ func TestV1Beta1PkgToV1Alpha1_Build(t *testing.T) { require.True(t, *result.Build.Signed) require.Len(t, result.Build.VersionRequirements, 1) require.Equal(t, "v0.28.0", result.Build.VersionRequirements[0].Version) - require.Equal(t, v1beta1.APIVersion, result.Build.APIVersion) } func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { t.Parallel() - optional := true - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ Kind: v1beta1.ZarfPackageConfig, - Components: []v1beta1.ZarfComponent{ + Components: []v1beta1.Component{ { Name: "my-component", Description: "test component", - Default: true, - Optional: &optional, - Only: v1beta1.ZarfComponentOnlyTarget{ - LocalOS: "linux", - Cluster: v1beta1.ZarfComponentOnlyCluster{ - Architecture: "amd64", - Distros: []string{"k3s"}, - }, - Flavor: "vanilla", + Optional: true, + Target: v1beta1.ComponentTarget{ + OS: "linux", + Architecture: "amd64", + Flavor: "vanilla", }, - Import: v1beta1.ZarfComponentImport{ - Path: "./path", - URL: "oci://example.com/pkg", + Import: v1beta1.ComponentImport{ + Local: []v1beta1.ComponentImportLocal{{Path: "./path"}}, + Remote: []v1beta1.ComponentImportRemote{{URL: "oci://example.com/pkg"}}, }, - Images: []v1beta1.ZarfImage{ + Images: []v1beta1.Image{ {Name: "nginx:latest"}, {Name: "redis:7", Source: "daemon"}, }, - Repos: []string{"https://github.com/example/repo"}, + Repositories: []string{"https://github.com/example/repo"}, }, }, } @@ -736,21 +714,20 @@ func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { comp := result.Components[0] require.Equal(t, "my-component", comp.Name) require.Equal(t, "test component", comp.Description) - require.True(t, comp.Default) - // Optional=true → Required=false + // Optional=true → Required=false. require.NotNil(t, comp.Required) require.False(t, *comp.Required) require.Equal(t, "linux", comp.Only.LocalOS) require.Equal(t, "amd64", comp.Only.Cluster.Architecture) - require.Equal(t, []string{"k3s"}, comp.Only.Cluster.Distros) require.Equal(t, "vanilla", comp.Only.Flavor) + // v1beta1 Local[0] / Remote[0] project back onto v1alpha1 Import.Path/URL. require.Equal(t, "./path", comp.Import.Path) require.Equal(t, "oci://example.com/pkg", comp.Import.URL) - // Images are converted from []ZarfImage to []string. + // Images are converted from []Image back to []string. require.Len(t, comp.Images, 2) require.Equal(t, "nginx:latest", comp.Images[0]) require.Equal(t, "redis:7", comp.Images[1]) @@ -762,14 +739,14 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { t.Parallel() tests := []struct { name string - chart v1beta1.ZarfChart + chart v1beta1.Chart validate func(t *testing.T, c v1alpha1.ZarfChart) }{ { name: "helm repo", - chart: v1beta1.ZarfChart{ + chart: v1beta1.Chart{ Name: "podinfo", - HelmRepo: v1beta1.HelmRepoSource{ + HelmRepository: &v1beta1.HelmRepositorySource{ URL: "https://stefanprodan.github.io/podinfo", Name: "podinfo", Version: "6.4.0", @@ -783,9 +760,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, { name: "oci registry", - chart: v1beta1.ZarfChart{ + chart: v1beta1.Chart{ Name: "podinfo", - OCI: v1beta1.OCISource{ + OCI: &v1beta1.OCISource{ URL: "oci://ghcr.io/stefanprodan/charts/podinfo", Version: "6.4.0", }, @@ -797,9 +774,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, { name: "git repo with version in url", - chart: v1beta1.ZarfChart{ + chart: v1beta1.Chart{ Name: "my-chart", - Git: v1beta1.GitRepoSource{ + Git: &v1beta1.GitSource{ URL: "https://github.com/example/repo@6.4.0", Path: "charts/my-chart", }, @@ -812,9 +789,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, { name: "git repo without version", - chart: v1beta1.ZarfChart{ + chart: v1beta1.Chart{ Name: "my-chart", - Git: v1beta1.GitRepoSource{ + Git: &v1beta1.GitSource{ URL: "https://github.com/example/repo", Path: "charts/my-chart", }, @@ -826,11 +803,9 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { }, { name: "local path", - chart: v1beta1.ZarfChart{ - Name: "local-chart", - Local: v1beta1.LocalRepoSource{ - Path: "./charts/my-chart", - }, + chart: v1beta1.Chart{ + Name: "local-chart", + Local: &v1beta1.LocalSource{Path: "./charts/my-chart"}, }, validate: func(t *testing.T, c v1alpha1.ZarfChart) { require.Equal(t, "./charts/my-chart", c.LocalPath) @@ -841,12 +816,12 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { t.Parallel() - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ Kind: v1beta1.ZarfPackageConfig, - Components: []v1beta1.ZarfComponent{ + Components: []v1beta1.Component{ { Name: "chart-comp", - Charts: []v1beta1.ZarfChart{tt.chart}, + Charts: []v1beta1.Chart{tt.chart}, }, }, } @@ -858,18 +833,17 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { } } -func TestV1Beta1PkgToV1Alpha1_ManifestWaitInversion(t *testing.T) { +func TestV1Beta1PkgToV1Alpha1_ManifestSkipWaitInversion(t *testing.T) { t.Parallel() - waitFalse := false - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ Kind: v1beta1.ZarfPackageConfig, - Components: []v1beta1.ZarfComponent{ + Components: []v1beta1.Component{ { Name: "manifest-comp", - Manifests: []v1beta1.ZarfManifest{ + Manifests: []v1beta1.Manifest{ { - Name: "wait-false", - Wait: &waitFalse, + Name: "skip-wait", + SkipWait: true, }, { Name: "default-wait", @@ -883,10 +857,9 @@ func TestV1Beta1PkgToV1Alpha1_ManifestWaitInversion(t *testing.T) { require.Len(t, result.Components[0].Manifests, 2) - // Wait=false → NoWait=true + // SkipWait=true → NoWait=true. require.True(t, result.Components[0].Manifests[0].NoWait) - - // Wait=nil → NoWait=false (default) + // SkipWait=false → NoWait=false. require.False(t, result.Components[0].Manifests[1].NoWait) } @@ -894,40 +867,41 @@ func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { t.Parallel() mute := true dir := "/tmp" + maxSec := int32(30) + retries := int32(3) + maxSecDef := int32(60) + retriesDef := int32(2) - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ Kind: v1beta1.ZarfPackageConfig, - Components: []v1beta1.ZarfComponent{ + Components: []v1beta1.Component{ { Name: "action-comp", - Actions: v1beta1.ZarfComponentActions{ - OnDeploy: v1beta1.ZarfComponentActionSet{ - Defaults: v1beta1.ZarfComponentActionDefaults{ - Mute: true, - Timeout: &metav1.Duration{Duration: 60 * time.Second}, - Retries: 2, - Dir: "/work", - Env: []string{"FOO=bar"}, + Actions: v1beta1.ComponentActions{ + OnDeploy: v1beta1.ComponentActionSet{ + Defaults: v1beta1.ComponentActionDefaults{ + Silent: true, + MaxTotalSeconds: maxSecDef, + Retries: retriesDef, + Dir: "/work", + Env: []string{"FOO=bar"}, Shell: v1beta1.Shell{ Linux: "bash", }, }, - Before: []v1beta1.ZarfComponentAction{ + Before: []v1beta1.ComponentAction{ { - Cmd: "echo before", - Mute: &mute, - Timeout: &metav1.Duration{Duration: 30 * time.Second}, - Retries: 3, - Dir: &dir, - SetVariables: []v1beta1.Variable{ - {Name: "OUT_VAR", Sensitive: true}, - }, + Cmd: "echo before", + Silent: &mute, + MaxTotalSeconds: &maxSec, + Retries: &retries, + Dir: &dir, }, }, - After: []v1beta1.ZarfComponentAction{ + OnSuccess: []v1beta1.ComponentAction{ {Cmd: "echo after"}, }, - OnFailure: []v1beta1.ZarfComponentAction{ + OnFailure: []v1beta1.ComponentAction{ {Cmd: "echo failure"}, }, }, @@ -959,47 +933,38 @@ func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { require.Equal(t, 30, *before.MaxTotalSeconds) require.NotNil(t, before.MaxRetries) require.Equal(t, 3, *before.MaxRetries) - require.Len(t, before.SetVariables, 1) - require.Equal(t, "OUT_VAR", before.SetVariables[0].Name) - require.True(t, before.SetVariables[0].Sensitive) - // After - require.Len(t, actions.OnDeploy.After, 1) - require.Equal(t, "echo after", actions.OnDeploy.After[0].Cmd) + // OnSuccess + require.Len(t, actions.OnDeploy.OnSuccess, 1) + require.Equal(t, "echo after", actions.OnDeploy.OnSuccess[0].Cmd) // OnFailure require.Len(t, actions.OnDeploy.OnFailure, 1) require.Equal(t, "echo failure", actions.OnDeploy.OnFailure[0].Cmd) } -func TestV1Beta1PkgToV1Alpha1_Variables(t *testing.T) { +func TestV1Beta1PkgToV1Alpha1_VariablesShim(t *testing.T) { t.Parallel() - pkg := v1beta1.ZarfPackage{ + pkg := v1beta1.Package{ Kind: v1beta1.ZarfPackageConfig, - Variables: []v1beta1.InteractiveVariable{ - { - Variable: v1beta1.Variable{ - Name: "MY_VAR", - Sensitive: true, - AutoIndent: true, - Pattern: "^[a-z]+$", - Type: v1beta1.FileVariableType, - }, - Description: "A variable", - Default: "default-val", - Prompt: true, - }, - }, - Constants: []v1beta1.Constant{ - { - Name: "MY_CONST", - Value: "const-val", - Description: "A constant", - AutoIndent: true, - Pattern: ".*", + } + pkg.SetDeprecatedVariables([]v1alpha1.InteractiveVariable{ + { + Variable: v1alpha1.Variable{ + Name: "MY_VAR", + Sensitive: true, + AutoIndent: true, + Pattern: "^[a-z]+$", + Type: v1alpha1.FileVariableType, }, + Description: "A variable", + Default: "default-val", + Prompt: true, }, - } + }) + pkg.SetDeprecatedConstants([]v1alpha1.Constant{ + {Name: "MY_CONST", Value: "const-val"}, + }) result := V1Beta1PkgToV1Alpha1(pkg) @@ -1007,17 +972,14 @@ func TestV1Beta1PkgToV1Alpha1_Variables(t *testing.T) { v := result.Variables[0] require.Equal(t, "MY_VAR", v.Name) require.True(t, v.Sensitive) - require.True(t, v.AutoIndent) - require.Equal(t, "^[a-z]+$", v.Pattern) require.Equal(t, v1alpha1.FileVariableType, v.Type) require.Equal(t, "A variable", v.Description) require.Equal(t, "default-val", v.Default) require.True(t, v.Prompt) require.Len(t, result.Constants, 1) - c := result.Constants[0] - require.Equal(t, "MY_CONST", c.Name) - require.Equal(t, "const-val", c.Value) + require.Equal(t, "MY_CONST", result.Constants[0].Name) + require.Equal(t, "const-val", result.Constants[0].Value) } func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { @@ -1104,7 +1066,7 @@ func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { }, } - // Round-trip: v1alpha1 → v1beta1 → v1alpha1 + // Round-trip: v1alpha1 → v1beta1 → v1alpha1. beta := V1Alpha1PkgToV1Beta1(original) result := V1Beta1PkgToV1Alpha1(beta) @@ -1134,7 +1096,7 @@ func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { require.Len(t, comp.Manifests, 1) require.True(t, comp.Manifests[0].NoWait) - // Action timeouts should round-trip through Duration conversion. + // Action timeouts should round-trip through int32 conversion. require.Equal(t, 60, comp.Actions.OnDeploy.Defaults.MaxTotalSeconds) require.Equal(t, 2, comp.Actions.OnDeploy.Defaults.MaxRetries) require.Len(t, comp.Actions.OnDeploy.Before, 1) diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go index b921d6a80a..88bbfb7ac5 100644 --- a/src/internal/api/types/package.go +++ b/src/internal/api/types/package.go @@ -3,37 +3,41 @@ // Package types holds the internal generic representation of a Zarf package used for lossless conversions between API versions. // This type is never exposed publicly. Each API version converts to/from this type, giving N conversion functions instead of N². +// The shape mirrors the latest schema (v1beta1) with extra fields appended where earlier versions carry data that does not survive +// untouched on the latest schema. package types -import ( - "github.com/zarf-dev/zarf/src/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) +import "github.com/zarf-dev/zarf/src/api/v1alpha1" -// ZarfPackage is the internal superset representation used for conversions between API versions. -type ZarfPackage struct { +// Package is the internal superset representation used for conversions between API versions. +type Package struct { APIVersion string Kind string - Metadata ZarfMetadata - Build ZarfBuildData - Components []ZarfComponent - Constants []Constant - Variables []InteractiveVariable - Values ZarfValues + Metadata PackageMetadata + Build BuildData + Components []Component + Values Values Documentation map[string]string + + // v1alpha1-only fields preserved for lossless round-trip. + Variables []v1alpha1.InteractiveVariable + Constants []v1alpha1.Constant } -// ZarfMetadata is a superset of all metadata fields across API versions. -type ZarfMetadata struct { - Name string - Description string - Version string - Uncompressed bool - Architecture string - Annotations map[string]string - AllowNamespaceOverride *bool - - // v1alpha1-only fields +// PackageMetadata is the superset of metadata fields across API versions. +type PackageMetadata struct { + Name string + Description string + Version string + Uncompressed bool + Architecture string + Annotations map[string]string + // PreventNamespaceOverride is the v1beta1 form. v1alpha1 stores AllowNamespaceOverride *bool; + // only one of these should be populated by the converter. + PreventNamespaceOverride bool + AllowNamespaceOverride *bool + + // v1alpha1-only metadata fields. v1beta1 migrates these to Annotations. URL string Image string YOLO bool @@ -41,13 +45,14 @@ type ZarfMetadata struct { Documentation string Source string Vendor string - // AggregateChecksum lives in metadata in v1alpha1, build in v1beta1 + // AggregateChecksum lives in Metadata on v1alpha1 and in Build on v1beta1. AggregateChecksum string } -// ZarfBuildData is a superset of all build fields across API versions. -type ZarfBuildData struct { - Terminal string +// BuildData is the superset of build fields across API versions. +type BuildData struct { + // Hostname is the v1beta1 name (v1alpha1: Terminal). + Hostname string User string Architecture string Timestamp string @@ -61,9 +66,10 @@ type ZarfBuildData struct { VersionRequirements []VersionRequirement ProvenanceFiles []string AggregateChecksum string - APIVersion string + // APIVersion tracks the apiVersion of the package definition used to build the package. + APIVersion string - // v1alpha1-only + // v1alpha1-only build fields. DifferentialMissing []string } @@ -73,298 +79,237 @@ type VersionRequirement struct { Reason string } -// Constant represents a template constant. -type Constant struct { - Name string - Value string - Description string - AutoIndent bool - Pattern string -} - -// Variable represents a variable. -type Variable struct { - Name string - Sensitive bool - AutoIndent bool - Pattern string - Type string -} - -// InteractiveVariable is a variable that can prompt the user. -type InteractiveVariable struct { - Variable - Description string - Default string - Prompt bool -} - -// SetVariable tracks a variable that has been set. -type SetVariable struct { - Variable - Value string -} - -// SetValue declares a value that can be set during deploy. -type SetValue struct { - Key string - Value any - Type string -} - -// ZarfValues defines values files and schema. -type ZarfValues struct { +// Values defines values files and schema. +type Values struct { Files []string Schema string } -// ZarfComponent is the internal superset of component fields. -type ZarfComponent struct { +// Component is the superset of component fields across API versions. +type Component struct { Name string Description string - Only ZarfComponentOnlyTarget - Import ZarfComponentImport - Manifests []ZarfManifest - Charts []ZarfChart - Files []ZarfFile - Images []ZarfImage + Optional bool + Target ComponentTarget + Import ComponentImport + Service string + Manifests []Manifest + Charts []Chart + Files []File + Images []Image ImageArchives []ImageArchive - Repos []string - Actions ZarfComponentActions - Features ZarfComponentFeatures + Repositories []string + Actions ComponentActions - // v1alpha1-only fields preserved for lossless conversion + // v1alpha1-only fields preserved for lossless round-trip. Default bool Required *bool - Optional *bool Group string DataInjections []v1alpha1.ZarfDataInjection HealthChecks []v1alpha1.NamespacedObjectKindReference - // v1alpha1 chart variables are dropped (no shim needed, per proposal) -} - -// ZarfComponentOnlyTarget filters a component. -type ZarfComponentOnlyTarget struct { - LocalOS string - Cluster ZarfComponentOnlyCluster - Flavor string + Distros []string } -// ZarfComponentOnlyCluster represents architecture and distro filters. -type ZarfComponentOnlyCluster struct { +// ComponentTarget filters a component to a target OS/arch/flavor. +type ComponentTarget struct { + OS string Architecture string - Distros []string + Flavor string } -// ZarfComponentImport defines an imported component. -type ZarfComponentImport struct { - // v1alpha1-only +// ComponentImport carries imports from any API version. +type ComponentImport struct { + // v1beta1 form: separate lists of local and remote component config references. + Local []ComponentImportLocal + Remote []ComponentImportRemote + + // v1alpha1-only single-import fields. Name string Path string URL string } -// ZarfFile defines a file to deploy. -type ZarfFile struct { - Source string - Shasum string - Target string - Executable bool - Symlinks []string - ExtractPath string - Template *bool -} - -// ImageSource represents where an image is pulled from. -type ImageSource string - -const ( - // ImageSourceRegistry pulls from an OCI registry. - ImageSourceRegistry ImageSource = "registry" - // ImageSourceDaemon pulls from the local Docker daemon. - ImageSourceDaemon ImageSource = "daemon" -) - -// ZarfImage represents an OCI image. -type ZarfImage struct { - Name string - Source ImageSource -} - -// GetSource returns the image source, defaulting to ImageSourceRegistry. -func (img ZarfImage) GetSource() ImageSource { - if img.Source == "" { - return ImageSourceRegistry - } - return img.Source -} - -// ImageArchive defines a tar archive of images. -type ImageArchive struct { - Path string - Images []string +// ComponentImportLocal references a local component config file. +type ComponentImportLocal struct { + Path string } -// ZarfChart is the internal superset of chart fields. -type ZarfChart struct { - Name string - Namespace string - ReleaseName string - ValuesFiles []string - Values []ZarfChartValue +// ComponentImportRemote references a remote (OCI) component config. +type ComponentImportRemote struct { + URL string +} + +// KustomizeManifest holds kustomization settings for a manifest. +type KustomizeManifest struct { + Files []string + AllowAnyDirectory bool + EnablePlugins bool +} + +// Manifest is the superset of manifest fields across API versions. +type Manifest struct { + Name string + Namespace string + Files []string + Kustomize *KustomizeManifest + SkipWait bool + ServerSideApply string + EnableValues bool + + // v1alpha1-only round-trip fields. + Template *bool +} + +// Chart is the superset of chart fields across API versions. +type Chart struct { + Name string + Namespace string + ReleaseName string + ValuesFiles []string + Values []ChartValue + SkipSchemaValidation bool + ServerSideApply string + SkipWait bool + + // v1beta1 structured sources. + HelmRepository *HelmRepositorySource + Git *GitSource + Local *LocalSource + OCI *OCISource + + // v1alpha1-only flat source fields. Used during conversion to populate structured sources. + URL string + RepoName string + GitPath string + LocalPath string + Version string SchemaValidation *bool - ServerSideApply string - Wait *bool - - // v1beta1 structured sources - HelmRepo HelmRepoSource - Git GitRepoSource - Local LocalRepoSource - OCI OCISource - - // v1alpha1 flat fields (used during conversion to populate structured sources) - URL string - RepoName string - GitPath string - LocalPath string - Version string - NoWait bool + Variables []v1alpha1.ZarfChartVariable } -// ZarfChartValue maps a source path to a target path. -type ZarfChartValue struct { +// ChartValue maps a source path to a target path. +type ChartValue struct { SourcePath string TargetPath string } -// HelmRepoSource represents a Helm chart in a repository. -type HelmRepoSource struct { +// HelmRepositorySource represents a chart stored in a Helm repository. +type HelmRepositorySource struct { Name string URL string Version string } -// GitRepoSource represents a Helm chart in a Git repo. -type GitRepoSource struct { +// GitSource represents a chart stored in a Git repository. +type GitSource struct { URL string Path string } -// LocalRepoSource represents a local Helm chart. -type LocalRepoSource struct { +// LocalSource represents a chart stored locally. +type LocalSource struct { Path string } -// OCISource represents a Helm chart in an OCI registry. +// OCISource represents a chart stored in an OCI registry. type OCISource struct { URL string Version string } -// ZarfManifest defines raw manifests deployed as a Helm chart. -type ZarfManifest struct { - Name string - Namespace string - Files []string - KustomizeAllowAnyDirectory bool - Kustomizations []string - ServerSideApply string - Template *bool - Wait *bool - NoWait bool - - // v1alpha1-only - EnableKustomizePlugins bool -} - -// ZarfComponentFeatures defines CLI features for a component. -type ZarfComponentFeatures struct { - IsRegistry bool - Injector *Injector - IsAgent bool -} - -// Injector defines the Zarf injector configuration. -type Injector struct { - Enabled bool - Values *InjectorValues -} - -// InjectorValues defines configurable values for the injector. -type InjectorValues struct { - Tolerations string +// File is the superset of file fields across API versions. +type File struct { + Source string + Checksum string + Destination string + Executable bool + Symlinks []string + ExtractPath string + EnableValues bool } -// ZarfComponentActions are action sets mapped to lifecycle operations. -type ZarfComponentActions struct { - OnCreate ZarfComponentActionSet - OnDeploy ZarfComponentActionSet - OnRemove ZarfComponentActionSet +// Image represents an OCI image in the package. +type Image struct { + Name string + Source string } -// ZarfComponentActionSet is a set of actions for a lifecycle operation. -type ZarfComponentActionSet struct { - Defaults ZarfComponentActionDefaults - Before []ZarfComponentAction - After []ZarfComponentAction - OnSuccess []ZarfComponentAction // v1alpha1-only, merged into After for v1beta1 - OnFailure []ZarfComponentAction +// ImageArchive defines a tar archive of images to include in the package. +type ImageArchive struct { + Path string + Images []string } -// ZarfComponentActionDefaults sets default configs for child actions. -type ZarfComponentActionDefaults struct { - Mute bool - Timeout *metav1.Duration - Retries int - Dir string - Env []string - Shell Shell - - // v1alpha1 fields - MaxTotalSeconds int - MaxRetries int +// ComponentActions are ActionSets mapped to package lifecycle operations. +type ComponentActions struct { + OnCreate ComponentActionSet + OnDeploy ComponentActionSet + OnRemove ComponentActionSet +} + +// ComponentActionSet is a set of actions for one lifecycle operation. +type ComponentActionSet struct { + Defaults ComponentActionDefaults + Before []ComponentAction + OnSuccess []ComponentAction + OnFailure []ComponentAction +} + +// ComponentActionDefaults sets defaults for child actions. +type ComponentActionDefaults struct { + Silent bool + MaxTotalSeconds int32 + Retries int32 + Dir string + Env []string + Shell Shell +} + +// ComponentAction is the superset of action fields across API versions. +type ComponentAction struct { + Silent *bool + MaxTotalSeconds *int32 + Retries *int32 + Dir *string + Env []string + Cmd string + Shell *Shell + SetValues []SetValue + Description string + Wait *ComponentActionWait + EnableValues bool + + // v1alpha1-only round-trip fields. + SetVariables []v1alpha1.Variable + DeprecatedSetVariable string } -// ZarfComponentAction represents a single action. -type ZarfComponentAction struct { - Mute *bool - Timeout *metav1.Duration - Retries int - Dir *string - Env []string - Cmd string - Shell *Shell - SetVariables []Variable - SetValues []SetValue - Description string - Wait *ZarfComponentActionWait - Template *bool - - // v1alpha1 fields - MaxTotalSeconds *int - MaxRetries *int - DeprecatedSetVariable string +// SetValue declares a value that can be set during a deploy. +type SetValue struct { + Key string + Value any + Type string } -// ZarfComponentActionWait specifies a wait condition. -type ZarfComponentActionWait struct { - Cluster *ZarfComponentActionWaitCluster - Network *ZarfComponentActionWaitNetwork +// ComponentActionWait specifies a condition to wait for before continuing. +type ComponentActionWait struct { + Cluster *ComponentActionWaitCluster + Network *ComponentActionWaitNetwork } -// ZarfComponentActionWaitCluster specifies a cluster wait condition. -type ZarfComponentActionWaitCluster struct { +// ComponentActionWaitCluster specifies a cluster-level wait condition. +type ComponentActionWaitCluster struct { Kind string Name string Namespace string Condition string } -// ZarfComponentActionWaitNetwork specifies a network wait condition. -type ZarfComponentActionWaitNetwork struct { +// ComponentActionWaitNetwork specifies a network-level wait condition. +type ComponentActionWaitNetwork struct { Protocol string Address string - Code int + Code int32 } // Shell represents shell preferences per OS. diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index c4081fa4dd..af33672c78 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors +// Package v1alpha1 contains functions for converting between the public v1alpha1 Zarf package and the internal generic representation. package v1alpha1 import ( @@ -9,15 +10,14 @@ import ( "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/internal/api/types" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // ConvertToGeneric converts a v1alpha1 ZarfPackage to the internal generic representation. -func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { - g := types.ZarfPackage{ +func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.Package { + g := types.Package{ APIVersion: pkg.APIVersion, Kind: string(pkg.Kind), - Metadata: types.ZarfMetadata{ + Metadata: types.PackageMetadata{ Name: pkg.Metadata.Name, Description: pkg.Metadata.Description, Version: pkg.Metadata.Version, @@ -34,8 +34,8 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { Vendor: pkg.Metadata.Vendor, AggregateChecksum: pkg.Metadata.AggregateChecksum, }, - Build: types.ZarfBuildData{ - Terminal: pkg.Build.Terminal, + Build: types.BuildData{ + Hostname: pkg.Build.Terminal, User: pkg.Build.User, Architecture: pkg.Build.Architecture, Timestamp: pkg.Build.Timestamp, @@ -46,15 +46,16 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, Flavor: pkg.Build.Flavor, Signed: pkg.Build.Signed, - APIVersion: pkg.Build.APIVersion, DifferentialMissing: pkg.Build.DifferentialMissing, ProvenanceFiles: pkg.Build.ProvenanceFiles, }, - Values: types.ZarfValues{ + Values: types.Values{ Files: pkg.Values.Files, Schema: pkg.Values.Schema, }, Documentation: pkg.Documentation, + Variables: append([]v1alpha1.InteractiveVariable(nil), pkg.Variables...), + Constants: append([]v1alpha1.Constant(nil), pkg.Constants...), } for _, vr := range pkg.Build.VersionRequirements { @@ -64,40 +65,15 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.ZarfPackage { }) } - for _, c := range pkg.Constants { - g.Constants = append(g.Constants, types.Constant{ - Name: c.Name, - Value: c.Value, - Description: c.Description, - AutoIndent: c.AutoIndent, - Pattern: c.Pattern, - }) - } - - for _, v := range pkg.Variables { - g.Variables = append(g.Variables, types.InteractiveVariable{ - Variable: types.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: string(v.Type), - }, - Description: v.Description, - Default: v.Default, - Prompt: v.Prompt, - }) - } - for _, c := range pkg.Components { - g.Components = append(g.Components, convertV1Alpha1Component(c)) + g.Components = append(g.Components, componentToGeneric(c)) } return g } -func convertV1Alpha1Component(c v1alpha1.ZarfComponent) types.ZarfComponent { - gc := types.ZarfComponent{ +func componentToGeneric(c v1alpha1.ZarfComponent) types.Component { + gc := types.Component{ Name: c.Name, Description: c.Description, Default: c.Default, @@ -105,72 +81,43 @@ func convertV1Alpha1Component(c v1alpha1.ZarfComponent) types.ZarfComponent { Group: c.DeprecatedGroup, DataInjections: c.DataInjections, HealthChecks: c.HealthChecks, - Repos: c.Repos, - Features: inferFeaturesFromName(c.Name), - Only: types.ZarfComponentOnlyTarget{ - LocalOS: c.Only.LocalOS, - Cluster: types.ZarfComponentOnlyCluster{ - Architecture: c.Only.Cluster.Architecture, - Distros: c.Only.Cluster.Distros, - }, - Flavor: c.Only.Flavor, + Repositories: c.Repos, + Target: types.ComponentTarget{ + OS: c.Only.LocalOS, + Architecture: c.Only.Cluster.Architecture, + Flavor: c.Only.Flavor, }, - Import: types.ZarfComponentImport{ + Distros: c.Only.Cluster.Distros, + Import: types.ComponentImport{ Name: c.Import.Name, Path: c.Import.Path, URL: c.Import.URL, }, - Actions: convertV1Alpha1Actions(c.Actions), + Actions: actionsToGeneric(c.Actions), } for _, m := range c.Manifests { - gc.Manifests = append(gc.Manifests, types.ZarfManifest{ - Name: m.Name, - Namespace: m.Namespace, - Files: m.Files, - KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, - Kustomizations: m.Kustomizations, - ServerSideApply: m.ServerSideApply, - Template: m.Template, - NoWait: m.NoWait, - EnableKustomizePlugins: m.EnableKustomizePlugins, - }) + gc.Manifests = append(gc.Manifests, manifestToGeneric(m)) } for _, ch := range c.Charts { - gc.Charts = append(gc.Charts, types.ZarfChart{ - Name: ch.Name, - Namespace: ch.Namespace, - ReleaseName: ch.ReleaseName, - ValuesFiles: ch.ValuesFiles, - SchemaValidation: ch.SchemaValidation, - ServerSideApply: ch.ServerSideApply, - NoWait: ch.NoWait, - URL: ch.URL, - RepoName: ch.RepoName, - GitPath: ch.GitPath, - LocalPath: ch.LocalPath, - Version: ch.Version, - Values: convertV1Alpha1ChartValues(ch.Values), - }) + gc.Charts = append(gc.Charts, chartToGeneric(ch)) } for _, f := range c.Files { - gc.Files = append(gc.Files, types.ZarfFile{ - Source: f.Source, - Shasum: f.Shasum, - Target: f.Target, - Executable: f.Executable, - Symlinks: f.Symlinks, - ExtractPath: f.ExtractPath, - Template: f.Template, + gc.Files = append(gc.Files, types.File{ + Source: f.Source, + Checksum: f.Shasum, + Destination: f.Target, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + EnableValues: derefBool(f.Template), }) } for _, img := range c.Images { - gc.Images = append(gc.Images, types.ZarfImage{ - Name: img, - }) + gc.Images = append(gc.Images, types.Image{Name: img}) } for _, ia := range c.ImageArchives { @@ -183,10 +130,51 @@ func convertV1Alpha1Component(c v1alpha1.ZarfComponent) types.ZarfComponent { return gc } -func convertV1Alpha1ChartValues(vals []v1alpha1.ZarfChartValue) []types.ZarfChartValue { - var out []types.ZarfChartValue +func manifestToGeneric(m v1alpha1.ZarfManifest) types.Manifest { + gm := types.Manifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + SkipWait: m.NoWait, + ServerSideApply: m.ServerSideApply, + EnableValues: derefBool(m.Template), + Template: m.Template, + } + if len(m.Kustomizations) > 0 || m.KustomizeAllowAnyDirectory || m.EnableKustomizePlugins { + gm.Kustomize = &types.KustomizeManifest{ + Files: m.Kustomizations, + AllowAnyDirectory: m.KustomizeAllowAnyDirectory, + EnablePlugins: m.EnableKustomizePlugins, + } + } + return gm +} + +func chartToGeneric(ch v1alpha1.ZarfChart) types.Chart { + gc := types.Chart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SkipSchemaValidation: ch.SchemaValidation != nil && !*ch.SchemaValidation, + ServerSideApply: ch.ServerSideApply, + SkipWait: ch.NoWait, + URL: ch.URL, + RepoName: ch.RepoName, + GitPath: ch.GitPath, + LocalPath: ch.LocalPath, + Version: ch.Version, + SchemaValidation: ch.SchemaValidation, + Variables: ch.Variables, + Values: chartValuesToGeneric(ch.Values), + } + return gc +} + +func chartValuesToGeneric(vals []v1alpha1.ZarfChartValue) []types.ChartValue { + var out []types.ChartValue for _, v := range vals { - out = append(out, types.ZarfChartValue{ + out = append(out, types.ChartValue{ SourcePath: v.SourcePath, TargetPath: v.TargetPath, }) @@ -194,66 +182,67 @@ func convertV1Alpha1ChartValues(vals []v1alpha1.ZarfChartValue) []types.ZarfChar return out } -func convertV1Alpha1Actions(a v1alpha1.ZarfComponentActions) types.ZarfComponentActions { - return types.ZarfComponentActions{ - OnCreate: convertV1Alpha1ActionSet(a.OnCreate), - OnDeploy: convertV1Alpha1ActionSet(a.OnDeploy), - OnRemove: convertV1Alpha1ActionSet(a.OnRemove), +func actionsToGeneric(a v1alpha1.ZarfComponentActions) types.ComponentActions { + return types.ComponentActions{ + OnCreate: actionSetToGeneric(a.OnCreate), + OnDeploy: actionSetToGeneric(a.OnDeploy), + OnRemove: actionSetToGeneric(a.OnRemove), } } -func convertV1Alpha1ActionSet(s v1alpha1.ZarfComponentActionSet) types.ZarfComponentActionSet { - return types.ZarfComponentActionSet{ - Defaults: types.ZarfComponentActionDefaults{ - Mute: s.Defaults.Mute, - MaxTotalSeconds: s.Defaults.MaxTotalSeconds, - MaxRetries: s.Defaults.MaxRetries, - Dir: s.Defaults.Dir, - Env: s.Defaults.Env, - Shell: types.Shell{ - Windows: s.Defaults.Shell.Windows, - Linux: s.Defaults.Shell.Linux, - Darwin: s.Defaults.Shell.Darwin, - }, +func actionSetToGeneric(s v1alpha1.ZarfComponentActionSet) types.ComponentActionSet { + defaults := types.ComponentActionDefaults{ + Silent: s.Defaults.Mute, + MaxTotalSeconds: clampToInt32(s.Defaults.MaxTotalSeconds), + Retries: clampToInt32(s.Defaults.MaxRetries), + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: types.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, }, - Before: convertV1Alpha1ActionSlice(s.Before), - After: convertV1Alpha1ActionSlice(s.After), - OnSuccess: convertV1Alpha1ActionSlice(s.OnSuccess), - OnFailure: convertV1Alpha1ActionSlice(s.OnFailure), + } + + onSuccess := actionSliceToGeneric(s.After) + onSuccess = append(onSuccess, actionSliceToGeneric(s.OnSuccess)...) + + return types.ComponentActionSet{ + Defaults: defaults, + Before: actionSliceToGeneric(s.Before), + OnSuccess: onSuccess, + OnFailure: actionSliceToGeneric(s.OnFailure), } } -func convertV1Alpha1ActionSlice(actions []v1alpha1.ZarfComponentAction) []types.ZarfComponentAction { - var out []types.ZarfComponentAction +func actionSliceToGeneric(actions []v1alpha1.ZarfComponentAction) []types.ComponentAction { + var out []types.ComponentAction for _, a := range actions { - out = append(out, convertV1Alpha1Action(a)) + out = append(out, actionToGeneric(a)) } return out } -func convertV1Alpha1Action(a v1alpha1.ZarfComponentAction) types.ZarfComponentAction { - ga := types.ZarfComponentAction{ - Mute: a.Mute, - Retries: derefIntOr(a.MaxRetries, 0), +func actionToGeneric(a v1alpha1.ZarfComponentAction) types.ComponentAction { + ga := types.ComponentAction{ + Silent: a.Mute, Dir: a.Dir, Env: a.Env, Cmd: a.Cmd, Description: a.Description, - Wait: convertV1Alpha1Wait(a.Wait), - Template: a.Template, - MaxTotalSeconds: a.MaxTotalSeconds, - MaxRetries: a.MaxRetries, + Wait: waitToGeneric(a.Wait), + EnableValues: derefBool(a.Template), + SetVariables: a.SetVariables, DeprecatedSetVariable: a.DeprecatedSetVariable, } - for _, v := range a.SetVariables { - ga.SetVariables = append(ga.SetVariables, types.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: string(v.Type), - }) + if a.MaxTotalSeconds != nil { + v := clampToInt32(*a.MaxTotalSeconds) + ga.MaxTotalSeconds = &v + } + if a.MaxRetries != nil { + v := clampToInt32(*a.MaxRetries) + ga.Retries = &v } for _, sv := range a.SetValues { @@ -275,13 +264,13 @@ func convertV1Alpha1Action(a v1alpha1.ZarfComponentAction) types.ZarfComponentAc return ga } -func convertV1Alpha1Wait(w *v1alpha1.ZarfComponentActionWait) *types.ZarfComponentActionWait { +func waitToGeneric(w *v1alpha1.ZarfComponentActionWait) *types.ComponentActionWait { if w == nil { return nil } - gw := &types.ZarfComponentActionWait{} + gw := &types.ComponentActionWait{} if w.Cluster != nil { - gw.Cluster = &types.ZarfComponentActionWaitCluster{ + gw.Cluster = &types.ComponentActionWaitCluster{ Kind: w.Cluster.Kind, Name: w.Cluster.Name, Namespace: w.Cluster.Namespace, @@ -289,87 +278,40 @@ func convertV1Alpha1Wait(w *v1alpha1.ZarfComponentActionWait) *types.ZarfCompone } } if w.Network != nil { - gw.Network = &types.ZarfComponentActionWaitNetwork{ + gw.Network = &types.ComponentActionWaitNetwork{ Protocol: w.Network.Protocol, Address: w.Network.Address, - Code: w.Network.Code, + Code: clampToInt32(w.Network.Code), } } return gw } -// inferFeaturesFromName infers v1beta1-style Features from v1alpha1 component names. -// v1alpha1 has no Features field; these well-known component names encode the same semantics. -func inferFeaturesFromName(name string) types.ZarfComponentFeatures { - switch name { - case "zarf-registry", "zarf-injector": - return types.ZarfComponentFeatures{IsRegistry: true} - case "zarf-seed-registry": - return types.ZarfComponentFeatures{ - IsRegistry: true, - Injector: &types.Injector{Enabled: true}, - } - case "zarf-agent": - return types.ZarfComponentFeatures{IsAgent: true} - default: - return types.ZarfComponentFeatures{} - } -} - -func derefIntOr(p *int, def int) int { - if p != nil { - return *p - } - return def -} - // ConvertFromGeneric converts the internal generic representation to a v1alpha1 ZarfPackage. -func ConvertFromGeneric(g types.ZarfPackage) v1alpha1.ZarfPackage { +func ConvertFromGeneric(g types.Package) v1alpha1.ZarfPackage { pkg := v1alpha1.ZarfPackage{ - APIVersion: v1alpha1.APIVersion, - Kind: v1alpha1.ZarfPackageKind(g.Kind), - Metadata: convertGenericToV1Alpha1Metadata(g.Metadata, g.Build), - Build: convertGenericToV1Alpha1Build(g.Build), - Values: v1alpha1.ZarfValues{ - Files: g.Values.Files, - Schema: g.Values.Schema, - }, + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageKind(g.Kind), + Metadata: metadataFromGeneric(g.Metadata, g.Build), + Build: buildFromGeneric(g.Build), + Values: v1alpha1.ZarfValues{Files: g.Values.Files, Schema: g.Values.Schema}, Documentation: g.Documentation, + Variables: append([]v1alpha1.InteractiveVariable(nil), g.Variables...), + Constants: append([]v1alpha1.Constant(nil), g.Constants...), } - for _, c := range g.Constants { - pkg.Constants = append(pkg.Constants, v1alpha1.Constant{ - Name: c.Name, - Value: c.Value, - Description: c.Description, - AutoIndent: c.AutoIndent, - Pattern: c.Pattern, - }) - } - - for _, v := range g.Variables { - pkg.Variables = append(pkg.Variables, v1alpha1.InteractiveVariable{ - Variable: v1alpha1.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: v1alpha1.VariableType(v.Type), - }, - Description: v.Description, - Default: v.Default, - Prompt: v.Prompt, - }) + if pkg.Kind == "" { + pkg.Kind = v1alpha1.ZarfPackageConfig } for _, c := range g.Components { - pkg.Components = append(pkg.Components, convertGenericToV1Alpha1Component(c)) + pkg.Components = append(pkg.Components, componentFromGeneric(c)) } return pkg } -func convertGenericToV1Alpha1Metadata(m types.ZarfMetadata, b types.ZarfBuildData) v1alpha1.ZarfMetadata { +func metadataFromGeneric(m types.PackageMetadata, b types.BuildData) v1alpha1.ZarfMetadata { meta := v1alpha1.ZarfMetadata{ Name: m.Name, Description: m.Description, @@ -386,10 +328,17 @@ func convertGenericToV1Alpha1Metadata(m types.ZarfMetadata, b types.ZarfBuildDat Vendor: m.Vendor, } - // AggregateChecksum: prefer metadata (v1alpha1 native location), fall back to build (v1beta1 location). - if m.AggregateChecksum != "" { + // If we only have the v1beta1 form of namespace override, project it back to v1alpha1. + if meta.AllowNamespaceOverride == nil { + allow := !m.PreventNamespaceOverride + meta.AllowNamespaceOverride = &allow + } + + // AggregateChecksum: prefer the v1alpha1 native location, fall back to build (v1beta1 location). + switch { + case m.AggregateChecksum != "": meta.AggregateChecksum = m.AggregateChecksum - } else if b.AggregateChecksum != "" { + case b.AggregateChecksum != "": meta.AggregateChecksum = b.AggregateChecksum } @@ -422,9 +371,9 @@ func convertGenericToV1Alpha1Metadata(m types.ZarfMetadata, b types.ZarfBuildDat return meta } -func convertGenericToV1Alpha1Build(b types.ZarfBuildData) v1alpha1.ZarfBuildData { +func buildFromGeneric(b types.BuildData) v1alpha1.ZarfBuildData { out := v1alpha1.ZarfBuildData{ - Terminal: b.Terminal, + Terminal: b.Hostname, User: b.User, Architecture: b.Architecture, Timestamp: b.Timestamp, @@ -436,7 +385,6 @@ func convertGenericToV1Alpha1Build(b types.ZarfBuildData) v1alpha1.ZarfBuildData DifferentialMissing: b.DifferentialMissing, Flavor: b.Flavor, Signed: b.Signed, - APIVersion: b.APIVersion, ProvenanceFiles: b.ProvenanceFiles, } @@ -450,137 +398,148 @@ func convertGenericToV1Alpha1Build(b types.ZarfBuildData) v1alpha1.ZarfBuildData return out } -func convertGenericToV1Alpha1Component(c types.ZarfComponent) v1alpha1.ZarfComponent { - gc := v1alpha1.ZarfComponent{ +func componentFromGeneric(c types.Component) v1alpha1.ZarfComponent { + ac := v1alpha1.ZarfComponent{ Name: c.Name, Description: c.Description, Default: c.Default, - Required: convertOptionalToRequired(c.Optional, c.Required), + Required: requiredFromGeneric(c.Optional, c.Required), DeprecatedGroup: c.Group, DataInjections: c.DataInjections, HealthChecks: c.HealthChecks, - Repos: c.Repos, + Repos: c.Repositories, Only: v1alpha1.ZarfComponentOnlyTarget{ - LocalOS: c.Only.LocalOS, + LocalOS: c.Target.OS, Cluster: v1alpha1.ZarfComponentOnlyCluster{ - Architecture: c.Only.Cluster.Architecture, - Distros: c.Only.Cluster.Distros, + Architecture: c.Target.Architecture, + Distros: c.Distros, }, - Flavor: c.Only.Flavor, + Flavor: c.Target.Flavor, }, Import: v1alpha1.ZarfComponentImport{ Name: c.Import.Name, Path: c.Import.Path, URL: c.Import.URL, }, - Actions: convertGenericToV1Alpha1Actions(c.Actions), + Actions: actionsFromGeneric(c.Actions), + } + + // If the v1alpha1 single-path import fields are empty but the v1beta1 lists have one entry, project it back. + if ac.Import.Path == "" && len(c.Import.Local) > 0 { + ac.Import.Path = c.Import.Local[0].Path + } + if ac.Import.URL == "" && len(c.Import.Remote) > 0 { + ac.Import.URL = c.Import.Remote[0].URL } for _, m := range c.Manifests { - gc.Manifests = append(gc.Manifests, convertGenericToV1Alpha1Manifest(m)) + ac.Manifests = append(ac.Manifests, manifestFromGeneric(m)) } for _, ch := range c.Charts { - gc.Charts = append(gc.Charts, convertGenericToV1Alpha1Chart(ch)) + ac.Charts = append(ac.Charts, chartFromGeneric(ch)) } for _, f := range c.Files { - gc.Files = append(gc.Files, v1alpha1.ZarfFile{ + af := v1alpha1.ZarfFile{ Source: f.Source, - Shasum: f.Shasum, - Target: f.Target, + Shasum: f.Checksum, + Target: f.Destination, Executable: f.Executable, Symlinks: f.Symlinks, ExtractPath: f.ExtractPath, - Template: f.Template, - }) + } + if f.EnableValues { + t := true + af.Template = &t + } + ac.Files = append(ac.Files, af) } for _, img := range c.Images { - gc.Images = append(gc.Images, img.Name) + ac.Images = append(ac.Images, img.Name) } for _, ia := range c.ImageArchives { - gc.ImageArchives = append(gc.ImageArchives, v1alpha1.ImageArchive{ + ac.ImageArchives = append(ac.ImageArchives, v1alpha1.ImageArchive{ Path: ia.Path, Images: ia.Images, }) } - return gc + return ac } -// convertOptionalToRequired maps back from the generic superset to v1alpha1 Required. -// If the original Required is preserved (from a v1alpha1 origin), use it directly. -// If only Optional is set (from a v1beta1 origin), invert it. -func convertOptionalToRequired(optional *bool, required *bool) *bool { +// requiredFromGeneric maps the v1beta1 Optional and v1alpha1 Required back to v1alpha1 Required. +// Prefers preserved v1alpha1 Required; otherwise inverts Optional. +func requiredFromGeneric(optional bool, required *bool) *bool { if required != nil { return required } - if optional == nil { - return nil - } - inverted := !*optional - return &inverted + v := !optional + return &v } -func convertGenericToV1Alpha1Manifest(m types.ZarfManifest) v1alpha1.ZarfManifest { +func manifestFromGeneric(m types.Manifest) v1alpha1.ZarfManifest { am := v1alpha1.ZarfManifest{ - Name: m.Name, - Namespace: m.Namespace, - Files: m.Files, - KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, - Kustomizations: m.Kustomizations, - ServerSideApply: m.ServerSideApply, - Template: m.Template, - NoWait: m.NoWait, - EnableKustomizePlugins: m.EnableKustomizePlugins, + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + ServerSideApply: m.ServerSideApply, + NoWait: m.SkipWait, + Template: m.Template, + } + if m.Kustomize != nil { + am.Kustomizations = m.Kustomize.Files + am.KustomizeAllowAnyDirectory = m.Kustomize.AllowAnyDirectory + am.EnableKustomizePlugins = m.Kustomize.EnablePlugins + } + if am.Template == nil && m.EnableValues { + t := true + am.Template = &t } - - // Invert Wait → NoWait if Wait was explicitly set and NoWait wasn't already. - if !am.NoWait && m.Wait != nil { - am.NoWait = !*m.Wait - } - return am } -func convertGenericToV1Alpha1Chart(ch types.ZarfChart) v1alpha1.ZarfChart { +func chartFromGeneric(ch types.Chart) v1alpha1.ZarfChart { ac := v1alpha1.ZarfChart{ - Name: ch.Name, - Namespace: ch.Namespace, - ReleaseName: ch.ReleaseName, - ValuesFiles: ch.ValuesFiles, - SchemaValidation: ch.SchemaValidation, - ServerSideApply: ch.ServerSideApply, - NoWait: ch.NoWait, - URL: ch.URL, - RepoName: ch.RepoName, - GitPath: ch.GitPath, - LocalPath: ch.LocalPath, - Version: ch.Version, - } - - // Invert Wait → NoWait if Wait was explicitly set and NoWait wasn't already. - if !ac.NoWait && ch.Wait != nil { - ac.NoWait = !*ch.Wait - } - - // If flat fields are empty but structured sources are populated, convert back to flat. + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + ServerSideApply: ch.ServerSideApply, + NoWait: ch.SkipWait, + URL: ch.URL, + RepoName: ch.RepoName, + GitPath: ch.GitPath, + LocalPath: ch.LocalPath, + Version: ch.Version, + Variables: ch.Variables, + } + + // Prefer preserved v1alpha1 SchemaValidation; otherwise derive from SkipSchemaValidation. + if ch.SchemaValidation != nil { + ac.SchemaValidation = ch.SchemaValidation + } else if ch.SkipSchemaValidation { + f := false + ac.SchemaValidation = &f + } + + // If flat fields are empty but structured sources are populated, project them onto the flat fields. if ac.URL == "" && ac.LocalPath == "" { switch { - case ch.HelmRepo.URL != "": - ac.URL = ch.HelmRepo.URL - ac.RepoName = ch.HelmRepo.Name + case ch.HelmRepository != nil && ch.HelmRepository.URL != "": + ac.URL = ch.HelmRepository.URL + ac.RepoName = ch.HelmRepository.Name if ac.Version == "" { - ac.Version = ch.HelmRepo.Version + ac.Version = ch.HelmRepository.Version } - case ch.OCI.URL != "": + case ch.OCI != nil && ch.OCI.URL != "": ac.URL = ch.OCI.URL if ac.Version == "" { ac.Version = ch.OCI.Version } - case ch.Git.URL != "": + case ch.Git != nil && ch.Git.URL != "": gitURL := ch.Git.URL if idx := strings.LastIndex(gitURL, "@"); idx > 0 { if ac.Version == "" { @@ -590,7 +549,7 @@ func convertGenericToV1Alpha1Chart(ch types.ZarfChart) v1alpha1.ZarfChart { } ac.URL = gitURL ac.GitPath = ch.Git.Path - case ch.Local.Path != "": + case ch.Local != nil && ch.Local.Path != "": ac.LocalPath = ch.Local.Path } } @@ -605,105 +564,71 @@ func convertGenericToV1Alpha1Chart(ch types.ZarfChart) v1alpha1.ZarfChart { return ac } -func convertGenericToV1Alpha1Actions(a types.ZarfComponentActions) v1alpha1.ZarfComponentActions { +func actionsFromGeneric(a types.ComponentActions) v1alpha1.ZarfComponentActions { return v1alpha1.ZarfComponentActions{ - OnCreate: convertGenericToV1Alpha1ActionSet(a.OnCreate), - OnDeploy: convertGenericToV1Alpha1ActionSet(a.OnDeploy), - OnRemove: convertGenericToV1Alpha1ActionSet(a.OnRemove), + OnCreate: actionSetFromGeneric(a.OnCreate), + OnDeploy: actionSetFromGeneric(a.OnDeploy), + OnRemove: actionSetFromGeneric(a.OnRemove), } } -func convertGenericToV1Alpha1ActionSet(s types.ZarfComponentActionSet) v1alpha1.ZarfComponentActionSet { - return v1alpha1.ZarfComponentActionSet{ - Defaults: v1alpha1.ZarfComponentActionDefaults{ - Mute: s.Defaults.Mute, - MaxTotalSeconds: convertMaxTotalSeconds(s.Defaults.MaxTotalSeconds, s.Defaults.Timeout), - MaxRetries: convertMaxRetries(s.Defaults.MaxRetries, s.Defaults.Retries), - Dir: s.Defaults.Dir, - Env: s.Defaults.Env, - Shell: v1alpha1.Shell{ - Windows: s.Defaults.Shell.Windows, - Linux: s.Defaults.Shell.Linux, - Darwin: s.Defaults.Shell.Darwin, - }, +func actionSetFromGeneric(s types.ComponentActionSet) v1alpha1.ZarfComponentActionSet { + defaults := v1alpha1.ZarfComponentActionDefaults{ + Mute: s.Defaults.Silent, + MaxTotalSeconds: int(s.Defaults.MaxTotalSeconds), + MaxRetries: int(s.Defaults.Retries), + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: v1alpha1.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, }, - Before: convertGenericToV1Alpha1ActionSlice(s.Before), - After: convertGenericToV1Alpha1ActionSlice(s.After), - OnSuccess: convertGenericToV1Alpha1ActionSlice(s.OnSuccess), - OnFailure: convertGenericToV1Alpha1ActionSlice(s.OnFailure), - } -} - -// convertMaxTotalSeconds returns the v1alpha1 MaxTotalSeconds value. -// Prefers the preserved v1alpha1 value; falls back to converting the Duration. -func convertMaxTotalSeconds(v1alpha1Val int, timeout *metav1.Duration) int { - if v1alpha1Val > 0 { - return v1alpha1Val - } - if timeout != nil { - secs := timeout.Duration.Seconds() - if secs > math.MaxInt32 { - return math.MaxInt32 - } - return int(secs) } - return 0 -} -// convertMaxRetries returns the v1alpha1 MaxRetries value. -// Prefers the preserved v1alpha1 value; falls back to the generic Retries. -func convertMaxRetries(v1alpha1Val int, retries int) int { - if v1alpha1Val > 0 { - return v1alpha1Val + return v1alpha1.ZarfComponentActionSet{ + Defaults: defaults, + Before: actionSliceFromGeneric(s.Before), + OnSuccess: actionSliceFromGeneric(s.OnSuccess), + OnFailure: actionSliceFromGeneric(s.OnFailure), } - return retries } -func convertGenericToV1Alpha1ActionSlice(actions []types.ZarfComponentAction) []v1alpha1.ZarfComponentAction { +func actionSliceFromGeneric(actions []types.ComponentAction) []v1alpha1.ZarfComponentAction { var out []v1alpha1.ZarfComponentAction for _, a := range actions { - out = append(out, convertGenericToV1Alpha1Action(a)) + out = append(out, actionFromGeneric(a)) } return out } -func convertGenericToV1Alpha1Action(a types.ZarfComponentAction) v1alpha1.ZarfComponentAction { - ga := v1alpha1.ZarfComponentAction{ - Mute: a.Mute, - MaxTotalSeconds: a.MaxTotalSeconds, - MaxRetries: a.MaxRetries, +func actionFromGeneric(a types.ComponentAction) v1alpha1.ZarfComponentAction { + aa := v1alpha1.ZarfComponentAction{ + Mute: a.Silent, Dir: a.Dir, Env: a.Env, Cmd: a.Cmd, Description: a.Description, - Wait: convertGenericToV1Alpha1Wait(a.Wait), - Template: a.Template, + Wait: waitFromGeneric(a.Wait), + SetVariables: a.SetVariables, DeprecatedSetVariable: a.DeprecatedSetVariable, } - // If preserved v1alpha1 MaxTotalSeconds is nil but we have a Duration, convert it. - if ga.MaxTotalSeconds == nil && a.Timeout != nil { - secs := int(a.Timeout.Duration.Seconds()) - ga.MaxTotalSeconds = &secs + if a.MaxTotalSeconds != nil { + v := int(*a.MaxTotalSeconds) + aa.MaxTotalSeconds = &v } - - // If preserved v1alpha1 MaxRetries is nil but we have Retries, convert it. - if ga.MaxRetries == nil && a.Retries > 0 { - ga.MaxRetries = &a.Retries + if a.Retries != nil { + v := int(*a.Retries) + aa.MaxRetries = &v } - - for _, v := range a.SetVariables { - ga.SetVariables = append(ga.SetVariables, v1alpha1.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: v1alpha1.VariableType(v.Type), - }) + if a.EnableValues { + t := true + aa.Template = &t } for _, sv := range a.SetValues { - ga.SetValues = append(ga.SetValues, v1alpha1.SetValue{ + aa.SetValues = append(aa.SetValues, v1alpha1.SetValue{ Key: sv.Key, Value: sv.Value, Type: v1alpha1.SetValueType(sv.Type), @@ -711,17 +636,17 @@ func convertGenericToV1Alpha1Action(a types.ZarfComponentAction) v1alpha1.ZarfCo } if a.Shell != nil { - ga.Shell = &v1alpha1.Shell{ + aa.Shell = &v1alpha1.Shell{ Windows: a.Shell.Windows, Linux: a.Shell.Linux, Darwin: a.Shell.Darwin, } } - return ga + return aa } -func convertGenericToV1Alpha1Wait(w *types.ZarfComponentActionWait) *v1alpha1.ZarfComponentActionWait { +func waitFromGeneric(w *types.ComponentActionWait) *v1alpha1.ZarfComponentActionWait { if w == nil { return nil } @@ -738,8 +663,25 @@ func convertGenericToV1Alpha1Wait(w *types.ZarfComponentActionWait) *v1alpha1.Za aw.Network = &v1alpha1.ZarfComponentActionWaitNetwork{ Protocol: w.Network.Protocol, Address: w.Network.Address, - Code: w.Network.Code, + Code: int(w.Network.Code), } } return aw } + +func clampToInt32(v int) int32 { + if v > math.MaxInt32 { + return math.MaxInt32 + } + if v < math.MinInt32 { + return math.MinInt32 + } + return int32(v) +} + +func derefBool(p *bool) bool { + if p == nil { + return false + } + return *p +} diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 9e1e1f8494..99744aa79e 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -1,33 +1,33 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors -// Package v1beta1 contains functions for validating and converting the public v1beta1 Zarf package +// Package v1beta1 contains functions for converting between the public v1beta1 Zarf package and the internal generic representation. package v1beta1 import ( "strings" - "time" + "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" "github.com/zarf-dev/zarf/src/internal/api/types" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// ConvertToGeneric converts a v1beta1 ZarfPackage to the internal generic representation. -func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { - g := types.ZarfPackage{ +// ConvertToGeneric converts a v1beta1 Package to the internal generic representation. +func ConvertToGeneric(pkg v1beta1.Package) types.Package { + g := types.Package{ APIVersion: pkg.APIVersion, Kind: string(pkg.Kind), - Metadata: types.ZarfMetadata{ - Name: pkg.Metadata.Name, - Description: pkg.Metadata.Description, - Version: pkg.Metadata.Version, - Uncompressed: pkg.Metadata.Uncompressed, - Architecture: pkg.Metadata.Architecture, - Annotations: pkg.Metadata.Annotations, + Metadata: types.PackageMetadata{ + Name: pkg.Metadata.Name, + Description: pkg.Metadata.Description, + Version: pkg.Metadata.Version, + Uncompressed: pkg.Metadata.Uncompressed, + Architecture: pkg.Metadata.Architecture, + Annotations: pkg.Metadata.Annotations, + PreventNamespaceOverride: pkg.Metadata.PreventNamespaceOverride, }, - Build: types.ZarfBuildData{ - Terminal: pkg.Build.Terminal, + Build: types.BuildData{ + Hostname: pkg.Build.Hostname, User: pkg.Build.User, Architecture: pkg.Build.Architecture, Timestamp: pkg.Build.Timestamp, @@ -38,19 +38,18 @@ func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { DifferentialPackageVersion: pkg.Build.DifferentialPackageVersion, Flavor: pkg.Build.Flavor, Signed: pkg.Build.Signed, - APIVersion: pkg.Build.APIVersion, - AggregateChecksum: pkg.Build.AggregateChecksum, ProvenanceFiles: pkg.Build.ProvenanceFiles, + AggregateChecksum: pkg.Build.AggregateChecksum, }, - Values: types.ZarfValues{ + Values: types.Values{ Files: pkg.Values.Files, Schema: pkg.Values.Schema, }, Documentation: pkg.Documentation, + Variables: pkg.GetDeprecatedVariables(), + Constants: pkg.GetDeprecatedConstants(), } - g.Metadata.AllowNamespaceOverride = pkg.Metadata.AllowNamespaceOverride - for _, vr := range pkg.Build.VersionRequirements { g.Build.VersionRequirements = append(g.Build.VersionRequirements, types.VersionRequirement{ Version: vr.Version, @@ -58,100 +57,54 @@ func ConvertToGeneric(pkg v1beta1.ZarfPackage) types.ZarfPackage { }) } - for _, c := range pkg.Constants { - g.Constants = append(g.Constants, types.Constant{ - Name: c.Name, - Value: c.Value, - Description: c.Description, - AutoIndent: c.AutoIndent, - Pattern: c.Pattern, - }) - } - - for _, v := range pkg.Variables { - g.Variables = append(g.Variables, types.InteractiveVariable{ - Variable: types.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: string(v.Type), - }, - Description: v.Description, - Default: v.Default, - Prompt: v.Prompt, - }) - } - for _, c := range pkg.Components { - g.Components = append(g.Components, convertV1Beta1Component(c)) + g.Components = append(g.Components, componentToGeneric(c)) } return g } -func convertV1Beta1Component(c v1beta1.ZarfComponent) types.ZarfComponent { - gc := types.ZarfComponent{ - Name: c.Name, - Description: c.Description, - Default: c.Default, - Optional: c.Optional, - DataInjections: c.GetDataInjections(), - Repos: c.Repos, - Only: types.ZarfComponentOnlyTarget{ - LocalOS: c.Only.LocalOS, - Cluster: types.ZarfComponentOnlyCluster{ - Architecture: c.Only.Cluster.Architecture, - Distros: c.Only.Cluster.Distros, - }, - Flavor: c.Only.Flavor, - }, - Import: types.ZarfComponentImport{ - Path: c.Import.Path, - URL: c.Import.URL, - }, - Features: types.ZarfComponentFeatures{ - IsRegistry: c.Features.IsRegistry, - IsAgent: c.Features.IsAgent, +func componentToGeneric(c v1beta1.Component) types.Component { + gc := types.Component{ + Name: c.Name, + Description: c.Description, + Optional: c.Optional, + Service: string(c.Service), + Repositories: c.Repositories, + Target: types.ComponentTarget{ + OS: c.Target.OS, + Architecture: c.Target.Architecture, + Flavor: c.Target.Flavor, }, - Actions: convertV1Beta1Actions(c.Actions), - } - - if c.Features.Injector != nil { - gc.Features.Injector = &types.Injector{ - Enabled: c.Features.Injector.Enabled, - } - if c.Features.Injector.Values != nil { - gc.Features.Injector.Values = &types.InjectorValues{ - Tolerations: c.Features.Injector.Values.Tolerations, - } - } + Import: importToGeneric(c.Import), + Actions: actionsToGeneric(c.Actions), + DataInjections: c.GetDeprecatedDataInjections(), } for _, m := range c.Manifests { - gc.Manifests = append(gc.Manifests, convertV1Beta1Manifest(m)) + gc.Manifests = append(gc.Manifests, manifestToGeneric(m)) } for _, ch := range c.Charts { - gc.Charts = append(gc.Charts, convertV1Beta1Chart(ch)) + gc.Charts = append(gc.Charts, chartToGeneric(ch)) } for _, f := range c.Files { - gc.Files = append(gc.Files, types.ZarfFile{ - Source: f.Source, - Shasum: f.Shasum, - Target: f.Target, - Executable: f.Executable, - Symlinks: f.Symlinks, - ExtractPath: f.ExtractPath, - Template: f.Template, + gc.Files = append(gc.Files, types.File{ + Source: f.Source, + Checksum: f.Checksum, + Destination: f.Destination, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + EnableValues: f.EnableValues, }) } for _, img := range c.Images { - gc.Images = append(gc.Images, types.ZarfImage{ + gc.Images = append(gc.Images, types.Image{ Name: img.Name, - Source: types.ImageSource(img.Source), + Source: img.Source, }) } @@ -165,49 +118,74 @@ func convertV1Beta1Component(c v1beta1.ZarfComponent) types.ZarfComponent { return gc } -func convertV1Beta1Manifest(m v1beta1.ZarfManifest) types.ZarfManifest { - return types.ZarfManifest{ - Name: m.Name, - Namespace: m.Namespace, - Files: m.Files, - KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, - Kustomizations: m.Kustomizations, - ServerSideApply: m.ServerSideApply, - Template: m.Template, - Wait: m.Wait, +func importToGeneric(imp v1beta1.ComponentImport) types.ComponentImport { + out := types.ComponentImport{} + for _, l := range imp.Local { + out.Local = append(out.Local, types.ComponentImportLocal{Path: l.Path}) + } + for _, r := range imp.Remote { + out.Remote = append(out.Remote, types.ComponentImportRemote{URL: r.URL}) + } + return out +} + +func manifestToGeneric(m v1beta1.Manifest) types.Manifest { + gm := types.Manifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + SkipWait: m.SkipWait, + ServerSideApply: string(m.ServerSideApply), + EnableValues: m.EnableValues, + } + if m.Kustomize != nil { + gm.Kustomize = &types.KustomizeManifest{ + Files: m.Kustomize.Files, + AllowAnyDirectory: m.Kustomize.AllowAnyDirectory, + EnablePlugins: m.Kustomize.EnablePlugins, + } } + return gm } -func convertV1Beta1Chart(ch v1beta1.ZarfChart) types.ZarfChart { - gc := types.ZarfChart{ - Name: ch.Name, - Namespace: ch.Namespace, - ReleaseName: ch.ReleaseName, - ValuesFiles: ch.ValuesFiles, - SchemaValidation: ch.SchemaValidation, - ServerSideApply: ch.ServerSideApply, - Wait: ch.Wait, - HelmRepo: types.HelmRepoSource{ - Name: ch.HelmRepo.Name, - URL: ch.HelmRepo.URL, - Version: ch.HelmRepo.Version, - }, - Git: types.GitRepoSource{ +func chartToGeneric(ch v1beta1.Chart) types.Chart { + gc := types.Chart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SkipSchemaValidation: ch.SkipSchemaValidation, + ServerSideApply: string(ch.ServerSideApply), + SkipWait: ch.SkipWait, + Version: ch.GetDeprecatedVersion(), + Variables: ch.GetDeprecatedVariables(), + } + + if ch.HelmRepository != nil { + gc.HelmRepository = &types.HelmRepositorySource{ + Name: ch.HelmRepository.Name, + URL: ch.HelmRepository.URL, + Version: ch.HelmRepository.Version, + } + } + if ch.Git != nil { + gc.Git = &types.GitSource{ URL: ch.Git.URL, Path: ch.Git.Path, - }, - Local: types.LocalRepoSource{ - Path: ch.Local.Path, - }, - OCI: types.OCISource{ + } + } + if ch.Local != nil { + gc.Local = &types.LocalSource{Path: ch.Local.Path} + } + if ch.OCI != nil { + gc.OCI = &types.OCISource{ URL: ch.OCI.URL, Version: ch.OCI.Version, - }, - Version: ch.GetDeprecatedVersion(), + } } for _, v := range ch.Values { - gc.Values = append(gc.Values, types.ZarfChartValue{ + gc.Values = append(gc.Values, types.ChartValue{ SourcePath: v.SourcePath, TargetPath: v.TargetPath, }) @@ -216,63 +194,54 @@ func convertV1Beta1Chart(ch v1beta1.ZarfChart) types.ZarfChart { return gc } -func convertV1Beta1Actions(a v1beta1.ZarfComponentActions) types.ZarfComponentActions { - return types.ZarfComponentActions{ - OnCreate: convertV1Beta1ActionSet(a.OnCreate), - OnDeploy: convertV1Beta1ActionSet(a.OnDeploy), - OnRemove: convertV1Beta1ActionSet(a.OnRemove), +func actionsToGeneric(a v1beta1.ComponentActions) types.ComponentActions { + return types.ComponentActions{ + OnCreate: actionSetToGeneric(a.OnCreate), + OnDeploy: actionSetToGeneric(a.OnDeploy), + OnRemove: actionSetToGeneric(a.OnRemove), } } -func convertV1Beta1ActionSet(s v1beta1.ZarfComponentActionSet) types.ZarfComponentActionSet { - return types.ZarfComponentActionSet{ - Defaults: types.ZarfComponentActionDefaults{ - Mute: s.Defaults.Mute, - Timeout: s.Defaults.Timeout, - Retries: s.Defaults.Retries, - Dir: s.Defaults.Dir, - Env: s.Defaults.Env, +func actionSetToGeneric(s v1beta1.ComponentActionSet) types.ComponentActionSet { + return types.ComponentActionSet{ + Defaults: types.ComponentActionDefaults{ + Silent: s.Defaults.Silent, + MaxTotalSeconds: s.Defaults.MaxTotalSeconds, + Retries: s.Defaults.Retries, + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, Shell: types.Shell{ Windows: s.Defaults.Shell.Windows, Linux: s.Defaults.Shell.Linux, Darwin: s.Defaults.Shell.Darwin, }, }, - Before: convertV1Beta1ActionSlice(s.Before), - After: convertV1Beta1ActionSlice(s.After), - OnFailure: convertV1Beta1ActionSlice(s.OnFailure), + Before: actionSliceToGeneric(s.Before), + OnSuccess: actionSliceToGeneric(s.OnSuccess), + OnFailure: actionSliceToGeneric(s.OnFailure), } } -func convertV1Beta1ActionSlice(actions []v1beta1.ZarfComponentAction) []types.ZarfComponentAction { - var out []types.ZarfComponentAction +func actionSliceToGeneric(actions []v1beta1.ComponentAction) []types.ComponentAction { + var out []types.ComponentAction for _, a := range actions { - out = append(out, convertV1Beta1Action(a)) + out = append(out, actionToGeneric(a)) } return out } -func convertV1Beta1Action(a v1beta1.ZarfComponentAction) types.ZarfComponentAction { - ga := types.ZarfComponentAction{ - Mute: a.Mute, - Timeout: a.Timeout, - Retries: a.Retries, - Dir: a.Dir, - Env: a.Env, - Cmd: a.Cmd, - Description: a.Description, - Wait: convertV1Beta1Wait(a.Wait), - Template: a.Template, - } - - for _, v := range a.SetVariables { - ga.SetVariables = append(ga.SetVariables, types.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: string(v.Type), - }) +func actionToGeneric(a v1beta1.ComponentAction) types.ComponentAction { + ga := types.ComponentAction{ + Silent: a.Silent, + MaxTotalSeconds: a.MaxTotalSeconds, + Retries: a.Retries, + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: waitToGeneric(a.Wait), + EnableValues: a.EnableValues, + SetVariables: a.GetDeprecatedSetVariables(), } for _, sv := range a.SetValues { @@ -294,13 +263,13 @@ func convertV1Beta1Action(a v1beta1.ZarfComponentAction) types.ZarfComponentActi return ga } -func convertV1Beta1Wait(w *v1beta1.ZarfComponentActionWait) *types.ZarfComponentActionWait { +func waitToGeneric(w *v1beta1.ComponentActionWait) *types.ComponentActionWait { if w == nil { return nil } - gw := &types.ZarfComponentActionWait{} + gw := &types.ComponentActionWait{} if w.Cluster != nil { - gw.Cluster = &types.ZarfComponentActionWaitCluster{ + gw.Cluster = &types.ComponentActionWaitCluster{ Kind: w.Cluster.Kind, Name: w.Cluster.Name, Namespace: w.Cluster.Namespace, @@ -308,7 +277,7 @@ func convertV1Beta1Wait(w *v1beta1.ZarfComponentActionWait) *types.ZarfComponent } } if w.Network != nil { - gw.Network = &types.ZarfComponentActionWaitNetwork{ + gw.Network = &types.ComponentActionWaitNetwork{ Protocol: w.Network.Protocol, Address: w.Network.Address, Code: w.Network.Code, @@ -317,61 +286,51 @@ func convertV1Beta1Wait(w *v1beta1.ZarfComponentActionWait) *types.ZarfComponent return gw } -// ConvertFromGeneric converts the internal generic representation to a v1beta1 ZarfPackage. -func ConvertFromGeneric(g types.ZarfPackage) v1beta1.ZarfPackage { - pkg := v1beta1.ZarfPackage{ - APIVersion: v1beta1.APIVersion, - Kind: v1beta1.ZarfPackageKind(g.Kind), - Metadata: convertMetadata(g.Metadata), - Build: convertBuild(g.Build, g.Metadata), - Values: v1beta1.ZarfValues{ - Files: g.Values.Files, - Schema: g.Values.Schema, - }, +// ConvertFromGeneric converts the internal generic representation to a v1beta1 Package. +func ConvertFromGeneric(g types.Package) v1beta1.Package { + pkg := v1beta1.Package{ + APIVersion: v1beta1.APIVersion, + Kind: v1beta1.PackageKind(g.Kind), + Metadata: metadataFromGeneric(g.Metadata), + Build: buildFromGeneric(g.Build, g.Metadata), + Values: v1beta1.Values{Files: g.Values.Files, Schema: g.Values.Schema}, Documentation: g.Documentation, } - for _, c := range g.Constants { - pkg.Constants = append(pkg.Constants, v1beta1.Constant{ - Name: c.Name, - Value: c.Value, - Description: c.Description, - AutoIndent: c.AutoIndent, - Pattern: c.Pattern, - }) + if pkg.Kind == "" { + pkg.Kind = v1beta1.ZarfPackageConfig } - for _, v := range g.Variables { - pkg.Variables = append(pkg.Variables, v1beta1.InteractiveVariable{ - Variable: v1beta1.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: v1beta1.VariableType(v.Type), - }, - Description: v.Description, - Default: v.Default, - Prompt: v.Prompt, - }) + // v1beta1 has no Kind ZarfInitConfig; collapse the v1alpha1 init kind into the normal package kind. + if string(pkg.Kind) == "ZarfInitConfig" { + pkg.Kind = v1beta1.ZarfPackageConfig } + pkg.SetDeprecatedVariables(g.Variables) + pkg.SetDeprecatedConstants(g.Constants) + for _, c := range g.Components { - pkg.Components = append(pkg.Components, convertComponent(c)) + pkg.Components = append(pkg.Components, componentFromGeneric(c)) } return pkg } -func convertMetadata(m types.ZarfMetadata) v1beta1.ZarfMetadata { - meta := v1beta1.ZarfMetadata{ - Name: m.Name, - Description: m.Description, - Version: m.Version, - Uncompressed: m.Uncompressed, - Architecture: m.Architecture, - Annotations: m.Annotations, - AllowNamespaceOverride: m.AllowNamespaceOverride, +func metadataFromGeneric(m types.PackageMetadata) v1beta1.PackageMetadata { + meta := v1beta1.PackageMetadata{ + Name: m.Name, + Description: m.Description, + Version: m.Version, + Uncompressed: m.Uncompressed, + Architecture: m.Architecture, + Annotations: m.Annotations, + } + + // Map v1alpha1 AllowNamespaceOverride (*bool, default allow) onto v1beta1 PreventNamespaceOverride (bool, default allow). + if m.AllowNamespaceOverride != nil { + meta.PreventNamespaceOverride = !*m.AllowNamespaceOverride + } else { + meta.PreventNamespaceOverride = m.PreventNamespaceOverride } // Migrate v1alpha1-only metadata fields into annotations. @@ -396,9 +355,9 @@ func convertMetadata(m types.ZarfMetadata) v1beta1.ZarfMetadata { return meta } -func convertBuild(b types.ZarfBuildData, m types.ZarfMetadata) v1beta1.ZarfBuildData { - out := v1beta1.ZarfBuildData{ - Terminal: b.Terminal, +func buildFromGeneric(b types.BuildData, m types.PackageMetadata) v1beta1.BuildData { + out := v1beta1.BuildData{ + Hostname: b.Hostname, User: b.User, Architecture: b.Architecture, Timestamp: b.Timestamp, @@ -409,14 +368,14 @@ func convertBuild(b types.ZarfBuildData, m types.ZarfMetadata) v1beta1.ZarfBuild DifferentialPackageVersion: b.DifferentialPackageVersion, Flavor: b.Flavor, Signed: b.Signed, - APIVersion: b.APIVersion, ProvenanceFiles: b.ProvenanceFiles, } // AggregateChecksum lives in metadata in v1alpha1, build in v1beta1. - if b.AggregateChecksum != "" { + switch { + case b.AggregateChecksum != "": out.AggregateChecksum = b.AggregateChecksum - } else if m.AggregateChecksum != "" { + case m.AggregateChecksum != "": out.AggregateChecksum = m.AggregateChecksum } @@ -430,68 +389,61 @@ func convertBuild(b types.ZarfBuildData, m types.ZarfMetadata) v1beta1.ZarfBuild return out } -func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { - gc := v1beta1.ZarfComponent{ - Name: c.Name, - Description: c.Description, - Default: c.Default, - Optional: convertRequiredToOptional(c.Required), - Only: v1beta1.ZarfComponentOnlyTarget{ - LocalOS: c.Only.LocalOS, - Cluster: v1beta1.ZarfComponentOnlyCluster{ - Architecture: c.Only.Cluster.Architecture, - Distros: c.Only.Cluster.Distros, - }, - Flavor: c.Only.Flavor, +func componentFromGeneric(c types.Component) v1beta1.Component { + bc := v1beta1.Component{ + Name: c.Name, + Description: c.Description, + Optional: optionalFromGeneric(c.Optional, c.Required), + Repositories: c.Repositories, + Target: v1beta1.ComponentTarget{ + OS: c.Target.OS, + Architecture: c.Target.Architecture, + Flavor: c.Target.Flavor, }, - Import: v1beta1.ZarfComponentImport{ - Path: c.Import.Path, - URL: c.Import.URL, - }, - Features: convertFeatures(c.Features), - Repos: c.Repos, - Actions: convertActions(c.Actions), + Import: importFromGeneric(c.Import), + Service: serviceFromGeneric(c), + Actions: actionsFromGeneric(c.Actions), } for _, m := range c.Manifests { - gc.Manifests = append(gc.Manifests, convertManifest(m)) + bc.Manifests = append(bc.Manifests, manifestFromGeneric(m)) } for _, ch := range c.Charts { - gc.Charts = append(gc.Charts, convertChart(ch)) + bc.Charts = append(bc.Charts, chartFromGeneric(ch)) } for _, f := range c.Files { - gc.Files = append(gc.Files, v1beta1.ZarfFile{ - Source: f.Source, - Shasum: f.Shasum, - Target: f.Target, - Executable: f.Executable, - Symlinks: f.Symlinks, - ExtractPath: f.ExtractPath, - Template: f.Template, + bc.Files = append(bc.Files, v1beta1.File{ + Source: f.Source, + Checksum: f.Checksum, + Destination: f.Destination, + Executable: f.Executable, + Symlinks: f.Symlinks, + ExtractPath: f.ExtractPath, + EnableValues: f.EnableValues, }) } for _, img := range c.Images { - gc.Images = append(gc.Images, v1beta1.ZarfImage{ + bc.Images = append(bc.Images, v1beta1.Image{ Name: img.Name, - Source: string(img.Source), + Source: img.Source, }) } for _, ia := range c.ImageArchives { - gc.ImageArchives = append(gc.ImageArchives, v1beta1.ImageArchive{ + bc.ImageArchives = append(bc.ImageArchives, v1beta1.ImageArchive{ Path: ia.Path, Images: ia.Images, }) } - // Convert v1alpha1 HealthChecks into onDeploy after wait actions. + // Convert v1alpha1 HealthChecks into onDeploy onSuccess wait actions. for _, hc := range c.HealthChecks { - gc.Actions.OnDeploy.After = append(gc.Actions.OnDeploy.After, v1beta1.ZarfComponentAction{ - Wait: &v1beta1.ZarfComponentActionWait{ - Cluster: &v1beta1.ZarfComponentActionWaitCluster{ + bc.Actions.OnDeploy.OnSuccess = append(bc.Actions.OnDeploy.OnSuccess, v1beta1.ComponentAction{ + Wait: &v1beta1.ComponentActionWait{ + Cluster: &v1beta1.ComponentActionWaitCluster{ Kind: healthCheckKind(hc.Kind, hc.APIVersion), Name: hc.Name, Namespace: hc.Namespace, @@ -500,154 +452,146 @@ func convertComponent(c types.ZarfComponent) v1beta1.ZarfComponent { }) } - // Preserve v1alpha1-only fields via private shims for lossless round-tripping. - gc.SetDataInjections(c.DataInjections) + bc.SetDeprecatedDataInjections(c.DataInjections) - return gc + return bc } -func convertFeatures(f types.ZarfComponentFeatures) v1beta1.ZarfComponentFeatures { - out := v1beta1.ZarfComponentFeatures{ - IsRegistry: f.IsRegistry, - IsAgent: f.IsAgent, - } - if f.Injector != nil { - out.Injector = &v1beta1.Injector{ - Enabled: f.Injector.Enabled, - } - if f.Injector.Values != nil { - out.Injector.Values = &v1beta1.InjectorValues{ - Tolerations: f.Injector.Values.Tolerations, - } - } +// optionalFromGeneric maps the v1alpha1 Required *bool and v1beta1 Optional bool onto a single v1beta1 Optional bool. +// v1alpha1: Required=nil/false → Optional=true; Required=true → Optional=false. +// v1beta1: Optional flows through directly when Required is nil. +func optionalFromGeneric(optional bool, required *bool) bool { + if required != nil { + return !*required } - return out + return optional } -// convertRequiredToOptional inverts the v1alpha1 Required *bool into v1beta1 Optional *bool. -// v1alpha1 Required=nil → not required → Optional=nil (default false in v1beta1 means required) -// Wait — v1alpha1 Required=nil means "not required" but v1beta1 Optional=nil means "not optional" (required). -// So Required=nil needs to become Optional=true if the component was truly optional. -// However, the v1alpha1 default is Required=nil meaning "not required" only when Default is false. -// The safest mapping: Required=true → Optional=nil (required), Required=false/nil → Optional=true (optional). -// But actually the semantics differ: in v1alpha1, Required=nil + Default=false means the component -// prompts the user. In v1beta1, Optional=nil means required (no prompt). We preserve Required -// directly and let the caller interpret the v1alpha1 Default/Required/Group semantics. -func convertRequiredToOptional(required *bool) *bool { - if required == nil { - return nil - } - inverted := !*required - return &inverted +func serviceFromGeneric(c types.Component) v1beta1.Service { + if c.Service != "" { + return v1beta1.Service(c.Service) + } + // Infer the v1beta1 Service from well-known v1alpha1 component names. + switch c.Name { + case "zarf-registry": + return v1beta1.ServiceRegistry + case "zarf-seed-registry": + return v1beta1.ServiceSeedRegistry + case "zarf-injector": + return v1beta1.ServiceInjector + case "zarf-agent": + return v1beta1.ServiceAgent + case "zarf-gitea": + return v1beta1.ServiceGitServer + } + return "" } -func convertManifest(m types.ZarfManifest) v1beta1.ZarfManifest { - bm := v1beta1.ZarfManifest{ - Name: m.Name, - Namespace: m.Namespace, - Files: m.Files, - KustomizeAllowAnyDirectory: m.KustomizeAllowAnyDirectory, - Kustomizations: m.Kustomizations, - ServerSideApply: m.ServerSideApply, - Template: m.Template, +func importFromGeneric(imp types.ComponentImport) v1beta1.ComponentImport { + out := v1beta1.ComponentImport{} + for _, l := range imp.Local { + out.Local = append(out.Local, v1beta1.ComponentImportLocal{Path: l.Path}) } - - // Invert NoWait → Wait. If the user explicitly set Wait already, prefer that. - if m.Wait != nil { - bm.Wait = m.Wait - } else if m.NoWait { - f := false - bm.Wait = &f + for _, r := range imp.Remote { + out.Remote = append(out.Remote, v1beta1.ComponentImportRemote{URL: r.URL}) + } + // Promote v1alpha1 single-import fields when no structured imports are present. + if len(out.Local) == 0 && imp.Path != "" { + out.Local = append(out.Local, v1beta1.ComponentImportLocal{Path: imp.Path}) + } + if len(out.Remote) == 0 && imp.URL != "" { + out.Remote = append(out.Remote, v1beta1.ComponentImportRemote{URL: imp.URL}) } + return out +} +func manifestFromGeneric(m types.Manifest) v1beta1.Manifest { + bm := v1beta1.Manifest{ + Name: m.Name, + Namespace: m.Namespace, + Files: m.Files, + SkipWait: m.SkipWait, + ServerSideApply: v1beta1.ServerSideApplyMode(m.ServerSideApply), + EnableValues: m.EnableValues, + } + if m.Kustomize != nil { + bm.Kustomize = &v1beta1.KustomizeManifest{ + Files: m.Kustomize.Files, + AllowAnyDirectory: m.Kustomize.AllowAnyDirectory, + EnablePlugins: m.Kustomize.EnablePlugins, + } + } + // v1alpha1 Template *bool maps onto EnableValues when it is explicitly true. + if !bm.EnableValues && m.Template != nil && *m.Template { + bm.EnableValues = true + } return bm } -func convertChart(ch types.ZarfChart) v1beta1.ZarfChart { - bc := v1beta1.ZarfChart{ - Name: ch.Name, - Namespace: ch.Namespace, - ReleaseName: ch.ReleaseName, - ValuesFiles: ch.ValuesFiles, - SchemaValidation: ch.SchemaValidation, - ServerSideApply: ch.ServerSideApply, - Values: convertChartValues(ch.Values), - } - - // Invert NoWait → Wait. - if ch.Wait != nil { - bc.Wait = ch.Wait - } else if ch.NoWait { - f := false - bc.Wait = &f - } - - // Convert flat v1alpha1 chart source fields into structured v1beta1 sources. - // If structured sources are already populated (from a v1beta1 origin), use them directly. - if ch.HelmRepo != (types.HelmRepoSource{}) { - bc.HelmRepo = v1beta1.HelmRepoSource{ - Name: ch.HelmRepo.Name, - URL: ch.HelmRepo.URL, - Version: ch.HelmRepo.Version, +func chartFromGeneric(ch types.Chart) v1beta1.Chart { + bc := v1beta1.Chart{ + Name: ch.Name, + Namespace: ch.Namespace, + ReleaseName: ch.ReleaseName, + ValuesFiles: ch.ValuesFiles, + SkipSchemaValidation: ch.SkipSchemaValidation, + ServerSideApply: v1beta1.ServerSideApplyMode(ch.ServerSideApply), + SkipWait: ch.SkipWait, + Values: chartValuesFromGeneric(ch.Values), + } + + // Use the structured sources if present; otherwise infer from v1alpha1 flat fields. + switch { + case ch.HelmRepository != nil: + bc.HelmRepository = &v1beta1.HelmRepositorySource{ + Name: ch.HelmRepository.Name, + URL: ch.HelmRepository.URL, + Version: ch.HelmRepository.Version, } - } else if ch.Git != (types.GitRepoSource{}) { - bc.Git = v1beta1.GitRepoSource{ - URL: ch.Git.URL, - Path: ch.Git.Path, - } - } else if ch.Local != (types.LocalRepoSource{}) { - bc.Local = v1beta1.LocalRepoSource{ - Path: ch.Local.Path, - } - } else if ch.OCI != (types.OCISource{}) { - bc.OCI = v1beta1.OCISource{ - URL: ch.OCI.URL, - Version: ch.OCI.Version, - } - } else if ch.URL != "" { - // Infer source type from v1alpha1 flat fields. + case ch.Git != nil: + bc.Git = &v1beta1.GitSource{URL: ch.Git.URL, Path: ch.Git.Path} + case ch.Local != nil: + bc.Local = &v1beta1.LocalSource{Path: ch.Local.Path} + case ch.OCI != nil: + bc.OCI = &v1beta1.OCISource{URL: ch.OCI.URL, Version: ch.OCI.Version} + case ch.URL != "": switch { case strings.HasPrefix(ch.URL, "oci://"): - bc.OCI = v1beta1.OCISource{ - URL: ch.URL, - Version: ch.Version, - } + bc.OCI = &v1beta1.OCISource{URL: ch.URL, Version: ch.Version} case ch.GitPath != "" || isGitURL(ch.URL): gitURL := ch.URL - // Only append @version if the URL doesn't already contain a ref, - // mirroring the runtime behavior in packager/helm/repo.go. if ch.Version != "" && !strings.Contains(ch.URL, "@") { gitURL += "@" + ch.Version } - bc.Git = v1beta1.GitRepoSource{ - URL: gitURL, - Path: ch.GitPath, - } + bc.Git = &v1beta1.GitSource{URL: gitURL, Path: ch.GitPath} default: - bc.HelmRepo = v1beta1.HelmRepoSource{ + bc.HelmRepository = &v1beta1.HelmRepositorySource{ Name: ch.RepoName, URL: ch.URL, Version: ch.Version, } } - } else if ch.LocalPath != "" { - bc.Local = v1beta1.LocalRepoSource{ - Path: ch.LocalPath, - } + case ch.LocalPath != "": + bc.Local = &v1beta1.LocalSource{Path: ch.LocalPath} + } + + // v1alpha1 SchemaValidation *bool: nil/true → SkipSchemaValidation=false; explicit false → true. + if !bc.SkipSchemaValidation && ch.SchemaValidation != nil && !*ch.SchemaValidation { + bc.SkipSchemaValidation = true } - // Preserve the v1alpha1 flat version via the private shim for lossless round-tripping. if ch.Version != "" { bc.SetDeprecatedVersion(ch.Version) } + bc.SetDeprecatedVariables(ch.Variables) return bc } -func convertChartValues(vals []types.ZarfChartValue) []v1beta1.ZarfChartValue { - var out []v1beta1.ZarfChartValue +func chartValuesFromGeneric(vals []types.ChartValue) []v1beta1.ChartValue { + var out []v1beta1.ChartValue for _, v := range vals { - out = append(out, v1beta1.ZarfChartValue{ + out = append(out, v1beta1.ChartValue{ SourcePath: v.SourcePath, TargetPath: v.TargetPath, }) @@ -655,105 +599,53 @@ func convertChartValues(vals []types.ZarfChartValue) []v1beta1.ZarfChartValue { return out } -func convertActions(a types.ZarfComponentActions) v1beta1.ZarfComponentActions { - return v1beta1.ZarfComponentActions{ - OnCreate: convertActionSet(a.OnCreate), - OnDeploy: convertActionSet(a.OnDeploy), - OnRemove: convertActionSet(a.OnRemove), - } -} - -func convertActionSet(s types.ZarfComponentActionSet) v1beta1.ZarfComponentActionSet { - after := convertActionSlice(s.After) - // Merge v1alpha1 OnSuccess into After. - after = append(after, convertActionSlice(s.OnSuccess)...) - - return v1beta1.ZarfComponentActionSet{ - Defaults: convertActionDefaults(s.Defaults), - Before: convertActionSlice(s.Before), - After: after, - OnFailure: convertActionSlice(s.OnFailure), +func actionsFromGeneric(a types.ComponentActions) v1beta1.ComponentActions { + return v1beta1.ComponentActions{ + OnCreate: actionSetFromGeneric(a.OnCreate), + OnDeploy: actionSetFromGeneric(a.OnDeploy), + OnRemove: actionSetFromGeneric(a.OnRemove), } } -func convertActionDefaults(d types.ZarfComponentActionDefaults) v1beta1.ZarfComponentActionDefaults { - out := v1beta1.ZarfComponentActionDefaults{ - Mute: d.Mute, - Dir: d.Dir, - Env: d.Env, - Shell: v1beta1.Shell{ - Windows: d.Shell.Windows, - Linux: d.Shell.Linux, - Darwin: d.Shell.Darwin, +func actionSetFromGeneric(s types.ComponentActionSet) v1beta1.ComponentActionSet { + return v1beta1.ComponentActionSet{ + Defaults: v1beta1.ComponentActionDefaults{ + Silent: s.Defaults.Silent, + MaxTotalSeconds: s.Defaults.MaxTotalSeconds, + Retries: s.Defaults.Retries, + Dir: s.Defaults.Dir, + Env: s.Defaults.Env, + Shell: v1beta1.Shell{ + Windows: s.Defaults.Shell.Windows, + Linux: s.Defaults.Shell.Linux, + Darwin: s.Defaults.Shell.Darwin, + }, }, + Before: actionSliceFromGeneric(s.Before), + OnSuccess: actionSliceFromGeneric(s.OnSuccess), + OnFailure: actionSliceFromGeneric(s.OnFailure), } - - // Prefer the structured Duration if present, otherwise convert MaxTotalSeconds. - if d.Timeout != nil { - out.Timeout = d.Timeout - } else if d.MaxTotalSeconds > 0 { - dur := metav1.Duration{Duration: time.Duration(d.MaxTotalSeconds) * time.Second} - out.Timeout = &dur - } - - // Prefer Retries if set, otherwise convert MaxRetries. - if d.Retries > 0 { - out.Retries = d.Retries - } else if d.MaxRetries > 0 { - out.Retries = d.MaxRetries - } - - return out } -func convertActionSlice(actions []types.ZarfComponentAction) []v1beta1.ZarfComponentAction { - var out []v1beta1.ZarfComponentAction +func actionSliceFromGeneric(actions []types.ComponentAction) []v1beta1.ComponentAction { + var out []v1beta1.ComponentAction for _, a := range actions { - out = append(out, convertAction(a)) + out = append(out, actionFromGeneric(a)) } return out } -func convertAction(a types.ZarfComponentAction) v1beta1.ZarfComponentAction { - ba := v1beta1.ZarfComponentAction{ - Mute: a.Mute, - Dir: a.Dir, - Env: a.Env, - Cmd: a.Cmd, - Description: a.Description, - Wait: convertWait(a.Wait), - Template: a.Template, - Retries: a.Retries, - } - - // Prefer structured Duration if present, otherwise convert MaxTotalSeconds. - if a.Timeout != nil { - ba.Timeout = a.Timeout - } else if a.MaxTotalSeconds != nil { - dur := metav1.Duration{Duration: time.Duration(*a.MaxTotalSeconds) * time.Second} - ba.Timeout = &dur - } - - // Prefer Retries if already set, otherwise convert MaxRetries. - if ba.Retries == 0 && a.MaxRetries != nil { - ba.Retries = *a.MaxRetries - } - - for _, v := range a.SetVariables { - ba.SetVariables = append(ba.SetVariables, v1beta1.Variable{ - Name: v.Name, - Sensitive: v.Sensitive, - AutoIndent: v.AutoIndent, - Pattern: v.Pattern, - Type: v1beta1.VariableType(v.Type), - }) - } - - // Fold DeprecatedSetVariable into SetVariables if it was set. - if a.DeprecatedSetVariable != "" { - ba.SetVariables = append(ba.SetVariables, v1beta1.Variable{ - Name: a.DeprecatedSetVariable, - }) +func actionFromGeneric(a types.ComponentAction) v1beta1.ComponentAction { + ba := v1beta1.ComponentAction{ + Silent: a.Silent, + MaxTotalSeconds: a.MaxTotalSeconds, + Retries: a.Retries, + Dir: a.Dir, + Env: a.Env, + Cmd: a.Cmd, + Description: a.Description, + Wait: waitFromGeneric(a.Wait), + EnableValues: a.EnableValues, } for _, sv := range a.SetValues { @@ -772,16 +664,24 @@ func convertAction(a types.ZarfComponentAction) v1beta1.ZarfComponentAction { } } + setVariables := append([]v1alpha1.Variable(nil), a.SetVariables...) + if a.DeprecatedSetVariable != "" { + setVariables = append(setVariables, v1alpha1.Variable{Name: a.DeprecatedSetVariable}) + } + if len(setVariables) > 0 { + ba.SetDeprecatedSetVariables(setVariables) + } + return ba } -func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionWait { +func waitFromGeneric(w *types.ComponentActionWait) *v1beta1.ComponentActionWait { if w == nil { return nil } - bw := &v1beta1.ZarfComponentActionWait{} + bw := &v1beta1.ComponentActionWait{} if w.Cluster != nil { - bw.Cluster = &v1beta1.ZarfComponentActionWaitCluster{ + bw.Cluster = &v1beta1.ComponentActionWaitCluster{ Kind: w.Cluster.Kind, Name: w.Cluster.Name, Namespace: w.Cluster.Namespace, @@ -789,7 +689,7 @@ func convertWait(w *types.ZarfComponentActionWait) *v1beta1.ZarfComponentActionW } } if w.Network != nil { - bw.Network = &v1beta1.ZarfComponentActionWaitNetwork{ + bw.Network = &v1beta1.ComponentActionWaitNetwork{ Protocol: w.Network.Protocol, Address: w.Network.Address, Code: w.Network.Code, @@ -810,8 +710,6 @@ func healthCheckKind(kind, apiVersion string) string { } func isGitURL(url string) bool { - // Strip any @ref suffix before checking, matching the runtime behavior - // in packager/helm/repo.go which uses transform.GitURLSplitRef. if idx := strings.LastIndex(url, "@"); idx > 0 { url = url[:idx] } From b1037aaba71a4ecb6b7772c89fe863cc620a54a3 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 29 May 2026 09:51:15 -0400 Subject: [PATCH 18/24] fix tests Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 98 +++++++++++++++-------------- src/internal/api/v1beta1/convert.go | 24 +++---- 2 files changed, 65 insertions(+), 57 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index e0d1c0804d..788d47628f 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -690,20 +690,22 @@ func TestV1Beta1PkgToV1Alpha1_ComponentBasics(t *testing.T) { Name: "my-component", Description: "test component", Optional: true, - Target: v1beta1.ComponentTarget{ - OS: "linux", - Architecture: "amd64", - Flavor: "vanilla", - }, - Import: v1beta1.ComponentImport{ - Local: []v1beta1.ComponentImportLocal{{Path: "./path"}}, - Remote: []v1beta1.ComponentImportRemote{{URL: "oci://example.com/pkg"}}, - }, - Images: []v1beta1.Image{ - {Name: "nginx:latest"}, - {Name: "redis:7", Source: "daemon"}, + ComponentSpec: v1beta1.ComponentSpec{ + Target: v1beta1.ComponentTarget{ + OS: "linux", + Architecture: "amd64", + Flavor: "vanilla", + }, + Import: v1beta1.ComponentImport{ + Local: []v1beta1.ComponentImportLocal{{Path: "./path"}}, + Remote: []v1beta1.ComponentImportRemote{{URL: "oci://example.com/pkg"}}, + }, + Images: []v1beta1.Image{ + {Name: "nginx:latest"}, + {Name: "redis:7", Source: "daemon"}, + }, + Repositories: []string{"https://github.com/example/repo"}, }, - Repositories: []string{"https://github.com/example/repo"}, }, }, } @@ -820,8 +822,8 @@ func TestV1Beta1PkgToV1Alpha1_ChartSources(t *testing.T) { Kind: v1beta1.ZarfPackageConfig, Components: []v1beta1.Component{ { - Name: "chart-comp", - Charts: []v1beta1.Chart{tt.chart}, + Name: "chart-comp", + ComponentSpec: v1beta1.ComponentSpec{Charts: []v1beta1.Chart{tt.chart}}, }, }, } @@ -840,13 +842,15 @@ func TestV1Beta1PkgToV1Alpha1_ManifestSkipWaitInversion(t *testing.T) { Components: []v1beta1.Component{ { Name: "manifest-comp", - Manifests: []v1beta1.Manifest{ - { - Name: "skip-wait", - SkipWait: true, - }, - { - Name: "default-wait", + ComponentSpec: v1beta1.ComponentSpec{ + Manifests: []v1beta1.Manifest{ + { + Name: "skip-wait", + SkipWait: true, + }, + { + Name: "default-wait", + }, }, }, }, @@ -877,32 +881,34 @@ func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { Components: []v1beta1.Component{ { Name: "action-comp", - Actions: v1beta1.ComponentActions{ - OnDeploy: v1beta1.ComponentActionSet{ - Defaults: v1beta1.ComponentActionDefaults{ - Silent: true, - MaxTotalSeconds: maxSecDef, - Retries: retriesDef, - Dir: "/work", - Env: []string{"FOO=bar"}, - Shell: v1beta1.Shell{ - Linux: "bash", + ComponentSpec: v1beta1.ComponentSpec{ + Actions: v1beta1.ComponentActions{ + OnDeploy: v1beta1.ComponentActionSet{ + Defaults: v1beta1.ComponentActionDefaults{ + Silent: true, + MaxTotalSeconds: maxSecDef, + Retries: retriesDef, + Dir: "/work", + Env: []string{"FOO=bar"}, + Shell: v1beta1.Shell{ + Linux: "bash", + }, }, - }, - Before: []v1beta1.ComponentAction{ - { - Cmd: "echo before", - Silent: &mute, - MaxTotalSeconds: &maxSec, - Retries: &retries, - Dir: &dir, + Before: []v1beta1.ComponentAction{ + { + Cmd: "echo before", + Silent: &mute, + MaxTotalSeconds: &maxSec, + Retries: &retries, + Dir: &dir, + }, + }, + OnSuccess: []v1beta1.ComponentAction{ + {Cmd: "echo after"}, + }, + OnFailure: []v1beta1.ComponentAction{ + {Cmd: "echo failure"}, }, - }, - OnSuccess: []v1beta1.ComponentAction{ - {Cmd: "echo after"}, - }, - OnFailure: []v1beta1.ComponentAction{ - {Cmd: "echo failure"}, }, }, }, diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 99744aa79e..f8f5b20934 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -391,18 +391,20 @@ func buildFromGeneric(b types.BuildData, m types.PackageMetadata) v1beta1.BuildD func componentFromGeneric(c types.Component) v1beta1.Component { bc := v1beta1.Component{ - Name: c.Name, - Description: c.Description, - Optional: optionalFromGeneric(c.Optional, c.Required), - Repositories: c.Repositories, - Target: v1beta1.ComponentTarget{ - OS: c.Target.OS, - Architecture: c.Target.Architecture, - Flavor: c.Target.Flavor, + Name: c.Name, + Description: c.Description, + Optional: optionalFromGeneric(c.Optional, c.Required), + ComponentSpec: v1beta1.ComponentSpec{ + Repositories: c.Repositories, + Target: v1beta1.ComponentTarget{ + OS: c.Target.OS, + Architecture: c.Target.Architecture, + Flavor: c.Target.Flavor, + }, + Import: importFromGeneric(c.Import), + Service: serviceFromGeneric(c), + Actions: actionsFromGeneric(c.Actions), }, - Import: importFromGeneric(c.Import), - Service: serviceFromGeneric(c), - Actions: actionsFromGeneric(c.Actions), } for _, m := range c.Manifests { From 8ed3b4c88840fb29bf17fb028fac4481a1892da5 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 29 May 2026 10:04:23 -0400 Subject: [PATCH 19/24] cast Signed-off-by: Austin Abro --- src/internal/api/v1alpha1/convert.go | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index af33672c78..09429c2095 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -5,7 +5,6 @@ package v1alpha1 import ( - "math" "strings" "github.com/zarf-dev/zarf/src/api/v1alpha1" @@ -193,8 +192,8 @@ func actionsToGeneric(a v1alpha1.ZarfComponentActions) types.ComponentActions { func actionSetToGeneric(s v1alpha1.ZarfComponentActionSet) types.ComponentActionSet { defaults := types.ComponentActionDefaults{ Silent: s.Defaults.Mute, - MaxTotalSeconds: clampToInt32(s.Defaults.MaxTotalSeconds), - Retries: clampToInt32(s.Defaults.MaxRetries), + MaxTotalSeconds: int32(s.Defaults.MaxTotalSeconds), + Retries: int32(s.Defaults.MaxRetries), Dir: s.Defaults.Dir, Env: s.Defaults.Env, Shell: types.Shell{ @@ -237,11 +236,11 @@ func actionToGeneric(a v1alpha1.ZarfComponentAction) types.ComponentAction { } if a.MaxTotalSeconds != nil { - v := clampToInt32(*a.MaxTotalSeconds) + v := int32(*a.MaxTotalSeconds) ga.MaxTotalSeconds = &v } if a.MaxRetries != nil { - v := clampToInt32(*a.MaxRetries) + v := int32(*a.MaxRetries) ga.Retries = &v } @@ -281,7 +280,7 @@ func waitToGeneric(w *v1alpha1.ZarfComponentActionWait) *types.ComponentActionWa gw.Network = &types.ComponentActionWaitNetwork{ Protocol: w.Network.Protocol, Address: w.Network.Address, - Code: clampToInt32(w.Network.Code), + Code: int32(w.Network.Code), } } return gw @@ -669,16 +668,6 @@ func waitFromGeneric(w *types.ComponentActionWait) *v1alpha1.ZarfComponentAction return aw } -func clampToInt32(v int) int32 { - if v > math.MaxInt32 { - return math.MaxInt32 - } - if v < math.MinInt32 { - return math.MinInt32 - } - return int32(v) -} - func derefBool(p *bool) bool { if p == nil { return false From 38bcbc83ef00de4cb2e19be4d5aa6e359b046659 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 29 May 2026 10:24:13 -0400 Subject: [PATCH 20/24] original api verison Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 39 ++++++++++++++++++++++++++++ src/internal/api/types/package.go | 4 +-- src/internal/api/v1alpha1/convert.go | 8 ++++++ src/internal/api/v1beta1/convert.go | 8 ++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 788d47628f..2e55717ea1 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -583,6 +583,18 @@ func TestV1Alpha1PkgToV1Beta1_DeprecatedVersionShim(t *testing.T) { require.Equal(t, "1.2.3", chart.GetDeprecatedVersion()) } +func TestV1Alpha1PkgToV1Beta1_OriginalAPIVersion(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageConfig, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.Equal(t, v1alpha1.APIVersion, result.Build.OriginalAPIVersion()) +} + // --- v1beta1 → v1alpha1 tests --- func TestV1Beta1PkgToV1Alpha1_Metadata(t *testing.T) { @@ -988,6 +1000,33 @@ func TestV1Beta1PkgToV1Alpha1_VariablesShim(t *testing.T) { require.Equal(t, "const-val", result.Constants[0].Value) } +func TestV1Beta1PkgToV1Alpha1_OriginalAPIVersion(t *testing.T) { + t.Parallel() + pkg := v1beta1.Package{ + APIVersion: v1beta1.APIVersion, + Kind: v1beta1.ZarfPackageConfig, + } + + result := V1Beta1PkgToV1Alpha1(pkg) + + require.Equal(t, v1beta1.APIVersion, result.Build.OriginalAPIVersion()) +} + +func TestOriginalAPIVersion_SurvivesRoundTrip(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + APIVersion: v1alpha1.APIVersion, + Kind: v1alpha1.ZarfPackageConfig, + } + + beta := V1Alpha1PkgToV1Beta1(pkg) + require.Equal(t, v1alpha1.APIVersion, beta.Build.OriginalAPIVersion()) + + // Converting back must preserve the true original, not report v1beta1. + result := V1Beta1PkgToV1Alpha1(beta) + require.Equal(t, v1alpha1.APIVersion, result.Build.OriginalAPIVersion()) +} + func TestRoundTrip_V1Alpha1_To_V1Beta1_And_Back(t *testing.T) { t.Parallel() required := true diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go index 88bbfb7ac5..85507c5cbc 100644 --- a/src/internal/api/types/package.go +++ b/src/internal/api/types/package.go @@ -66,8 +66,8 @@ type BuildData struct { VersionRequirements []VersionRequirement ProvenanceFiles []string AggregateChecksum string - // APIVersion tracks the apiVersion of the package definition used to build the package. - APIVersion string + // OriginalAPIVersion tracks the apiVersion the package was read from before any conversion. + OriginalAPIVersion string // v1alpha1-only build fields. DifferentialMissing []string diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index 09429c2095..74af8d3e0a 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -13,6 +13,11 @@ import ( // ConvertToGeneric converts a v1alpha1 ZarfPackage to the internal generic representation. func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.Package { + // Preserve an already-recorded original across multi-hop conversions; otherwise this is the original. + originalAPIVersion := pkg.Build.OriginalAPIVersion() + if originalAPIVersion == "" { + originalAPIVersion = pkg.APIVersion + } g := types.Package{ APIVersion: pkg.APIVersion, Kind: string(pkg.Kind), @@ -47,6 +52,7 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.Package { Signed: pkg.Build.Signed, DifferentialMissing: pkg.Build.DifferentialMissing, ProvenanceFiles: pkg.Build.ProvenanceFiles, + OriginalAPIVersion: originalAPIVersion, }, Values: types.Values{ Files: pkg.Values.Files, @@ -394,6 +400,8 @@ func buildFromGeneric(b types.BuildData) v1alpha1.ZarfBuildData { }) } + out.SetOriginalAPIVersion(b.OriginalAPIVersion) + return out } diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index f8f5b20934..8ecad1ccee 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -14,6 +14,11 @@ import ( // ConvertToGeneric converts a v1beta1 Package to the internal generic representation. func ConvertToGeneric(pkg v1beta1.Package) types.Package { + // Preserve an already-recorded original across multi-hop conversions; otherwise this is the original. + originalAPIVersion := pkg.Build.OriginalAPIVersion() + if originalAPIVersion == "" { + originalAPIVersion = pkg.APIVersion + } g := types.Package{ APIVersion: pkg.APIVersion, Kind: string(pkg.Kind), @@ -40,6 +45,7 @@ func ConvertToGeneric(pkg v1beta1.Package) types.Package { Signed: pkg.Build.Signed, ProvenanceFiles: pkg.Build.ProvenanceFiles, AggregateChecksum: pkg.Build.AggregateChecksum, + OriginalAPIVersion: originalAPIVersion, }, Values: types.Values{ Files: pkg.Values.Files, @@ -386,6 +392,8 @@ func buildFromGeneric(b types.BuildData, m types.PackageMetadata) v1beta1.BuildD }) } + out.SetOriginalAPIVersion(b.OriginalAPIVersion) + return out } From 89bb1163e041c476255672ddf061a20f6bf1a0ff Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 29 May 2026 10:58:06 -0400 Subject: [PATCH 21/24] test out coverpkg Signed-off-by: Austin Abro --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1be60e7f5b..e054401bca 100644 --- a/Makefile +++ b/Makefile @@ -225,7 +225,7 @@ test-upgrade: ## Run the Zarf CLI E2E tests for an external registry and cluster .PHONY: test-unit test-unit: ## Run unit tests - go test -failfast -v -race -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v '^github.com/zarf-dev/zarf/src/test') + go test -failfast -v -race -coverprofile=coverage.out -covermode=atomic -coverpkg="./src/..." $$(go list ./... | grep -v '^github.com/zarf-dev/zarf/src/test') .PHONY: test-unit-quick test-unit-quick: ## Run unit tests without the race detector or coverage From 7afa5771596ae02f47d46569bdceb29dceafbc44 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 3 Jun 2026 09:34:26 -0400 Subject: [PATCH 22/24] cleaner conversion logic Signed-off-by: Austin Abro --- src/api/convert/convert.go | 3 +- src/api/convert/convert_test.go | 65 ++++++--- src/api/v1beta1/convert.go | 121 +++++++++++++++++ src/internal/api/types/package.go | 80 +++++++++-- src/internal/api/v1alpha1/convert.go | 190 +++++++++++++++++++++++++-- src/internal/api/v1beta1/convert.go | 111 +++++++++++----- 6 files changed, 497 insertions(+), 73 deletions(-) create mode 100644 src/api/v1beta1/convert.go diff --git a/src/api/convert/convert.go b/src/api/convert/convert.go index 0eaf46f26f..5d49c2ed8a 100644 --- a/src/api/convert/convert.go +++ b/src/api/convert/convert.go @@ -14,7 +14,8 @@ import ( // V1Alpha1PkgToV1Beta1 converts a v1alpha1 ZarfPackage to a v1beta1 Package. func V1Alpha1PkgToV1Beta1(pkg v1alpha1.ZarfPackage) v1beta1.Package { generic := internalv1alpha1.ConvertToGeneric(pkg) - return internalv1beta1.ConvertFromGeneric(generic) + v1beta1Pkg := internalv1beta1.ConvertFromGeneric(generic) + return v1beta1.SetDeprecatedFromGeneric(generic, v1beta1Pkg) } // V1Beta1PkgToV1Alpha1 converts a v1beta1 Package to a v1alpha1 ZarfPackage. diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 2e55717ea1..349f2d7e07 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" + "github.com/zarf-dev/zarf/src/internal/api/types" ) func TestV1Alpha1PkgToV1Beta1_Metadata(t *testing.T) { @@ -143,7 +144,7 @@ func TestV1Alpha1PkgToV1Beta1_VariablesAndConstantsShim(t *testing.T) { require.True(t, vars[0].Sensitive) require.True(t, vars[0].AutoIndent) require.Equal(t, "^[a-z]+$", vars[0].Pattern) - require.Equal(t, v1alpha1.FileVariableType, vars[0].Type) + require.Equal(t, v1beta1.FileVariableType, vars[0].Type) require.Equal(t, "A variable", vars[0].Description) require.Equal(t, "default-val", vars[0].Default) require.True(t, vars[0].Prompt) @@ -156,6 +157,29 @@ func TestV1Alpha1PkgToV1Beta1_VariablesAndConstantsShim(t *testing.T) { require.True(t, consts[0].AutoIndent) } +func TestV1Alpha1PkgToV1Beta1_YOLOAndGroupShim(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Metadata: v1alpha1.ZarfMetadata{ + Name: "yolo-pkg", + YOLO: true, + }, + Components: []v1alpha1.ZarfComponent{ + { + Name: "comp", + DeprecatedGroup: "my-group", + }, + }, + } + + result := V1Alpha1PkgToV1Beta1(pkg) + + require.True(t, result.Metadata.GetDeprecatedYOLO()) + require.Len(t, result.Components, 1) + require.Equal(t, "my-group", result.Components[0].GetDeprecatedGroup()) +} + func TestV1Alpha1PkgToV1Beta1_ComponentBasics(t *testing.T) { t.Parallel() required := true @@ -592,7 +616,7 @@ func TestV1Alpha1PkgToV1Beta1_OriginalAPIVersion(t *testing.T) { result := V1Alpha1PkgToV1Beta1(pkg) - require.Equal(t, v1alpha1.APIVersion, result.Build.OriginalAPIVersion()) + require.Equal(t, v1alpha1.APIVersion, result.Build.GetOriginalAPIVersion()) } // --- v1beta1 → v1alpha1 tests --- @@ -963,26 +987,25 @@ func TestV1Beta1PkgToV1Alpha1_Actions(t *testing.T) { func TestV1Beta1PkgToV1Alpha1_VariablesShim(t *testing.T) { t.Parallel() - pkg := v1beta1.Package{ - Kind: v1beta1.ZarfPackageConfig, - } - pkg.SetDeprecatedVariables([]v1alpha1.InteractiveVariable{ - { - Variable: v1alpha1.Variable{ - Name: "MY_VAR", - Sensitive: true, - AutoIndent: true, - Pattern: "^[a-z]+$", - Type: v1alpha1.FileVariableType, + pkg := v1beta1.SetDeprecatedFromGeneric(types.Package{ + Variables: []types.InteractiveVariable{ + { + Variable: types.Variable{ + Name: "MY_VAR", + Sensitive: true, + AutoIndent: true, + Pattern: "^[a-z]+$", + Type: types.FileVariableType, + }, + Description: "A variable", + Default: "default-val", + Prompt: true, }, - Description: "A variable", - Default: "default-val", - Prompt: true, }, - }) - pkg.SetDeprecatedConstants([]v1alpha1.Constant{ - {Name: "MY_CONST", Value: "const-val"}, - }) + Constants: []types.Constant{ + {Name: "MY_CONST", Value: "const-val"}, + }, + }, v1beta1.Package{Kind: v1beta1.ZarfPackageConfig}) result := V1Beta1PkgToV1Alpha1(pkg) @@ -1020,7 +1043,7 @@ func TestOriginalAPIVersion_SurvivesRoundTrip(t *testing.T) { } beta := V1Alpha1PkgToV1Beta1(pkg) - require.Equal(t, v1alpha1.APIVersion, beta.Build.OriginalAPIVersion()) + require.Equal(t, v1alpha1.APIVersion, beta.Build.GetOriginalAPIVersion()) // Converting back must preserve the true original, not report v1beta1. result := V1Beta1PkgToV1Alpha1(beta) diff --git a/src/api/v1beta1/convert.go b/src/api/v1beta1/convert.go new file mode 100644 index 0000000000..20021b96c2 --- /dev/null +++ b/src/api/v1beta1/convert.go @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package v1beta1 + +import "github.com/zarf-dev/zarf/src/internal/api/types" + +// SetDeprecatedFromGeneric populates the unexported v1alpha1 backwards-compatibility +// shim fields on an already-converted package. +// This is intentionally not reachable outside Zarf as types.Package is internal. +func SetDeprecatedFromGeneric(g types.Package, pkg Package) Package { + pkg.variables = interactiveVarsFromGeneric(g.Variables) + pkg.constants = constantsFromGeneric(g.Constants) + pkg.Metadata.yolo = g.Metadata.YOLO + + for i := range pkg.Components { + gc := g.Components[i] + pkg.Components[i].dataInjections = dataInjectionsFromGeneric(gc.DataInjections) + pkg.Components[i].group = gc.Group + + for j := range pkg.Components[i].Charts { + gch := gc.Charts[j] + pkg.Components[i].Charts[j].version = gch.Version + pkg.Components[i].Charts[j].variables = chartVarsFromGeneric(gch.Variables) + } + + applyActionSetSetVariables(&pkg.Components[i].Actions.OnCreate, gc.Actions.OnCreate) + applyActionSetSetVariables(&pkg.Components[i].Actions.OnDeploy, gc.Actions.OnDeploy) + applyActionSetSetVariables(&pkg.Components[i].Actions.OnRemove, gc.Actions.OnRemove) + } + + return pkg +} + +func applyActionSetSetVariables(set *ComponentActionSet, g types.ComponentActionSet) { + applyActionSliceSetVariables(set.Before, g.Before) + applyActionSliceSetVariables(set.OnSuccess, g.OnSuccess) + applyActionSliceSetVariables(set.OnFailure, g.OnFailure) +} + +func applyActionSliceSetVariables(actions []ComponentAction, g []types.ComponentAction) { + for k := range g { + setVars := setVarsFromGeneric(g[k].SetVariables) + if g[k].DeprecatedSetVariable != "" { + setVars = append(setVars, Variable{Name: g[k].DeprecatedSetVariable}) + } + if len(setVars) > 0 { + actions[k].setVariables = setVars + } + } +} + +func variableFromGeneric(v types.Variable) Variable { + return Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: VariableType(v.Type), + } +} + +func setVarsFromGeneric(in []types.Variable) []Variable { + var out []Variable + for _, v := range in { + out = append(out, variableFromGeneric(v)) + } + return out +} + +func interactiveVarsFromGeneric(in []types.InteractiveVariable) []InteractiveVariable { + var out []InteractiveVariable + for _, v := range in { + out = append(out, InteractiveVariable{ + Variable: variableFromGeneric(v.Variable), + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + return out +} + +func constantsFromGeneric(in []types.Constant) []Constant { + var out []Constant + for _, c := range in { + out = append(out, Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + return out +} + +func chartVarsFromGeneric(in []types.ZarfChartVariable) []ZarfChartVariable { + var out []ZarfChartVariable + for _, v := range in { + out = append(out, ZarfChartVariable{Name: v.Name, Description: v.Description, Path: v.Path}) + } + return out +} + +func dataInjectionsFromGeneric(in []types.ZarfDataInjection) []ZarfDataInjection { + var out []ZarfDataInjection + for _, d := range in { + out = append(out, ZarfDataInjection{ + Source: d.Source, + Target: ZarfContainerTarget{ + Namespace: d.Target.Namespace, + Selector: d.Target.Selector, + Container: d.Target.Container, + Path: d.Target.Path, + }, + Compress: d.Compress, + }) + } + return out +} diff --git a/src/internal/api/types/package.go b/src/internal/api/types/package.go index 85507c5cbc..d67a8b2ff4 100644 --- a/src/internal/api/types/package.go +++ b/src/internal/api/types/package.go @@ -7,8 +7,6 @@ // untouched on the latest schema. package types -import "github.com/zarf-dev/zarf/src/api/v1alpha1" - // Package is the internal superset representation used for conversions between API versions. type Package struct { APIVersion string @@ -20,8 +18,8 @@ type Package struct { Documentation map[string]string // v1alpha1-only fields preserved for lossless round-trip. - Variables []v1alpha1.InteractiveVariable - Constants []v1alpha1.Constant + Variables []InteractiveVariable + Constants []Constant } // PackageMetadata is the superset of metadata fields across API versions. @@ -105,8 +103,8 @@ type Component struct { Default bool Required *bool Group string - DataInjections []v1alpha1.ZarfDataInjection - HealthChecks []v1alpha1.NamespacedObjectKindReference + DataInjections []ZarfDataInjection + HealthChecks []NamespacedObjectKindReference Distros []string } @@ -184,7 +182,7 @@ type Chart struct { LocalPath string Version string SchemaValidation *bool - Variables []v1alpha1.ZarfChartVariable + Variables []ZarfChartVariable } // ChartValue maps a source path to a target path. @@ -280,7 +278,7 @@ type ComponentAction struct { EnableValues bool // v1alpha1-only round-trip fields. - SetVariables []v1alpha1.Variable + SetVariables []Variable DeprecatedSetVariable string } @@ -318,3 +316,69 @@ type Shell struct { Linux string Darwin string } + +// VariableType represents a type of a Zarf package variable. +type VariableType string + +const ( + // RawVariableType is the default type for a Zarf package variable. + RawVariableType VariableType = "raw" + // FileVariableType loads a variable's contents from a file. + FileVariableType VariableType = "file" +) + +// Variable represents a variable that has a value set programmatically. +type Variable struct { + Name string + Sensitive bool + AutoIndent bool + Pattern string + Type VariableType +} + +// InteractiveVariable is a variable that can prompt a user for more information. +type InteractiveVariable struct { + Variable + Description string + Default string + Prompt bool +} + +// Constant is a value that can be used to dynamically template resources or run in actions. +type Constant struct { + Name string + Value string + Description string + AutoIndent bool + Pattern string +} + +// ZarfChartVariable represents a variable that can be set for Helm chart overrides. +type ZarfChartVariable struct { + Name string + Description string + Path string +} + +// ZarfContainerTarget defines the destination info for a ZarfDataInjection target. +type ZarfContainerTarget struct { + Namespace string + Selector string + Container string + Path string +} + +// ZarfDataInjection is a data-injection definition. +type ZarfDataInjection struct { + Source string + Target ZarfContainerTarget + Compress bool +} + +// NamespacedObjectKindReference references a cluster resource targeted by a health check. +type NamespacedObjectKindReference struct { + APIVersion string + Kind string + Namespace string + Name string +} diff --git a/src/internal/api/v1alpha1/convert.go b/src/internal/api/v1alpha1/convert.go index 74af8d3e0a..cde1466068 100644 --- a/src/internal/api/v1alpha1/convert.go +++ b/src/internal/api/v1alpha1/convert.go @@ -59,8 +59,8 @@ func ConvertToGeneric(pkg v1alpha1.ZarfPackage) types.Package { Schema: pkg.Values.Schema, }, Documentation: pkg.Documentation, - Variables: append([]v1alpha1.InteractiveVariable(nil), pkg.Variables...), - Constants: append([]v1alpha1.Constant(nil), pkg.Constants...), + Variables: interactiveVarsToGeneric(pkg.Variables), + Constants: constantsToGeneric(pkg.Constants), } for _, vr := range pkg.Build.VersionRequirements { @@ -84,8 +84,8 @@ func componentToGeneric(c v1alpha1.ZarfComponent) types.Component { Default: c.Default, Required: c.Required, Group: c.DeprecatedGroup, - DataInjections: c.DataInjections, - HealthChecks: c.HealthChecks, + DataInjections: dataInjectionsToGeneric(c.DataInjections), + HealthChecks: healthChecksToGeneric(c.HealthChecks), Repositories: c.Repos, Target: types.ComponentTarget{ OS: c.Only.LocalOS, @@ -170,7 +170,7 @@ func chartToGeneric(ch v1alpha1.ZarfChart) types.Chart { LocalPath: ch.LocalPath, Version: ch.Version, SchemaValidation: ch.SchemaValidation, - Variables: ch.Variables, + Variables: chartVarsToGeneric(ch.Variables), Values: chartValuesToGeneric(ch.Values), } return gc @@ -237,7 +237,7 @@ func actionToGeneric(a v1alpha1.ZarfComponentAction) types.ComponentAction { Description: a.Description, Wait: waitToGeneric(a.Wait), EnableValues: derefBool(a.Template), - SetVariables: a.SetVariables, + SetVariables: varsToGeneric(a.SetVariables), DeprecatedSetVariable: a.DeprecatedSetVariable, } @@ -301,8 +301,8 @@ func ConvertFromGeneric(g types.Package) v1alpha1.ZarfPackage { Build: buildFromGeneric(g.Build), Values: v1alpha1.ZarfValues{Files: g.Values.Files, Schema: g.Values.Schema}, Documentation: g.Documentation, - Variables: append([]v1alpha1.InteractiveVariable(nil), g.Variables...), - Constants: append([]v1alpha1.Constant(nil), g.Constants...), + Variables: interactiveVarsFromGeneric(g.Variables), + Constants: constantsFromGeneric(g.Constants), } if pkg.Kind == "" { @@ -412,8 +412,8 @@ func componentFromGeneric(c types.Component) v1alpha1.ZarfComponent { Default: c.Default, Required: requiredFromGeneric(c.Optional, c.Required), DeprecatedGroup: c.Group, - DataInjections: c.DataInjections, - HealthChecks: c.HealthChecks, + DataInjections: dataInjectionsFromGeneric(c.DataInjections), + HealthChecks: healthChecksFromGeneric(c.HealthChecks), Repos: c.Repositories, Only: v1alpha1.ZarfComponentOnlyTarget{ LocalOS: c.Target.OS, @@ -521,7 +521,7 @@ func chartFromGeneric(ch types.Chart) v1alpha1.ZarfChart { GitPath: ch.GitPath, LocalPath: ch.LocalPath, Version: ch.Version, - Variables: ch.Variables, + Variables: chartVarsFromGeneric(ch.Variables), } // Prefer preserved v1alpha1 SchemaValidation; otherwise derive from SkipSchemaValidation. @@ -617,7 +617,7 @@ func actionFromGeneric(a types.ComponentAction) v1alpha1.ZarfComponentAction { Cmd: a.Cmd, Description: a.Description, Wait: waitFromGeneric(a.Wait), - SetVariables: a.SetVariables, + SetVariables: varsFromGeneric(a.SetVariables), DeprecatedSetVariable: a.DeprecatedSetVariable, } @@ -682,3 +682,169 @@ func derefBool(p *bool) bool { } return *p } + +func variableToGeneric(v v1alpha1.Variable) types.Variable { + return types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: types.VariableType(v.Type), + } +} + +func variableFromGeneric(v types.Variable) v1alpha1.Variable { + return v1alpha1.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: v1alpha1.VariableType(v.Type), + } +} + +func varsToGeneric(in []v1alpha1.Variable) []types.Variable { + var out []types.Variable + for _, v := range in { + out = append(out, variableToGeneric(v)) + } + return out +} + +func varsFromGeneric(in []types.Variable) []v1alpha1.Variable { + var out []v1alpha1.Variable + for _, v := range in { + out = append(out, variableFromGeneric(v)) + } + return out +} + +func interactiveVarsToGeneric(in []v1alpha1.InteractiveVariable) []types.InteractiveVariable { + var out []types.InteractiveVariable + for _, v := range in { + out = append(out, types.InteractiveVariable{ + Variable: variableToGeneric(v.Variable), + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + return out +} + +func interactiveVarsFromGeneric(in []types.InteractiveVariable) []v1alpha1.InteractiveVariable { + var out []v1alpha1.InteractiveVariable + for _, v := range in { + out = append(out, v1alpha1.InteractiveVariable{ + Variable: variableFromGeneric(v.Variable), + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + return out +} + +func constantsToGeneric(in []v1alpha1.Constant) []types.Constant { + var out []types.Constant + for _, c := range in { + out = append(out, types.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + return out +} + +func constantsFromGeneric(in []types.Constant) []v1alpha1.Constant { + var out []v1alpha1.Constant + for _, c := range in { + out = append(out, v1alpha1.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + return out +} + +func chartVarsToGeneric(in []v1alpha1.ZarfChartVariable) []types.ZarfChartVariable { + var out []types.ZarfChartVariable + for _, v := range in { + out = append(out, types.ZarfChartVariable{Name: v.Name, Description: v.Description, Path: v.Path}) + } + return out +} + +func chartVarsFromGeneric(in []types.ZarfChartVariable) []v1alpha1.ZarfChartVariable { + var out []v1alpha1.ZarfChartVariable + for _, v := range in { + out = append(out, v1alpha1.ZarfChartVariable{Name: v.Name, Description: v.Description, Path: v.Path}) + } + return out +} + +func dataInjectionsToGeneric(in []v1alpha1.ZarfDataInjection) []types.ZarfDataInjection { + var out []types.ZarfDataInjection + for _, d := range in { + out = append(out, types.ZarfDataInjection{ + Source: d.Source, + Target: types.ZarfContainerTarget{ + Namespace: d.Target.Namespace, + Selector: d.Target.Selector, + Container: d.Target.Container, + Path: d.Target.Path, + }, + Compress: d.Compress, + }) + } + return out +} + +func dataInjectionsFromGeneric(in []types.ZarfDataInjection) []v1alpha1.ZarfDataInjection { + var out []v1alpha1.ZarfDataInjection + for _, d := range in { + out = append(out, v1alpha1.ZarfDataInjection{ + Source: d.Source, + Target: v1alpha1.ZarfContainerTarget{ + Namespace: d.Target.Namespace, + Selector: d.Target.Selector, + Container: d.Target.Container, + Path: d.Target.Path, + }, + Compress: d.Compress, + }) + } + return out +} + +func healthChecksToGeneric(in []v1alpha1.NamespacedObjectKindReference) []types.NamespacedObjectKindReference { + var out []types.NamespacedObjectKindReference + for _, h := range in { + out = append(out, types.NamespacedObjectKindReference{ + APIVersion: h.APIVersion, + Kind: h.Kind, + Namespace: h.Namespace, + Name: h.Name, + }) + } + return out +} + +func healthChecksFromGeneric(in []types.NamespacedObjectKindReference) []v1alpha1.NamespacedObjectKindReference { + var out []v1alpha1.NamespacedObjectKindReference + for _, h := range in { + out = append(out, v1alpha1.NamespacedObjectKindReference{ + APIVersion: h.APIVersion, + Kind: h.Kind, + Namespace: h.Namespace, + Name: h.Name, + }) + } + return out +} diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 8ecad1ccee..468fb33041 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -7,7 +7,6 @@ package v1beta1 import ( "strings" - "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/api/v1beta1" "github.com/zarf-dev/zarf/src/internal/api/types" ) @@ -15,7 +14,7 @@ import ( // ConvertToGeneric converts a v1beta1 Package to the internal generic representation. func ConvertToGeneric(pkg v1beta1.Package) types.Package { // Preserve an already-recorded original across multi-hop conversions; otherwise this is the original. - originalAPIVersion := pkg.Build.OriginalAPIVersion() + originalAPIVersion := pkg.Build.GetOriginalAPIVersion() if originalAPIVersion == "" { originalAPIVersion = pkg.APIVersion } @@ -52,8 +51,8 @@ func ConvertToGeneric(pkg v1beta1.Package) types.Package { Schema: pkg.Values.Schema, }, Documentation: pkg.Documentation, - Variables: pkg.GetDeprecatedVariables(), - Constants: pkg.GetDeprecatedConstants(), + Variables: deprecatedVarsToGeneric(pkg.GetDeprecatedVariables()), + Constants: deprecatedConstantsToGeneric(pkg.GetDeprecatedConstants()), } for _, vr := range pkg.Build.VersionRequirements { @@ -84,7 +83,7 @@ func componentToGeneric(c v1beta1.Component) types.Component { }, Import: importToGeneric(c.Import), Actions: actionsToGeneric(c.Actions), - DataInjections: c.GetDeprecatedDataInjections(), + DataInjections: deprecatedDataInjectionsToGeneric(c.GetDeprecatedDataInjections()), } for _, m := range c.Manifests { @@ -164,7 +163,7 @@ func chartToGeneric(ch v1beta1.Chart) types.Chart { ServerSideApply: string(ch.ServerSideApply), SkipWait: ch.SkipWait, Version: ch.GetDeprecatedVersion(), - Variables: ch.GetDeprecatedVariables(), + Variables: deprecatedChartVarsToGeneric(ch.GetDeprecatedVariables()), } if ch.HelmRepository != nil { @@ -247,14 +246,13 @@ func actionToGeneric(a v1beta1.ComponentAction) types.ComponentAction { Description: a.Description, Wait: waitToGeneric(a.Wait), EnableValues: a.EnableValues, - SetVariables: a.GetDeprecatedSetVariables(), + SetVariables: deprecatedSetVarsToGeneric(a.GetDeprecatedSetVariables()), } for _, sv := range a.SetValues { ga.SetValues = append(ga.SetValues, types.SetValue{ - Key: sv.Key, - Value: sv.Value, - Type: string(sv.Type), + Key: sv.Key, + Type: string(sv.Type), }) } @@ -312,9 +310,6 @@ func ConvertFromGeneric(g types.Package) v1beta1.Package { pkg.Kind = v1beta1.ZarfPackageConfig } - pkg.SetDeprecatedVariables(g.Variables) - pkg.SetDeprecatedConstants(g.Constants) - for _, c := range g.Components { pkg.Components = append(pkg.Components, componentFromGeneric(c)) } @@ -462,8 +457,6 @@ func componentFromGeneric(c types.Component) v1beta1.Component { }) } - bc.SetDeprecatedDataInjections(c.DataInjections) - return bc } @@ -590,11 +583,6 @@ func chartFromGeneric(ch types.Chart) v1beta1.Chart { bc.SkipSchemaValidation = true } - if ch.Version != "" { - bc.SetDeprecatedVersion(ch.Version) - } - bc.SetDeprecatedVariables(ch.Variables) - return bc } @@ -660,9 +648,8 @@ func actionFromGeneric(a types.ComponentAction) v1beta1.ComponentAction { for _, sv := range a.SetValues { ba.SetValues = append(ba.SetValues, v1beta1.SetValue{ - Key: sv.Key, - Value: sv.Value, - Type: v1beta1.SetValueType(sv.Type), + Key: sv.Key, + Type: v1beta1.SetValueType(sv.Type), }) } @@ -674,14 +661,6 @@ func actionFromGeneric(a types.ComponentAction) v1beta1.ComponentAction { } } - setVariables := append([]v1alpha1.Variable(nil), a.SetVariables...) - if a.DeprecatedSetVariable != "" { - setVariables = append(setVariables, v1alpha1.Variable{Name: a.DeprecatedSetVariable}) - } - if len(setVariables) > 0 { - ba.SetDeprecatedSetVariables(setVariables) - } - return ba } @@ -725,3 +704,73 @@ func isGitURL(url string) bool { } return strings.HasSuffix(url, ".git") } + +func deprecatedVarToGeneric(v v1beta1.Variable) types.Variable { + return types.Variable{ + Name: v.Name, + Sensitive: v.Sensitive, + AutoIndent: v.AutoIndent, + Pattern: v.Pattern, + Type: types.VariableType(v.Type), + } +} + +func deprecatedVarsToGeneric(in []v1beta1.InteractiveVariable) []types.InteractiveVariable { + var out []types.InteractiveVariable + for _, v := range in { + out = append(out, types.InteractiveVariable{ + Variable: deprecatedVarToGeneric(v.Variable), + Description: v.Description, + Default: v.Default, + Prompt: v.Prompt, + }) + } + return out +} + +func deprecatedConstantsToGeneric(in []v1beta1.Constant) []types.Constant { + var out []types.Constant + for _, c := range in { + out = append(out, types.Constant{ + Name: c.Name, + Value: c.Value, + Description: c.Description, + AutoIndent: c.AutoIndent, + Pattern: c.Pattern, + }) + } + return out +} + +func deprecatedChartVarsToGeneric(in []v1beta1.ZarfChartVariable) []types.ZarfChartVariable { + var out []types.ZarfChartVariable + for _, v := range in { + out = append(out, types.ZarfChartVariable{Name: v.Name, Description: v.Description, Path: v.Path}) + } + return out +} + +func deprecatedSetVarsToGeneric(in []v1beta1.Variable) []types.Variable { + var out []types.Variable + for _, v := range in { + out = append(out, deprecatedVarToGeneric(v)) + } + return out +} + +func deprecatedDataInjectionsToGeneric(in []v1beta1.ZarfDataInjection) []types.ZarfDataInjection { + var out []types.ZarfDataInjection + for _, d := range in { + out = append(out, types.ZarfDataInjection{ + Source: d.Source, + Target: types.ZarfContainerTarget{ + Namespace: d.Target.Namespace, + Selector: d.Target.Selector, + Container: d.Target.Container, + Path: d.Target.Path, + }, + Compress: d.Compress, + }) + } + return out +} From 4d4151f9a0a78b49833109c075268f332670bf8f Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 4 Jun 2026 13:19:32 -0400 Subject: [PATCH 23/24] fix name Signed-off-by: Austin Abro --- src/api/convert/convert_test.go | 2 +- src/internal/api/v1beta1/convert.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/convert/convert_test.go b/src/api/convert/convert_test.go index 349f2d7e07..73b22da4f4 100644 --- a/src/api/convert/convert_test.go +++ b/src/api/convert/convert_test.go @@ -273,7 +273,7 @@ func TestV1Alpha1PkgToV1Beta1_ServiceInference(t *testing.T) { {name: "seed registry", compName: "zarf-seed-registry", service: v1beta1.ServiceSeedRegistry}, {name: "injector", compName: "zarf-injector", service: v1beta1.ServiceInjector}, {name: "agent", compName: "zarf-agent", service: v1beta1.ServiceAgent}, - {name: "git server", compName: "zarf-gitea", service: v1beta1.ServiceGitServer}, + {name: "git server", compName: "git-server", service: v1beta1.ServiceGitServer}, {name: "no service", compName: "my-app", service: ""}, } for _, tt := range tests { diff --git a/src/internal/api/v1beta1/convert.go b/src/internal/api/v1beta1/convert.go index 468fb33041..541857e8c2 100644 --- a/src/internal/api/v1beta1/convert.go +++ b/src/internal/api/v1beta1/convert.go @@ -484,7 +484,7 @@ func serviceFromGeneric(c types.Component) v1beta1.Service { return v1beta1.ServiceInjector case "zarf-agent": return v1beta1.ServiceAgent - case "zarf-gitea": + case "git-server": return v1beta1.ServiceGitServer } return "" From d28c334388a0bc5167c7556a5eaaec97f2b3c6cf Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 8 Jun 2026 09:05:34 -0400 Subject: [PATCH 24/24] convert Signed-off-by: Austin Abro --- src/cmd/internal_convert.go | 39 ++++++++++ src/cmd/internal_convert_test.go | 125 +++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/cmd/internal_convert_test.go diff --git a/src/cmd/internal_convert.go b/src/cmd/internal_convert.go index 39300766c0..cbb3e77154 100644 --- a/src/cmd/internal_convert.go +++ b/src/cmd/internal_convert.go @@ -77,13 +77,26 @@ func checkRemovedFields(pkg v1alpha1.ZarfPackage) error { // TODO, add link to connected docs when available errs = append(errs, fmt.Errorf(".metadata.yolo is removed without replacement in v1beta1 — replace it with connected deployments")) } + // TODO link to values docs + if len(pkg.Variables) > 0 { + errs = append(errs, fmt.Errorf(".variables is removed in v1beta1 — consider using Zarf values instead")) + } + if len(pkg.Constants) > 0 { + errs = append(errs, fmt.Errorf(".constants is removed in v1beta1 — consider using Zarf values instead")) + } for _, c := range pkg.Components { if c.DeprecatedGroup != "" { errs = append(errs, fmt.Errorf("can't convert component %s, .components.group is removed without replacement in v1beta1 — consider using .components[x].only.flavor instead", c.Name)) } + if c.Default { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.default is removed without replacement in v1beta1", c.Name)) + } if len(c.DataInjections) > 0 { errs = append(errs, fmt.Errorf("can't convert component %s, .components.dataInjections is removed without replacement in v1beta1 — see https://docs.zarf.dev/best-practices/data-injections-migration/ for alternatives", c.Name)) } + if len(c.Only.Cluster.Distros) > 0 { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.only.cluster.distro is removed without replacement in v1beta1", c.Name)) + } // TODO add link to example of newer import system if c.Import.Name != "" { errs = append(errs, fmt.Errorf("can't convert component %s, .components.import.name is removed without replacement in v1beta1", c.Name)) @@ -94,6 +107,32 @@ func checkRemovedFields(pkg v1alpha1.ZarfPackage) error { errs = append(errs, fmt.Errorf("can't convert chart %s in component %s, .components.charts.variables is removed without replacement in v1beta1 — consider using Zarf values instead", ch.Name, c.Name)) } } + errs = append(errs, checkRemovedActionFields(c)...) } return errors.Join(errs...) } + +// checkRemovedActionFields reports actions using setVariable/setVariables, which are removed in v1beta1 in favor of setValues. +func checkRemovedActionFields(c v1alpha1.ZarfComponent) []error { + var errs []error + actionSets := []struct { + onAny string + set v1alpha1.ZarfComponentActionSet + }{ + {"onCreate", c.Actions.OnCreate}, + {"onDeploy", c.Actions.OnDeploy}, + {"onRemove", c.Actions.OnRemove}, + } + for _, as := range actionSets { + set := as.set + for _, actions := range [][]v1alpha1.ZarfComponentAction{set.Before, set.After, set.OnSuccess, set.OnFailure} { + for _, a := range actions { + if a.DeprecatedSetVariable != "" || len(a.SetVariables) > 0 { + errs = append(errs, fmt.Errorf("can't convert component %s, .components.actions.%s setVariable/setVariables is removed in v1beta1 — use setValues instead", c.Name, as.onAny)) + break + } + } + } + } + return errs +} diff --git a/src/cmd/internal_convert_test.go b/src/cmd/internal_convert_test.go new file mode 100644 index 0000000000..dbd933e9be --- /dev/null +++ b/src/cmd/internal_convert_test.go @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cmd + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/zarf-dev/zarf/src/api/v1alpha1" +) + +func TestCheckRemovedFields(t *testing.T) { + t.Parallel() + tests := []struct { + name string + pkg v1alpha1.ZarfPackage + wantErr string + }{ + { + name: "yolo", + pkg: v1alpha1.ZarfPackage{Metadata: v1alpha1.ZarfMetadata{YOLO: true}}, + wantErr: ".metadata.yolo", + }, + { + name: "package variables", + pkg: v1alpha1.ZarfPackage{Variables: []v1alpha1.InteractiveVariable{{Variable: v1alpha1.Variable{Name: "FOO"}}}}, + wantErr: ".variables is removed", + }, + { + name: "package constants", + pkg: v1alpha1.ZarfPackage{Constants: []v1alpha1.Constant{{Name: "FOO"}}}, + wantErr: ".constants is removed", + }, + { + name: "component group", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{Name: "c", DeprecatedGroup: "g"}}}, + wantErr: ".components.group", + }, + { + name: "component default", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{Name: "c", Default: true}}}, + wantErr: ".components.default", + }, + { + name: "data injections", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{Name: "c", DataInjections: []v1alpha1.ZarfDataInjection{{Source: "/data"}}}}}, + wantErr: ".components.dataInjections", + }, + { + name: "cluster distro", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{ + Name: "c", + Only: v1alpha1.ZarfComponentOnlyTarget{Cluster: v1alpha1.ZarfComponentOnlyCluster{Distros: []string{"k3s"}}}, + }}}, + wantErr: ".components.only.cluster.distro", + }, + { + name: "import name", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{Name: "c", Import: v1alpha1.ZarfComponentImport{Name: "n"}}}}, + wantErr: ".components.import.name", + }, + { + name: "chart variables", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{Name: "c", Charts: []v1alpha1.ZarfChart{{Name: "ch", Variables: []v1alpha1.ZarfChartVariable{{Name: "V"}}}}}}}, + wantErr: ".components.charts.variables", + }, + { + name: "action setVariables", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{ + Name: "c", + Actions: v1alpha1.ZarfComponentActions{ + OnDeploy: v1alpha1.ZarfComponentActionSet{ + Before: []v1alpha1.ZarfComponentAction{{Cmd: "echo", SetVariables: []v1alpha1.Variable{{Name: "V"}}}}, + }, + }, + }}}, + wantErr: ".components.actions.onDeploy", + }, + { + name: "action deprecated setVariable", + pkg: v1alpha1.ZarfPackage{Components: []v1alpha1.ZarfComponent{{ + Name: "c", + Actions: v1alpha1.ZarfComponentActions{ + OnCreate: v1alpha1.ZarfComponentActionSet{ + OnSuccess: []v1alpha1.ZarfComponentAction{{Cmd: "echo", DeprecatedSetVariable: "V"}}, + }, + }, + }}}, + wantErr: ".components.actions.onCreate", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := checkRemovedFields(tt.pkg) + require.Error(t, err) + require.Contains(t, err.Error(), tt.wantErr) + }) + } +} + +func TestCheckRemovedFieldsConvertible(t *testing.T) { + t.Parallel() + pkg := v1alpha1.ZarfPackage{ + Kind: v1alpha1.ZarfPackageConfig, + Metadata: v1alpha1.ZarfMetadata{ + Name: "convertible", + }, + Components: []v1alpha1.ZarfComponent{ + { + Name: "web", + Images: []string{"nginx:latest"}, + Charts: []v1alpha1.ZarfChart{{Name: "ch", LocalPath: "./chart"}}, + Actions: v1alpha1.ZarfComponentActions{ + OnDeploy: v1alpha1.ZarfComponentActionSet{ + Before: []v1alpha1.ZarfComponentAction{{Cmd: "echo hi", SetValues: []v1alpha1.SetValue{{Key: "k"}}}}, + }, + }, + }, + }, + } + require.NoError(t, checkRemovedFields(pkg)) +}