From 53465d7a3e5f28897d892950fc5c1dd86ecbff2f Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Wed, 15 May 2024 10:32:40 -0500 Subject: [PATCH 001/132] refactor: context usage in k8s code (#2405) ## Description refactor context usage in k8s code ## Related Issue Relates to https://github.com/defenseunicorns/zarf/issues/2365 ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [x] Other (security config, docs update, etc) ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- .pre-commit-config.yaml | 1 + src/cmd/common/utils.go | 13 ++ src/cmd/connect.go | 15 +- src/cmd/destroy.go | 18 +- src/cmd/dev.go | 35 ++-- src/cmd/initialize.go | 9 +- src/cmd/internal.go | 33 ++-- src/cmd/package.go | 70 ++++---- src/cmd/root.go | 5 + src/cmd/tools/archiver.go | 5 +- src/cmd/tools/crane.go | 26 +-- src/cmd/tools/helm/load_plugins.go | 11 +- src/cmd/tools/helm/repo_add.go | 16 +- src/cmd/tools/helm/repo_index.go | 6 +- src/cmd/tools/zarf.go | 23 +-- src/extensions/bigbang/test/bigbang_test.go | 38 +++-- src/internal/packager/git/gitea.go | 25 +-- src/internal/packager/helm/post-render.go | 26 +-- src/internal/packager/helm/zarf.go | 26 ++- src/internal/packager/images/push.go | 5 +- src/pkg/cluster/common.go | 17 +- src/pkg/cluster/data.go | 7 +- src/pkg/cluster/injector.go | 103 ++++++------ src/pkg/cluster/namespace.go | 4 +- src/pkg/cluster/secrets.go | 17 +- src/pkg/cluster/state.go | 27 +-- src/pkg/cluster/tunnel.go | 27 +-- src/pkg/cluster/zarf.go | 81 ++++----- src/pkg/k8s/common.go | 48 +++--- src/pkg/k8s/configmap.go | 19 +-- src/pkg/k8s/dynamic.go | 14 +- src/pkg/k8s/hpa.go | 16 +- src/pkg/k8s/info.go | 11 +- src/pkg/k8s/namespace.go | 34 ++-- src/pkg/k8s/nodes.go | 8 +- src/pkg/k8s/pods.go | 177 +++++++++++--------- src/pkg/k8s/sa.go | 44 ++--- src/pkg/k8s/secrets.go | 24 +-- src/pkg/k8s/services.go | 34 ++-- src/pkg/k8s/tunnel.go | 49 ++++-- src/pkg/packager/common.go | 20 +-- src/pkg/packager/common_test.go | 3 +- src/pkg/packager/deploy.go | 77 +++++---- src/pkg/packager/dev.go | 7 +- src/pkg/packager/mirror.go | 11 +- src/pkg/packager/remove.go | 25 +-- src/pkg/packager/sources/cluster.go | 11 +- src/test/e2e/21_connect_creds_test.go | 15 +- src/test/e2e/22_git_and_gitops_test.go | 17 +- src/test/e2e/23_data_injection_test.go | 10 +- src/test/e2e/26_simple_packages_test.go | 3 +- src/test/e2e/30_config_file_test.go | 12 +- src/test/e2e/33_component_webhooks_test.go | 4 +- src/test/e2e/99_yolo_test.go | 3 +- src/test/external/ext_in_cluster_test.go | 6 +- src/test/external/ext_out_cluster_test.go | 6 +- 56 files changed, 768 insertions(+), 629 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e96cba653..38ae99b9bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,6 +9,7 @@ repos: args: - "--allow-missing-credentials" - id: detect-private-key + exclude: "src/test/e2e/30_config_file_test.go" - id: end-of-file-fixer exclude: site/src/content/docs/commands/.* - id: fix-byte-order-marker diff --git a/src/cmd/common/utils.go b/src/cmd/common/utils.go index 54113ee90f..4fe6fa5043 100644 --- a/src/cmd/common/utils.go +++ b/src/cmd/common/utils.go @@ -5,11 +5,13 @@ package common import ( + "context" "os" "os/signal" "syscall" "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -35,3 +37,14 @@ func ExitOnInterrupt() { } }() } + +// NewClusterOrDie creates a new Cluster instance and waits for the cluster to be ready or throws a fatal error. +func NewClusterOrDie(ctx context.Context) *cluster.Cluster { + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + message.Fatalf(err, "Failed to connect to cluster") + } + return c +} diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 39b382cad3..19422df967 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -32,7 +32,7 @@ var ( Aliases: []string{"c"}, Short: lang.CmdConnectShort, Long: lang.CmdConnectLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { var target string if len(args) > 0 { target = args[0] @@ -43,12 +43,14 @@ var ( spinner.Fatalf(err, lang.CmdConnectErrCluster, err.Error()) } + ctx := cmd.Context() + var tunnel *k8s.Tunnel if connectResourceName != "" { zt := cluster.NewTunnelInfo(connectNamespace, connectResourceType, connectResourceName, "", connectLocalPort, connectRemotePort) - tunnel, err = c.ConnectTunnelInfo(zt) + tunnel, err = c.ConnectTunnelInfo(ctx, zt) } else { - tunnel, err = c.Connect(target) + tunnel, err = c.Connect(ctx, target) } if err != nil { spinner.Fatalf(err, lang.CmdConnectErrService, err.Error()) @@ -90,8 +92,11 @@ var ( Use: "list", Aliases: []string{"l"}, Short: lang.CmdConnectListShort, - Run: func(_ *cobra.Command, _ []string) { - cluster.NewClusterOrDie().PrintConnectTable() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + if err := common.NewClusterOrDie(ctx).PrintConnectTable(ctx); err != nil { + message.Fatal(err, err.Error()) + } }, } ) diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index bc9dbe7d61..429aec2c16 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -10,10 +10,10 @@ import ( "regexp" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/helm" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" @@ -28,16 +28,14 @@ var destroyCmd = &cobra.Command{ Aliases: []string{"d"}, Short: lang.CmdDestroyShort, Long: lang.CmdDestroyLong, - Run: func(_ *cobra.Command, _ []string) { - c, err := cluster.NewClusterWithWait(cluster.DefaultTimeout) - if err != nil { - message.Fatalf(err, lang.ErrNoClusterConnection) - } + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) // NOTE: If 'zarf init' failed to deploy the k3s component (or if we're looking at the wrong kubeconfig) // there will be no zarf-state to load and the struct will be empty. In these cases, if we can find // the scripts to remove k3s, we will still try to remove a locally installed k3s cluster - state, err := c.LoadZarfState() + state, err := c.LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } @@ -74,10 +72,12 @@ var destroyCmd = &cobra.Command{ helm.Destroy(removeComponents) // If Zarf didn't deploy the cluster, only delete the ZarfNamespace - c.DeleteZarfNamespace() + if err := c.DeleteZarfNamespace(ctx); err != nil { + message.Fatal(err, err.Error()) + } // Remove zarf agent labels and secrets from namespaces Zarf doesn't manage - c.StripZarfLabelsAndSecretsFromNamespaces() + c.StripZarfLabelsAndSecretsFromNamespaces(ctx) } }, } diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 068565f7c7..17962176a4 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -40,7 +40,7 @@ var devDeployCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevDeployShort, Long: lang.CmdDevDeployLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() @@ -50,12 +50,10 @@ var devDeployCmd = &cobra.Command{ pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Create the package - if err := pkgClient.DevDeploy(); err != nil { + if err := pkgClient.DevDeploy(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdDevDeployErr, err.Error()) } }, @@ -209,19 +207,15 @@ var devFindImagesCmd = &cobra.Command{ Run: func(_ *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Find all the images the package might need if _, err := pkgClient.FindImages(); err != nil { message.Fatalf(err, lang.CmdDevFindImagesErr, err.Error()) } @@ -292,8 +286,14 @@ func init() { // use the package create config for this and reset it here to avoid overwriting the config.CreateOptions.SetVariables devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) - devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") - devFindImagesCmd.Flags().MarkHidden("set") + err := devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devFindImagesCmd.Flags().MarkHidden("set") + if err != nil { + message.Fatal(err, err.Error()) + } devFindImagesCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) @@ -341,7 +341,16 @@ func bindDevGenerateFlags(_ *viper.Viper) { generateFlags.StringVar(&pkgConfig.GenerateOpts.Output, "output-directory", "", "Output directory for the generated zarf.yaml") generateFlags.StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) - devGenerateCmd.MarkFlagRequired("url") - devGenerateCmd.MarkFlagRequired("version") - devGenerateCmd.MarkFlagRequired("output-directory") + err := devGenerateCmd.MarkFlagRequired("url") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devGenerateCmd.MarkFlagRequired("version") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devGenerateCmd.MarkFlagRequired("output-directory") + if err != nil { + message.Fatal(err, err.Error()) + } } diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index 0f8a1ee322..b41c3ca8d9 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -35,7 +35,7 @@ var initCmd = &cobra.Command{ Short: lang.CmdInitShort, Long: lang.CmdInitLong, Example: lang.CmdInitExample, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { zarfLogo := message.GetLogo() _, _ = fmt.Fprintln(os.Stderr, zarfLogo) @@ -58,17 +58,16 @@ var initCmd = &cobra.Command{ message.Fatal(err, err.Error()) } - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - // Deploy everything - err = pkgClient.Deploy() + ctx := cmd.Context() + + err = pkgClient.Deploy(ctx) if err != nil { message.Fatal(err, err.Error()) } diff --git a/src/cmd/internal.go b/src/cmd/internal.go index 55b7f39232..97263d0b08 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -16,7 +16,6 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent" "github.com/defenseunicorns/zarf/src/internal/packager/git" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" "github.com/invopop/jsonschema" @@ -194,15 +193,17 @@ var createReadOnlyGiteaUser = &cobra.Command{ Use: "create-read-only-gitea-user", Short: lang.CmdInternalCreateReadOnlyGiteaUserShort, Long: lang.CmdInternalCreateReadOnlyGiteaUserLong, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + // Load the state so we can get the credentials for the admin git user - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } // Create the non-admin user - if err = git.New(state.GitServer).CreateReadOnlyUser(); err != nil { + if err = git.New(state.GitServer).CreateReadOnlyUser(ctx); err != nil { message.WarnErr(err, lang.CmdInternalCreateReadOnlyGiteaUserErr) } }, @@ -212,24 +213,26 @@ var createPackageRegistryToken = &cobra.Command{ Use: "create-artifact-registry-token", Short: lang.CmdInternalArtifactRegistryGiteaTokenShort, Long: lang.CmdInternalArtifactRegistryGiteaTokenLong, - Run: func(_ *cobra.Command, _ []string) { - // Load the state so we can get the credentials for the admin git user - c := cluster.NewClusterOrDie() - state, err := c.LoadZarfState() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) + state, err := c.LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } // If we are setup to use an internal artifact server, create the artifact registry token if state.ArtifactServer.InternalServer { - token, err := git.New(state.GitServer).CreatePackageRegistryToken() + token, err := git.New(state.GitServer).CreatePackageRegistryToken(ctx) if err != nil { message.WarnErr(err, lang.CmdInternalArtifactRegistryGiteaTokenErr) } state.ArtifactServer.PushToken = token.Sha1 - c.SaveZarfState(state) + if err := c.SaveZarfState(ctx, state); err != nil { + message.Fatal(err, err.Error()) + } } }, } @@ -238,10 +241,11 @@ var updateGiteaPVC = &cobra.Command{ Use: "update-gitea-pvc", Short: lang.CmdInternalUpdateGiteaPVCShort, Long: lang.CmdInternalUpdateGiteaPVCLong, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() // There is a possibility that the pvc does not yet exist and Gitea helm chart should create it - helmShouldCreate, err := git.UpdateGiteaPVC(rollback) + helmShouldCreate, err := git.UpdateGiteaPVC(ctx, rollback) if err != nil { message.WarnErr(err, lang.CmdInternalUpdateGiteaPVCErr) } @@ -294,6 +298,9 @@ func addHiddenDummyFlag(cmd *cobra.Command, flagDummy string) { if cmd.PersistentFlags().Lookup(flagDummy) == nil { var dummyStr string cmd.PersistentFlags().StringVar(&dummyStr, flagDummy, "", "") - cmd.PersistentFlags().MarkHidden(flagDummy) + err := cmd.PersistentFlags().MarkHidden(flagDummy) + if err != nil { + message.Fatal(err, err.Error()) + } } } diff --git a/src/cmd/package.go b/src/cmd/package.go index a0f124a864..e8817bf60b 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -48,16 +48,13 @@ var packageCreateCmd = &cobra.Command{ config.CommonOptions.CachePath = config.ZarfDefaultCachePath } - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Create the package if err := pkgClient.Create(); err != nil { message.Fatalf(err, lang.CmdPackageCreateErr, err.Error()) } @@ -70,22 +67,19 @@ var packageDeployCmd = &cobra.Command{ Short: lang.CmdPackageDeployShort, Long: lang.CmdPackageDeployLong, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) - // Ensure uppercase keys from viper and CLI --set v := common.GetViper() - - // Merge the viper config file variables and provided CLI flag variables (CLI takes precedence)) pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Deploy the package - if err := pkgClient.Deploy(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Deploy(ctx); err != nil { message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) } }, @@ -98,15 +92,15 @@ var packageMirrorCmd = &cobra.Command{ Long: lang.CmdPackageMirrorLong, Example: lang.CmdPackageMirrorExample, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Deploy the package - if err := pkgClient.Mirror(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Mirror(ctx); err != nil { message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) } }, @@ -123,11 +117,9 @@ var packageInspectCmd = &cobra.Command{ src := identifyAndFallbackToClusterSource() - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - // Inspect the package if err := pkgClient.Inspect(); err != nil { message.Fatalf(err, lang.CmdPackageInspectErr, err.Error()) } @@ -139,9 +131,9 @@ var packageListCmd = &cobra.Command{ Use: "list", Aliases: []string{"l", "ls"}, Short: lang.CmdPackageListShort, - Run: func(_ *cobra.Command, _ []string) { - // Get all the deployed packages - deployedZarfPackages, errs := cluster.NewClusterOrDie().GetDeployedZarfPackages() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + deployedZarfPackages, errs := common.NewClusterOrDie(ctx).GetDeployedZarfPackages(ctx) if len(errs) > 0 && len(deployedZarfPackages) == 0 { message.Fatalf(errs, lang.CmdPackageListNoPackageWarn) } @@ -161,7 +153,6 @@ var packageListCmd = &cobra.Command{ }) } - // Print out the table for the user header := []string{"Package", "Version", "Components"} message.Table(header, packageData) @@ -177,15 +168,17 @@ var packageRemoveCmd = &cobra.Command{ Aliases: []string{"u", "rm"}, Args: cobra.MaximumNArgs(1), Short: lang.CmdPackageRemoveShort, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) src := identifyAndFallbackToClusterSource() - // Configure the packager + pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - if err := pkgClient.Remove(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Remove(ctx); err != nil { message.Fatalf(err, lang.CmdPackageRemoveErr, err.Error()) } }, @@ -220,11 +213,9 @@ var packagePublishCmd = &cobra.Command{ pkgConfig.PublishOpts.PackageDestination = ref.String() - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Publish the package if err := pkgClient.Publish(); err != nil { message.Fatalf(err, lang.CmdPackagePublishErr, err.Error()) } @@ -239,11 +230,9 @@ var packagePullCmd = &cobra.Command{ Run: func(_ *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = args[0] - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Pull the package if err := pkgClient.Pull(); err != nil { message.Fatalf(err, lang.CmdPackagePullErr, err.Error()) } @@ -288,7 +277,7 @@ func identifyAndFallbackToClusterSource() (src sources.PackageSource) { return src } -func getPackageCompletionArgs(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { +func getPackageCompletionArgs(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { var pkgCandidates []string c, err := cluster.NewCluster() @@ -296,8 +285,9 @@ func getPackageCompletionArgs(_ *cobra.Command, _ []string, _ string) ([]string, return pkgCandidates, cobra.ShellCompDirectiveDefault } - // Get all the deployed packages - deployedZarfPackages, _ := c.GetDeployedZarfPackages() + ctx := cmd.Context() + + deployedZarfPackages, _ := c.GetDeployedZarfPackages(ctx) // Populate list of package names for _, pkg := range deployedZarfPackages { pkgCandidates = append(pkgCandidates, pkg.Name) @@ -366,9 +356,18 @@ func bindCreateFlags(v *viper.Viper) { createFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - createFlags.MarkHidden("output-directory") - createFlags.MarkHidden("key") - createFlags.MarkHidden("key-pass") + err := createFlags.MarkHidden("output-directory") + if err != nil { + message.Fatal(err, err.Error()) + } + err = createFlags.MarkHidden("key") + if err != nil { + message.Fatal(err, err.Error()) + } + err = createFlags.MarkHidden("key-pass") + if err != nil { + message.Fatal(err, err.Error()) + } } func bindDeployFlags(v *viper.Viper) { @@ -388,7 +387,10 @@ func bindDeployFlags(v *viper.Viper) { deployFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", v.GetString(common.VPkgDeployShasum), lang.CmdPackageDeployFlagShasum) deployFlags.StringVar(&pkgConfig.PkgOpts.SGetKeyPath, "sget", v.GetString(common.VPkgDeploySget), lang.CmdPackageDeployFlagSget) - deployFlags.MarkHidden("sget") + err := deployFlags.MarkHidden("sget") + if err != nil { + message.Fatal(err, err.Error()) + } } func bindMirrorFlags(v *viper.Viper) { diff --git a/src/cmd/root.go b/src/cmd/root.go index 9563f382d3..83fb28e61d 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "fmt" "os" "strings" @@ -37,6 +38,10 @@ var rootCmd = &cobra.Command{ config.SkipLogFile = true } + // Set the global context for the root command and all child commands + ctx := context.Background() + cmd.SetContext(ctx) + common.SetupCLI() }, Short: lang.RootCmdShort, diff --git a/src/cmd/tools/archiver.go b/src/cmd/tools/archiver.go index 9a1ee1c022..344cc7398c 100644 --- a/src/cmd/tools/archiver.go +++ b/src/cmd/tools/archiver.go @@ -93,5 +93,8 @@ func init() { archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "decompress-all", false, "Decompress all tarballs in the archive") archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "unarchive-all", false, "Unarchive all tarballs in the archive") archiverDecompressCmd.MarkFlagsMutuallyExclusive("decompress-all", "unarchive-all") - archiverDecompressCmd.Flags().MarkHidden("decompress-all") + err := archiverDecompressCmd.Flags().MarkHidden("decompress-all") + if err != nil { + message.Fatal(err, err.Error()) + } } diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index 792e4b555c..b501aeac7a 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -124,13 +124,14 @@ func zarfCraneCatalog(cranePlatformOptions []crane.Option) *cobra.Command { return err } - // Load Zarf state - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { return err } - registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } @@ -173,8 +174,9 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command message.Note(lang.CmdToolsRegistryZarfState) - // Load the state (if able) - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { message.Warnf(lang.CmdToolsCraneConnectedButBadStateErr, err.Error()) return originalListFn(cmd, args) @@ -185,7 +187,7 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command return originalListFn(cmd, args) } - _, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + _, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } @@ -211,27 +213,27 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command return wrappedCommand } -func pruneImages(_ *cobra.Command, _ []string) error { +func pruneImages(cmd *cobra.Command, _ []string) error { // Try to connect to a Zarf initialized cluster c, err := cluster.NewCluster() if err != nil { return err } - // Load the state - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { return err } - // Load the currently deployed packages - zarfPackages, errs := c.GetDeployedZarfPackages() + zarfPackages, errs := c.GetDeployedZarfPackages(ctx) if len(errs) > 0 { return lang.ErrUnableToGetPackages } // Set up a tunnel to the registry if applicable - registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } diff --git a/src/cmd/tools/helm/load_plugins.go b/src/cmd/tools/helm/load_plugins.go index 28ea155030..f4d2800137 100644 --- a/src/cmd/tools/helm/load_plugins.go +++ b/src/cmd/tools/helm/load_plugins.go @@ -32,6 +32,7 @@ import ( "strings" "syscall" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/yaml" @@ -216,7 +217,10 @@ func loadCompletionForPlugin(pluginCmd *cobra.Command, plugin *plugin.Plugin) { if err != nil { // The file could be missing or invalid. No static completion for this plugin. if settings.Debug { - log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) + err := log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) + if err != nil { + message.Fatal(err, err.Error()) + } } // Continue to setup dynamic completion. cmds = &pluginCommand{} @@ -238,7 +242,10 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug if len(cmds.Name) == 0 { // Missing name for a command if settings.Debug { - log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) + err := log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) + if err != nil { + message.Fatal(err, err.Error()) + } } return } diff --git a/src/cmd/tools/helm/repo_add.go b/src/cmd/tools/helm/repo_add.go index d053689000..114cd020f5 100644 --- a/src/cmd/tools/helm/repo_add.go +++ b/src/cmd/tools/helm/repo_add.go @@ -31,6 +31,8 @@ import ( "time" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/gofrs/flock" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -77,13 +79,13 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { Use: "add [NAME] [URL]", Short: "add a chart repository", Args: require.ExactArgs(2), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { o.name = args[0] o.url = args[1] o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache - return o.run(out) + return o.run(cmd.Context(), out) }, } @@ -103,7 +105,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { return cmd } -func (o *repoAddOptions) run(out io.Writer) error { +func (o *repoAddOptions) run(ctx context.Context, out io.Writer) error { // Block deprecated repos if !o.allowDeprecatedRepos { for oldURL, newURL := range deprecatedRepos { @@ -128,11 +130,15 @@ func (o *repoAddOptions) run(out io.Writer) error { lockPath = o.repoFile + ".lock" } fileLock := flock.New(lockPath) - lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + lockCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) defer cancel() locked, err := fileLock.TryLockContext(lockCtx, time.Second) if err == nil && locked { - defer fileLock.Unlock() + defer func() { + if err := fileLock.Unlock(); err != nil { + message.Fatal(err, err.Error()) + } + }() } if err != nil { return err diff --git a/src/cmd/tools/helm/repo_index.go b/src/cmd/tools/helm/repo_index.go index a84a3af74c..1d6182e85e 100644 --- a/src/cmd/tools/helm/repo_index.go +++ b/src/cmd/tools/helm/repo_index.go @@ -27,6 +27,7 @@ import ( "path/filepath" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -101,7 +102,10 @@ func index(dir, url, mergeTo string) error { var i2 *repo.IndexFile if _, err := os.Stat(mergeTo); os.IsNotExist(err) { i2 = repo.NewIndexFile() - i2.WriteFile(mergeTo, helpers.ReadAllWriteUser) + err := i2.WriteFile(mergeTo, helpers.ReadAllWriteUser) + if err != nil { + message.Fatal(err, err.Error()) + } } else { i2, err = repo.LoadIndexFile(mergeTo) if err != nil { diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 363fe87c63..20655089bc 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -19,7 +19,6 @@ import ( "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/internal/packager/template" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/pki" @@ -51,8 +50,9 @@ var getCredsCmd = &cobra.Command{ Example: lang.CmdToolsGetCredsExample, Aliases: []string{"gc"}, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { - state, err := cluster.NewClusterOrDie().LoadZarfState() + Run: func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) if err != nil || state.Distro == "" { // If no distro the zarf secret did not load properly message.Fatalf(nil, lang.ErrLoadState) @@ -85,8 +85,9 @@ var updateCredsCmd = &cobra.Command{ } } - c := cluster.NewClusterOrDie() - oldState, err := c.LoadZarfState() + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) + oldState, err := c.LoadZarfState(ctx) if err != nil || oldState.Distro == "" { // If no distro the zarf secret did not load properly message.Fatalf(nil, lang.ErrLoadState) @@ -114,16 +115,16 @@ var updateCredsCmd = &cobra.Command{ if confirm { // Update registry and git pull secrets if slices.Contains(args, message.RegistryKey) { - c.UpdateZarfManagedImageSecrets(newState) + c.UpdateZarfManagedImageSecrets(ctx, newState) } if slices.Contains(args, message.GitKey) { - c.UpdateZarfManagedGitSecrets(newState) + c.UpdateZarfManagedGitSecrets(ctx, newState) } // Update artifact token (if internal) if slices.Contains(args, message.ArtifactKey) && newState.ArtifactServer.PushToken == "" && newState.ArtifactServer.InternalServer { g := git.New(oldState.GitServer) - tokenResponse, err := g.CreatePackageRegistryToken() + tokenResponse, err := g.CreatePackageRegistryToken(ctx) if err != nil { // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableCreateToken, err.Error()) @@ -133,7 +134,7 @@ var updateCredsCmd = &cobra.Command{ } // Save the final Zarf State - err = c.SaveZarfState(newState) + err = c.SaveZarfState(ctx, newState) if err != nil { message.Fatalf(err, lang.ErrSaveState) } @@ -150,14 +151,14 @@ var updateCredsCmd = &cobra.Command{ } if slices.Contains(args, message.GitKey) && newState.GitServer.InternalServer { g := git.New(newState.GitServer) - err = g.UpdateZarfGiteaUsers(oldState) + err = g.UpdateZarfGiteaUsers(ctx, oldState) if err != nil { // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateGit, err.Error()) } } if slices.Contains(args, message.AgentKey) { - err = h.UpdateZarfAgentValues() + err = h.UpdateZarfAgentValues(ctx) if err != nil { // Warn if we couldn't actually update the agent (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateAgent, err.Error()) diff --git a/src/extensions/bigbang/test/bigbang_test.go b/src/extensions/bigbang/test/bigbang_test.go index 64f0f95e23..4e016c6a6f 100644 --- a/src/extensions/bigbang/test/bigbang_test.go +++ b/src/extensions/bigbang/test/bigbang_test.go @@ -60,45 +60,47 @@ func TestReleases(t *testing.T) { zarfCache = fmt.Sprintf("--zarf-cache=%s", CIMount) } + ctx := context.Background() + // Initialize the cluster with the Git server and AMD64 architecture arch := "amd64" - stdOut, stdErr, err := zarfExec("init", "--components", "git-server", "--architecture", arch, tmpdir, "--confirm", zarfCache) + stdOut, stdErr, err := zarfExec(ctx, "init", "--components", "git-server", "--architecture", arch, tmpdir, "--confirm", zarfCache) require.NoError(t, err, stdOut, stdErr) // Remove the init package to free up disk space on the test runner - err = os.RemoveAll(fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, getZarfVersion(t))) + err = os.RemoveAll(fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, getZarfVersion(ctx, t))) require.NoError(t, err) // Build the previous version bbVersion := fmt.Sprintf("--set=BB_VERSION=%s", previous) bbMajor := fmt.Sprintf("--set=BB_MAJOR=%s", previous[0:1]) - stdOut, stdErr, err = zarfExec("package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, tmpdir, "--confirm") + stdOut, stdErr, err = zarfExec(ctx, "package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Clean up zarf cache to reduce disk pressure - stdOut, stdErr, err = zarfExec("tools", "clear-cache") + stdOut, stdErr, err = zarfExec(ctx, "tools", "clear-cache") require.NoError(t, err, stdOut, stdErr) // Deploy the previous version pkgPath := fmt.Sprintf("zarf-package-big-bang-test-%s-%s.tar.zst", arch, previous) - stdOut, stdErr, err = zarfExec("package", "deploy", pkgPath, tmpdir, "--confirm") + stdOut, stdErr, err = zarfExec(ctx, "package", "deploy", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // HACK: scale down the flux deployments due to very-low CPU in the test runner fluxControllers := []string{"helm-controller", "source-controller", "kustomize-controller", "notification-controller"} for _, deployment := range fluxControllers { - stdOut, stdErr, err = zarfExec("tools", "kubectl", "-n", "flux-system", "scale", "deployment", deployment, "--replicas=0") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "-n", "flux-system", "scale", "deployment", deployment, "--replicas=0") require.NoError(t, err, stdOut, stdErr) } // Cluster info - stdOut, stdErr, err = zarfExec("tools", "kubectl", "describe", "nodes") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "describe", "nodes") require.NoError(t, err, stdOut, stdErr) // Build the latest version bbVersion = fmt.Sprintf("--set=BB_VERSION=%s", latest) bbMajor = fmt.Sprintf("--set=BB_MAJOR=%s", latest[0:1]) - stdOut, stdErr, err = zarfExec("package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, "--differential", pkgPath, tmpdir, "--confirm") + stdOut, stdErr, err = zarfExec(ctx, "package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, "--differential", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Remove the previous version package @@ -106,23 +108,23 @@ func TestReleases(t *testing.T) { require.NoError(t, err) // Clean up zarf cache to reduce disk pressure - stdOut, stdErr, err = zarfExec("tools", "clear-cache") + stdOut, stdErr, err = zarfExec(ctx, "tools", "clear-cache") require.NoError(t, err, stdOut, stdErr) // Deploy the latest version pkgPath = fmt.Sprintf("zarf-package-big-bang-test-%s-%s-differential-%s.tar.zst", arch, previous, latest) - stdOut, stdErr, err = zarfExec("package", "deploy", pkgPath, tmpdir, "--confirm") + stdOut, stdErr, err = zarfExec(ctx, "package", "deploy", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Cluster info - stdOut, stdErr, err = zarfExec("tools", "kubectl", "describe", "nodes") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "describe", "nodes") require.NoError(t, err, stdOut, stdErr) // Test connectivity to Twistlock - testConnection(t) + testConnection(ctx, t) } -func testConnection(t *testing.T) { +func testConnection(ctx context.Context, t *testing.T) { // Establish the tunnel config c, err := cluster.NewCluster() require.NoError(t, err) @@ -130,7 +132,7 @@ func testConnection(t *testing.T) { require.NoError(t, err) // Establish the tunnel connection - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) require.NoError(t, err) defer tunnel.Close() @@ -140,14 +142,14 @@ func testConnection(t *testing.T) { require.Equal(t, 200, resp.StatusCode) } -func zarfExec(args ...string) (string, string, error) { - return exec.CmdWithContext(context.TODO(), exec.PrintCfg(), zarf, args...) +func zarfExec(ctx context.Context, args ...string) (string, string, error) { + return exec.CmdWithContext(ctx, exec.PrintCfg(), zarf, args...) } // getZarfVersion returns the current build/zarf version -func getZarfVersion(t *testing.T) string { +func getZarfVersion(ctx context.Context, t *testing.T) string { // Get the version of the CLI - stdOut, stdErr, err := zarfExec("version") + stdOut, stdErr, err := zarfExec(ctx, "version") require.NoError(t, err, stdOut, stdErr) return strings.Trim(stdOut, "\n") } diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go index f7bd86b00c..d243337d3c 100644 --- a/src/internal/packager/git/gitea.go +++ b/src/internal/packager/git/gitea.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -31,7 +32,7 @@ type CreateTokenResponse struct { } // CreateReadOnlyUser uses the Gitea API to create a non-admin Zarf user. -func (g *Git) CreateReadOnlyUser() error { +func (g *Git) CreateReadOnlyUser(ctx context.Context) error { message.Debugf("git.CreateReadOnlyUser()") c, err := cluster.NewCluster() @@ -44,7 +45,7 @@ func (g *Git) CreateReadOnlyUser() error { if err != nil { return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -102,16 +103,16 @@ func (g *Git) CreateReadOnlyUser() error { } // UpdateZarfGiteaUsers updates Zarf gitea users -func (g *Git) UpdateZarfGiteaUsers(oldState *types.ZarfState) error { +func (g *Git) UpdateZarfGiteaUsers(ctx context.Context, oldState *types.ZarfState) error { //Update git read only user password - err := g.UpdateGitUser(oldState.GitServer.PushPassword, g.Server.PullUsername, g.Server.PullPassword) + err := g.UpdateGitUser(ctx, oldState.GitServer.PushPassword, g.Server.PullUsername, g.Server.PullPassword) if err != nil { return fmt.Errorf("unable to update gitea read only user password: %w", err) } // Update Git admin password - err = g.UpdateGitUser(oldState.GitServer.PushPassword, g.Server.PushUsername, g.Server.PushPassword) + err = g.UpdateGitUser(ctx, oldState.GitServer.PushPassword, g.Server.PushUsername, g.Server.PushPassword) if err != nil { return fmt.Errorf("unable to update gitea admin user password: %w", err) } @@ -119,7 +120,7 @@ func (g *Git) UpdateZarfGiteaUsers(oldState *types.ZarfState) error { } // UpdateGitUser updates Zarf git server users -func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass string) error { +func (g *Git) UpdateGitUser(ctx context.Context, oldAdminPass string, username string, userpass string) error { message.Debugf("git.UpdateGitUser()") c, err := cluster.NewCluster() @@ -131,7 +132,7 @@ func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass strin if err != nil { return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -157,7 +158,7 @@ func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass strin } // CreatePackageRegistryToken uses the Gitea API to create a package registry token. -func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { +func (g *Git) CreatePackageRegistryToken(ctx context.Context) (CreateTokenResponse, error) { message.Debugf("git.CreatePackageRegistryToken()") c, err := cluster.NewCluster() @@ -170,7 +171,7 @@ func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { if err != nil { return CreateTokenResponse{}, err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return CreateTokenResponse{}, err } @@ -245,7 +246,7 @@ func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { } // UpdateGiteaPVC updates the existing Gitea persistent volume claim and tells Gitea whether to create or not. -func UpdateGiteaPVC(shouldRollBack bool) (string, error) { +func UpdateGiteaPVC(ctx context.Context, shouldRollBack bool) (string, error) { c, err := cluster.NewCluster() if err != nil { return "false", err @@ -260,12 +261,12 @@ func UpdateGiteaPVC(shouldRollBack bool) (string, error) { annotations := map[string]string{"meta.helm.sh/release-name": "zarf-gitea", "meta.helm.sh/release-namespace": "zarf"} if shouldRollBack { - err = c.K8s.RemoveLabelsAndAnnotations(cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) + err = c.K8s.RemoveLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) return "false", err } if pvcName == "data-zarf-gitea-0" { - err = c.K8s.AddLabelsAndAnnotations(cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) + err = c.K8s.AddLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) return "true", err } diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 7cee486d99..8b99cd14f0 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -6,6 +6,7 @@ package helm import ( "bytes" + "context" "fmt" "os" "path/filepath" @@ -80,13 +81,14 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { finalManifestsOutput := bytes.NewBuffer(nil) - // Otherwise, loop over the resources, if r.cluster != nil { - if err := r.editHelmResources(resources, finalManifestsOutput); err != nil { + ctx := context.Background() + + if err := r.editHelmResources(ctx, resources, finalManifestsOutput); err != nil { return nil, err } - if err := r.adoptAndUpdateNamespaces(); err != nil { + if err := r.adoptAndUpdateNamespaces(ctx); err != nil { return nil, err } } else { @@ -99,9 +101,9 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { return finalManifestsOutput, nil } -func (r *renderer) adoptAndUpdateNamespaces() error { +func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { c := r.cluster - existingNamespaces, _ := c.GetNamespaces() + existingNamespaces, _ := c.GetNamespaces(ctx) for name, namespace := range r.namespaces { // Check to see if this namespace already exists @@ -114,7 +116,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { if !existingNamespace { // This is a new namespace, add it - if _, err := c.CreateNamespace(namespace); err != nil { + if _, err := c.CreateNamespace(ctx, namespace); err != nil { return fmt.Errorf("unable to create the missing namespace %s", name) } } else if r.cfg.DeployOpts.AdoptExistingResources { @@ -123,7 +125,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { message.Warnf("Refusing to adopt the initial namespace: %s", name) } else { // This is an existing namespace to adopt - if _, err := c.UpdateNamespace(namespace); err != nil { + if _, err := c.UpdateNamespace(ctx, namespace); err != nil { return fmt.Errorf("unable to adopt the existing namespace %s", name) } } @@ -138,10 +140,10 @@ func (r *renderer) adoptAndUpdateNamespaces() error { validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) // Try to get a valid existing secret - currentRegistrySecret, _ := c.GetSecret(name, config.ZarfImagePullSecretName) + currentRegistrySecret, _ := c.GetSecret(ctx, name, config.ZarfImagePullSecretName) if currentRegistrySecret.Name != config.ZarfImagePullSecretName || !reflect.DeepEqual(currentRegistrySecret.Data, validRegistrySecret.Data) { // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(validRegistrySecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, validRegistrySecret); err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", name) } @@ -149,7 +151,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { gitServerSecret := c.GenerateGitPullCreds(name, config.ZarfGitServerSecretName, r.state.GitServer) // Create or update the zarf git server secret - if _, err := c.CreateOrUpdateSecret(gitServerSecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, gitServerSecret); err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", name) } } @@ -157,7 +159,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { return nil } -func (r *renderer) editHelmResources(resources []releaseutil.Manifest, finalManifestsOutput *bytes.Buffer) error { +func (r *renderer) editHelmResources(ctx context.Context, resources []releaseutil.Manifest, finalManifestsOutput *bytes.Buffer) error { for _, resource := range resources { // parse to unstructured to have access to more data than just the name rawData := &unstructured.Unstructured{} @@ -223,7 +225,7 @@ func (r *renderer) editHelmResources(resources []releaseutil.Manifest, finalMani "meta.helm.sh/release-namespace": r.chart.Namespace, } - if err := r.cluster.AddLabelsAndAnnotations(deployedNamespace, rawData.GetName(), rawData.GroupVersionKind().GroupKind(), helmLabels, helmAnnotations); err != nil { + if err := r.cluster.AddLabelsAndAnnotations(ctx, deployedNamespace, rawData.GetName(), rawData.GroupVersionKind().GroupKind(), helmLabels, helmAnnotations); err != nil { // Print a debug message since this could just be because the resource doesn't exist message.Debugf("Unable to adopt resource %s: %s", rawData.GetName(), err.Error()) } diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index c6db1299eb..94996b4173 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -5,6 +5,7 @@ package helm import ( + "context" "fmt" "github.com/defenseunicorns/zarf/src/internal/packager/template" @@ -50,7 +51,7 @@ func (h *Helm) UpdateZarfRegistryValues() error { } // UpdateZarfAgentValues updates the Zarf agent deployment with the new state values -func (h *Helm) UpdateZarfAgentValues() error { +func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { spinner := message.NewProgressSpinner("Gathering information to update Zarf Agent TLS") defer spinner.Stop() @@ -60,10 +61,14 @@ func (h *Helm) UpdateZarfAgentValues() error { } // Get the current agent image from one of its pods. - pods := h.cluster.WaitForPodsAndContainers(k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }, nil) + pods := h.cluster.WaitForPodsAndContainers( + ctx, + k8s.PodLookup{ + Namespace: cluster.ZarfNamespaceName, + Selector: "app=agent-hook", + }, + nil, + ) var currentAgentImage transform.Image if len(pods) > 0 && len(pods[0].Spec.Containers) > 0 { @@ -119,10 +124,13 @@ func (h *Helm) UpdateZarfAgentValues() error { defer spinner.Stop() // Force pods to be recreated to get the updated secret. - err = h.cluster.DeletePods(k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }) + err = h.cluster.DeletePods( + ctx, + k8s.PodLookup{ + Namespace: cluster.ZarfNamespaceName, + Selector: "app=agent-hook", + }, + ) if err != nil { return fmt.Errorf("error recycling pods for the Zarf Agent: %w", err) } diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index 64e592a5ab..e9a3645335 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -5,6 +5,7 @@ package images import ( + "context" "fmt" "time" @@ -20,7 +21,7 @@ import ( ) // Push pushes images to a registry. -func Push(cfg PushConfig) error { +func Push(ctx context.Context, cfg PushConfig) error { logs.Warn.SetOutput(&message.DebugWriter{}) logs.Progress.SetOutput(&message.DebugWriter{}) @@ -57,7 +58,7 @@ func Push(cfg PushConfig) error { if err := helpers.Retry(func() error { c, _ := cluster.NewCluster() if c != nil { - registryURL, tunnel, err = c.ConnectToZarfRegistryEndpoint(cfg.RegInfo) + registryURL, tunnel, err = c.ConnectToZarfRegistryEndpoint(ctx, cfg.RegInfo) if err != nil { return err } diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go index a84a06fee3..8b2153999b 100644 --- a/src/pkg/cluster/common.go +++ b/src/pkg/cluster/common.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "time" "github.com/defenseunicorns/zarf/src/config" @@ -27,19 +28,9 @@ var labels = k8s.Labels{ config.ZarfManagedByLabel: "zarf", } -// NewClusterOrDie creates a new Cluster instance and waits up to 30 seconds for the cluster to be ready or throws a fatal error. -func NewClusterOrDie() *Cluster { - c, err := NewClusterWithWait(DefaultTimeout) - if err != nil { - message.Fatalf(err, "Failed to connect to cluster") - } - - return c -} - // NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. -func NewClusterWithWait(timeout time.Duration) (*Cluster, error) { - spinner := message.NewProgressSpinner("Waiting for cluster connection (%s timeout)", timeout.String()) +func NewClusterWithWait(ctx context.Context) (*Cluster, error) { + spinner := message.NewProgressSpinner("Waiting for cluster connection") defer spinner.Stop() c := &Cluster{} @@ -50,7 +41,7 @@ func NewClusterWithWait(timeout time.Duration) (*Cluster, error) { return nil, err } - err = c.WaitForHealthyCluster(timeout) + err = c.WaitForHealthyCluster(ctx) if err != nil { return nil, err } diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index d2724c0d1f..0c5e526536 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "os" "path/filepath" @@ -25,7 +26,7 @@ import ( // HandleDataInjection waits for the target pod(s) to come up and inject the data into them // todo: this currently requires kubectl but we should have enough k8s work to make this native now. -func (c *Cluster) HandleDataInjection(wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { +func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { defer wg.Done() injectionCompletionMarker := filepath.Join(componentPath.DataInjections, config.GetDataInjectionMarker()) @@ -74,7 +75,7 @@ iterator: } // Wait until the pod we are injecting data into becomes available - pods := c.WaitForPodsAndContainers(target, podFilterByInitContainer) + pods := c.WaitForPodsAndContainers(ctx, target, podFilterByInitContainer) if len(pods) < 1 { continue } @@ -139,7 +140,7 @@ iterator: // Block one final time to make sure at least one pod has come up and injected the data // Using only the pod as the final selector because we don't know what the container name will be // Still using the init container filter to make sure we have the right running pod - _ = c.WaitForPodsAndContainers(podOnlyTarget, podFilterByInitContainer) + _ = c.WaitForPodsAndContainers(ctx, podOnlyTarget, podFilterByInitContainer) // Cleanup now to reduce disk pressure _ = os.RemoveAll(source) diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 716a260b96..dd2a918f98 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "net/http" "os" @@ -41,7 +42,7 @@ var ( type imageNodeMap map[string][]string // StartInjectionMadness initializes a Zarf injection into the cluster. -func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injectorSeedSrcs []string) { +func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imagesDir string, injectorSeedSrcs []string) { spinner := message.NewProgressSpinner("Attempting to bootstrap the seed image into the cluster") defer spinner.Stop() @@ -64,19 +65,20 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto var seedImages []transform.Image // Get all the images from the cluster - timeout := 5 * time.Minute - spinner.Updatef("Getting the list of existing cluster images (%s timeout)", timeout.String()) - if images, err = c.getImagesAndNodesForInjection(timeout); err != nil { + spinner.Updatef("Getting the list of existing cluster images") + findImagesCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + if images, err = c.getImagesAndNodesForInjection(findImagesCtx); err != nil { spinner.Fatalf(err, "Unable to generate a list of candidate images to perform the registry injection") } spinner.Updatef("Creating the injector configmap") - if err = c.createInjectorConfigmap(tmp.InjectionBinary); err != nil { + if err = c.createInjectorConfigmap(ctx, tmp.InjectionBinary); err != nil { spinner.Fatalf(err, "Unable to create the injector configmap") } spinner.Updatef("Creating the injector service") - if service, err := c.createService(); err != nil { + if service, err := c.createService(ctx); err != nil { spinner.Fatalf(err, "Unable to create the injector service") } else { config.ZarfSeedPort = fmt.Sprintf("%d", service.Spec.Ports[0].NodePort) @@ -88,7 +90,7 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } spinner.Updatef("Loading the seed registry configmaps") - if payloadConfigmaps, sha256sum, err = c.createPayloadConfigmaps(tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { + if payloadConfigmaps, sha256sum, err = c.createPayloadConfigmaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { spinner.Fatalf(err, "Unable to generate the injector payload configmaps") } @@ -105,7 +107,7 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) // Make sure the pod is not there first - _ = c.DeletePod(ZarfNamespaceName, "injector") + _ = c.DeletePod(ctx, ZarfNamespaceName, "injector") // Update the podspec image path and use the first node found pod, err := c.buildInjectionPod(node[0], image, payloadConfigmaps, sha256sum) @@ -116,7 +118,7 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } // Create the pod in the cluster - pod, err = c.CreatePod(pod) + pod, err = c.CreatePod(ctx, pod) if err != nil { // Just debug log the output because failures just result in trying the next image message.Debug(pod, err) @@ -124,7 +126,7 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } // if no error, try and wait for a seed image to be present, return if successful - if c.injectorIsReady(seedImages, spinner) { + if c.injectorIsReady(ctx, seedImages, spinner) { spinner.Success() return } @@ -137,20 +139,20 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } // StopInjectionMadness handles cleanup once the seed registry is up. -func (c *Cluster) StopInjectionMadness() error { +func (c *Cluster) StopInjectionMadness(ctx context.Context) error { // Try to kill the injector pod now - if err := c.DeletePod(ZarfNamespaceName, "injector"); err != nil { + if err := c.DeletePod(ctx, ZarfNamespaceName, "injector"); err != nil { return err } // Remove the configmaps labelMatch := map[string]string{"zarf-injector": "payload"} - if err := c.DeleteConfigMapsByLabel(ZarfNamespaceName, labelMatch); err != nil { + if err := c.DeleteConfigMapsByLabel(ctx, ZarfNamespaceName, labelMatch); err != nil { return err } // Remove the injector service - return c.DeleteService(ZarfNamespaceName, "zarf-injector") + return c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") } func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string, spinner *message.Spinner) ([]transform.Image, error) { @@ -162,34 +164,36 @@ func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSr spinner.Updatef("Loading the seed image '%s' from the package", src) ref, err := transform.ParseImageRef(src) if err != nil { - return seedImages, fmt.Errorf("failed to create ref for image %s: %w", src, err) + return nil, fmt.Errorf("failed to create ref for image %s: %w", src, err) } img, err := utils.LoadOCIImage(imagesDir, ref) if err != nil { - return seedImages, err + return nil, err } - crane.SaveOCI(img, seedImagesDir) + if err := crane.SaveOCI(img, seedImagesDir); err != nil { + return nil, err + } seedImages = append(seedImages, ref) // Get the image digest so we can set an annotation in the image.json later imgDigest, err := img.Digest() if err != nil { - return seedImages, err + return nil, err } // This is done _without_ the domain (different from pull.go) since the injector only handles local images localReferenceToDigest[ref.Path+ref.TagOrDigest] = imgDigest.String() } if err := utils.AddImageNameAnnotation(seedImagesDir, localReferenceToDigest); err != nil { - return seedImages, fmt.Errorf("unable to format OCI layout: %w", err) + return nil, fmt.Errorf("unable to format OCI layout: %w", err) } return seedImages, nil } -func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { +func (c *Cluster) createPayloadConfigmaps(ctx context.Context, seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { var configMaps []string // Chunk size has to accommodate base64 encoding & etcd 1MB limit @@ -226,7 +230,7 @@ func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner spinner.Updatef("Adding archive binary configmap %d of %d to the cluster", idx+1, chunkCount) // Attempt to create the configmap in the cluster - if _, err = c.ReplaceConfigmap(ZarfNamespaceName, fileName, configData); err != nil { + if _, err = c.ReplaceConfigmap(ctx, ZarfNamespaceName, fileName, configData); err != nil { return configMaps, "", err } @@ -241,13 +245,13 @@ func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner } // Test for pod readiness and seed image presence. -func (c *Cluster) injectorIsReady(seedImages []transform.Image, spinner *message.Spinner) bool { +func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Image, spinner *message.Spinner) bool { tunnel, err := c.NewTunnel(ZarfNamespaceName, k8s.SvcResource, ZarfInjectorName, "", 0, ZarfInjectorPort) if err != nil { return false } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return false } @@ -276,7 +280,7 @@ func (c *Cluster) injectorIsReady(seedImages []transform.Image, spinner *message return true } -func (c *Cluster) createInjectorConfigmap(binaryPath string) error { +func (c *Cluster) createInjectorConfigmap(ctx context.Context, binaryPath string) error { var err error configData := make(map[string][]byte) @@ -286,17 +290,17 @@ func (c *Cluster) createInjectorConfigmap(binaryPath string) error { } // Try to delete configmap silently - _ = c.DeleteConfigmap(ZarfNamespaceName, "rust-binary") + _ = c.DeleteConfigmap(ctx, ZarfNamespaceName, "rust-binary") // Attempt to create the configmap in the cluster - if _, err = c.CreateConfigmap(ZarfNamespaceName, "rust-binary", configData); err != nil { + if _, err = c.CreateConfigmap(ctx, ZarfNamespaceName, "rust-binary", configData); err != nil { return err } return nil } -func (c *Cluster) createService() (*corev1.Service, error) { +func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { service := c.GenerateService(ZarfNamespaceName, "zarf-injector") service.Spec.Type = corev1.ServiceTypeNodePort @@ -308,9 +312,9 @@ func (c *Cluster) createService() (*corev1.Service, error) { } // Attempt to purse the service silently - _ = c.DeleteService(ZarfNamespaceName, "zarf-injector") + _ = c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") - return c.CreateService(service) + return c.CreateService(ctx, service) } // buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. @@ -432,66 +436,61 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri return pod, nil } -// GetImagesFromAvailableNodes checks for images on schedulable nodes within a cluster and returns -func (c *Cluster) getImagesAndNodesForInjection(timeoutDuration time.Duration) (imageNodeMap, error) { - timeout := time.After(timeoutDuration) +// getImagesAndNodesForInjection checks for images on schedulable nodes within a cluster. +func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeMap, error) { result := make(imageNodeMap) + timer := time.NewTimer(0) + defer timer.Stop() + for { select { - - // On timeout abort - case <-timeout: - return nil, fmt.Errorf("get image list timed-out") - - // After delay, try running - default: - pods, err := c.GetPods(corev1.NamespaceAll, metav1.ListOptions{ + case <-ctx.Done(): + return nil, fmt.Errorf("get image list timed-out: %w", ctx.Err()) + case <-timer.C: + pods, err := c.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{ FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), }) if err != nil { return nil, fmt.Errorf("unable to get the list of %q pods in the cluster: %w", corev1.PodRunning, err) } - findImages: for _, pod := range pods.Items { nodeName := pod.Spec.NodeName - nodeDetails, err := c.GetNode(nodeName) + nodeDetails, err := c.GetNode(ctx, nodeName) if err != nil { return nil, fmt.Errorf("unable to get the node %q: %w", nodeName, err) } if nodeDetails.Status.Allocatable.Cpu().Cmp(injectorRequestedCPU) < 0 || nodeDetails.Status.Allocatable.Memory().Cmp(injectorRequestedMemory) < 0 { - continue findImages + continue } for _, taint := range nodeDetails.Spec.Taints { if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { - continue findImages + continue } } for _, container := range pod.Spec.InitContainers { result[container.Image] = append(result[container.Image], nodeName) } - for _, container := range pod.Spec.Containers { result[container.Image] = append(result[container.Image], nodeName) } - for _, container := range pod.Spec.EphemeralContainers { result[container.Image] = append(result[container.Image], nodeName) } } - } - if len(result) < 1 { - c.Log("no images found: %w") - time.Sleep(2 * time.Second) - } else { - return result, nil + if len(result) > 0 { + return result, nil + } + + c.Log("No images found on any node. Retrying...") + timer.Reset(2 * time.Second) } } } diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 82b4277901..a7209936b3 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -11,9 +11,9 @@ import ( ) // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster. -func (c *Cluster) DeleteZarfNamespace() { +func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { spinner := message.NewProgressSpinner("Deleting the zarf namespace from this cluster") defer spinner.Stop() - c.DeleteNamespace(context.TODO(), ZarfNamespaceName) + return c.DeleteNamespace(ctx, ZarfNamespaceName) } diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 2cdb1a20d1..17183d5e62 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "encoding/base64" "encoding/json" "reflect" @@ -73,16 +74,16 @@ func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo typ } // UpdateZarfManagedImageSecrets updates all Zarf-managed image secrets in all namespaces based on state -func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all image pull secrets for _, namespace := range namespaces.Items { - currentRegistrySecret, err := c.GetSecret(namespace.Name, config.ZarfImagePullSecretName) + currentRegistrySecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfImagePullSecretName) if err != nil { continue } @@ -96,7 +97,7 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) if !reflect.DeepEqual(currentRegistrySecret.Data, newRegistrySecret.Data) { // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(newRegistrySecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, newRegistrySecret); err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", namespace.Name) } } @@ -107,16 +108,16 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { } // UpdateZarfManagedGitSecrets updates all Zarf-managed git secrets in all namespaces based on state -func (c *Cluster) UpdateZarfManagedGitSecrets(state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all git pull secrets for _, namespace := range namespaces.Items { - currentGitSecret, err := c.GetSecret(namespace.Name, config.ZarfGitServerSecretName) + currentGitSecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfGitServerSecretName) if err != nil { continue } @@ -130,7 +131,7 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(state *types.ZarfState) { newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) if !reflect.DeepEqual(currentGitSecret.StringData, newGitSecret.StringData) { // Create or update the zarf git secret - if _, err := c.CreateOrUpdateSecret(newGitSecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, newGitSecret); err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", namespace.Name) } } diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index ebac18fbd3..a2585c4361 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "encoding/json" "fmt" "time" @@ -34,7 +35,7 @@ const ( ) // InitZarfState initializes the Zarf state with the given temporary directory and init configs. -func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { +func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitOptions) error { var ( distro string err error @@ -46,7 +47,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Attempt to load an existing state prior to init. // NOTE: We are ignoring the error here because we don't really expect a state to exist yet. spinner.Updatef("Checking cluster for existing Zarf deployment") - state, _ := c.LoadZarfState() + state, _ := c.LoadZarfState(ctx) // If state is nil, this is a new cluster. if state == nil { @@ -59,7 +60,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { state.ZarfAppliance = true } else { // Otherwise, trying to detect the K8s distro type. - distro, err = c.DetectDistro() + distro, err = c.DetectDistro(ctx) if err != nil { // This is a basic failure right now but likely could be polished to provide user guidance to resolve. return fmt.Errorf("unable to connect to the cluster to verify the distro: %w", err) @@ -79,7 +80,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Setup zarf agent PKI state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) - namespaces, err := c.GetNamespaces() + namespaces, err := c.GetNamespaces(ctx) if err != nil { return fmt.Errorf("unable to get the Kubernetes namespaces: %w", err) } @@ -93,7 +94,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // This label will tell the Zarf Agent to ignore this namespace. namespace.Labels[agentLabel] = "ignore" namespaceCopy := namespace - if _, err = c.UpdateNamespace(&namespaceCopy); err != nil { + if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it. message.WarnErrf(err, "Unable to mark the namespace %s as ignored by Zarf Agent", namespace.Name) } @@ -102,14 +103,16 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") zarfNamespace := c.NewZarfManagedNamespace(ZarfNamespaceName) - if _, err := c.CreateNamespace(zarfNamespace); err != nil { + if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { return fmt.Errorf("unable to create the zarf namespace: %w", err) } // Wait up to 2 minutes for the default service account to be created. // Some clusters seem to take a while to create this, see https://github.com/kubernetes/kubernetes/issues/66689. // The default SA is required for pods to start properly. - if _, err := c.WaitForServiceAccount(ZarfNamespaceName, "default", 2*time.Minute); err != nil { + saCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + if _, err := c.WaitForServiceAccount(saCtx, ZarfNamespaceName, "default"); err != nil { return fmt.Errorf("unable get default Zarf service account: %w", err) } @@ -158,7 +161,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { spinner.Success() // Save the state back to K8s - if err := c.SaveZarfState(state); err != nil { + if err := c.SaveZarfState(ctx, state); err != nil { return fmt.Errorf("unable to save the Zarf state: %w", err) } @@ -166,9 +169,9 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { } // LoadZarfState returns the current zarf/zarf-state secret data or an empty ZarfState. -func (c *Cluster) LoadZarfState() (state *types.ZarfState, err error) { +func (c *Cluster) LoadZarfState(ctx context.Context) (state *types.ZarfState, err error) { // Set up the API connection - secret, err := c.GetSecret(ZarfNamespaceName, ZarfStateSecretName) + secret, err := c.GetSecret(ctx, ZarfNamespaceName, ZarfStateSecretName) if err != nil { return nil, fmt.Errorf("%w. %s", err, message.ColorWrap("Did you remember to zarf init?", color.Bold)) } @@ -218,7 +221,7 @@ func (c *Cluster) debugPrintZarfState(state *types.ZarfState) { } // SaveZarfState takes a given state and persists it to the Zarf/zarf-state secret. -func (c *Cluster) SaveZarfState(state *types.ZarfState) error { +func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) error { c.debugPrintZarfState(state) // Convert the data back to JSON. @@ -249,7 +252,7 @@ func (c *Cluster) SaveZarfState(state *types.ZarfState) error { } // Attempt to create or update the secret and return. - if _, err := c.CreateOrUpdateSecret(secret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, secret); err != nil { return fmt.Errorf("unable to create the zarf state secret") } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index d52c606ecc..c960fb1e8e 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "strings" @@ -54,8 +55,8 @@ func NewTunnelInfo(namespace, resourceType, resourceName, urlSuffix string, loca } // PrintConnectTable will print a table of all Zarf connect matches found in the cluster. -func (c *Cluster) PrintConnectTable() error { - list, err := c.GetServicesByLabelExists(v1.NamespaceAll, config.ZarfConnectLabelName) +func (c *Cluster) PrintConnectTable(ctx context.Context) error { + list, err := c.GetServicesByLabelExists(ctx, v1.NamespaceAll, config.ZarfConnectLabelName) if err != nil { return err } @@ -78,7 +79,7 @@ func (c *Cluster) PrintConnectTable() error { } // Connect will establish a tunnel to the specified target. -func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { +func (c *Cluster) Connect(ctx context.Context, target string) (*k8s.Tunnel, error) { var err error zt := TunnelInfo{ namespace: ZarfNamespaceName, @@ -107,7 +108,7 @@ func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { default: if target != "" { - if zt, err = c.checkForZarfConnectLabel(target); err != nil { + if zt, err = c.checkForZarfConnectLabel(ctx, target); err != nil { return nil, fmt.Errorf("problem looking for a zarf connect label in the cluster: %s", err.Error()) } } @@ -120,17 +121,17 @@ func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { } } - return c.ConnectTunnelInfo(zt) + return c.ConnectTunnelInfo(ctx, zt) } // ConnectTunnelInfo connects to the cluster with the provided TunnelInfo -func (c *Cluster) ConnectTunnelInfo(zt TunnelInfo) (*k8s.Tunnel, error) { +func (c *Cluster) ConnectTunnelInfo(ctx context.Context, zt TunnelInfo) (*k8s.Tunnel, error) { tunnel, err := c.NewTunnel(zt.namespace, zt.resourceType, zt.resourceName, zt.urlSuffix, zt.localPort, zt.remotePort) if err != nil { return nil, err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return nil, err } @@ -139,7 +140,7 @@ func (c *Cluster) ConnectTunnelInfo(zt TunnelInfo) (*k8s.Tunnel, error) { } // ConnectToZarfRegistryEndpoint determines if a registry endpoint is in cluster, and if so opens a tunnel to connect to it -func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) { +func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) { registryEndpoint := registryInfo.Address var err error @@ -150,7 +151,7 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) return "", tunnel, err } } else { - svcInfo, err := c.ServiceInfoFromNodePortURL(registryInfo.Address) + svcInfo, err := c.ServiceInfoFromNodePortURL(ctx, registryInfo.Address) // If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource if err == nil { @@ -161,7 +162,7 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) } if tunnel != nil { - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return "", tunnel, err } @@ -172,13 +173,13 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) } // checkForZarfConnectLabel looks in the cluster for a connect name that matches the target -func (c *Cluster) checkForZarfConnectLabel(name string) (TunnelInfo, error) { +func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (TunnelInfo, error) { var err error var zt TunnelInfo message.Debugf("Looking for a Zarf Connect Label in the cluster") - matches, err := c.GetServicesByLabel("", config.ZarfConnectLabelName, name) + matches, err := c.GetServicesByLabel(ctx, "", config.ZarfConnectLabelName, name) if err != nil { return zt, fmt.Errorf("unable to lookup the service: %w", err) } @@ -195,7 +196,7 @@ func (c *Cluster) checkForZarfConnectLabel(name string) (TunnelInfo, error) { zt.remotePort = svc.Spec.Ports[0].TargetPort.IntValue() // if targetPort == 0, look for Port (which is required) if zt.remotePort == 0 { - zt.remotePort = c.FindPodContainerPort(svc) + zt.remotePort = c.FindPodContainerPort(ctx, svc) } // Add the url suffix too. diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index afef2362b3..3522dde6b5 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -7,7 +7,6 @@ package cluster import ( "context" "encoding/json" - "errors" "fmt" "strings" "time" @@ -23,11 +22,11 @@ import ( // GetDeployedZarfPackages gets metadata information about packages that have been deployed to the cluster. // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. // Returns a list of DeployedPackage structs and a list of errors. -func (c *Cluster) GetDeployedZarfPackages() ([]types.DeployedPackage, []error) { +func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.DeployedPackage, []error) { var deployedPackages = []types.DeployedPackage{} var errorList []error // Get the secrets that describe the deployed packages - secrets, err := c.GetSecretsWithLabel(ZarfNamespaceName, ZarfPackageInfoLabel) + secrets, err := c.GetSecretsWithLabel(ctx, ZarfNamespaceName, ZarfPackageInfoLabel) if err != nil { return deployedPackages, append(errorList, err) } @@ -52,9 +51,9 @@ func (c *Cluster) GetDeployedZarfPackages() ([]types.DeployedPackage, []error) { // GetDeployedPackage gets the metadata information about the package name provided (if it exists in the cluster). // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. -func (c *Cluster) GetDeployedPackage(packageName string) (deployedPackage *types.DeployedPackage, err error) { +func (c *Cluster) GetDeployedPackage(ctx context.Context, packageName string) (deployedPackage *types.DeployedPackage, err error) { // Get the secret that describes the deployed package - secret, err := c.GetSecret(ZarfNamespaceName, config.ZarfPackagePrefix+packageName) + secret, err := c.GetSecret(ctx, ZarfNamespaceName, config.ZarfPackagePrefix+packageName) if err != nil { return deployedPackage, err } @@ -63,7 +62,7 @@ func (c *Cluster) GetDeployedPackage(packageName string) (deployedPackage *types } // StripZarfLabelsAndSecretsFromNamespaces removes metadata and secrets from existing namespaces no longer manged by Zarf. -func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { +func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { spinner := message.NewProgressSpinner("Removing zarf metadata & secrets from existing namespaces not managed by Zarf") defer spinner.Stop() @@ -72,7 +71,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { LabelSelector: config.ZarfManagedByLabel + "=zarf", } - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { for _, namespace := range namespaces.Items { @@ -80,7 +79,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) delete(namespace.Labels, agentLabel) namespaceCopy := namespace - if _, err = c.UpdateNamespace(&namespaceCopy); err != nil { + if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it spinner.Errorf(err, "Unable to update the namespace labels for %s", namespace.Name) } @@ -89,7 +88,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { spinner.Updatef("Removing Zarf secrets for namespace %s", namespace.Name) err := c.Clientset.CoreV1(). Secrets(namespace.Name). - DeleteCollection(context.TODO(), deleteOptions, listOptions) + DeleteCollection(ctx, deleteOptions, listOptions) if err != nil { spinner.Errorf(err, "Unable to delete secrets from namespace %s", namespace.Name) } @@ -125,9 +124,8 @@ func (c *Cluster) PackageSecretNeedsWait(deployedPackage *types.DeployedPackage, } // RecordPackageDeploymentAndWait records the deployment of a package to the cluster and waits for any webhooks to complete. -func (c *Cluster) RecordPackageDeploymentAndWait(pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int, component types.ZarfComponent, skipWebhooks bool) (deployedPackage *types.DeployedPackage, err error) { - - deployedPackage, err = c.RecordPackageDeployment(pkg, components, connectStrings, generation) +func (c *Cluster) RecordPackageDeploymentAndWait(ctx context.Context, pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int, component types.ZarfComponent, skipWebhooks bool) (deployedPackage *types.DeployedPackage, err error) { + deployedPackage, err = c.RecordPackageDeployment(ctx, pkg, components, connectStrings, generation) if err != nil { return nil, err } @@ -135,41 +133,46 @@ func (c *Cluster) RecordPackageDeploymentAndWait(pkg types.ZarfPackage, componen packageNeedsWait, waitSeconds, hookName := c.PackageSecretNeedsWait(deployedPackage, component, skipWebhooks) // If no webhooks need to complete, we can return immediately. if !packageNeedsWait { - return nil, nil + return deployedPackage, nil } - // Timebox the amount of time we wait for a webhook to complete before erroring waitDuration := types.DefaultWebhookWaitDuration if waitSeconds > 0 { waitDuration = time.Duration(waitSeconds) * time.Second } - timeout := time.After(waitDuration) - // We need to wait for this package to finish having webhooks run, create a spinner and keep checking until it's ready - spinner := message.NewProgressSpinner("Waiting for webhook '%s' to complete for component '%s'", hookName, component.Name) + waitCtx, cancel := context.WithTimeout(ctx, waitDuration) + defer cancel() + + spinner := message.NewProgressSpinner("Waiting for webhook %q to complete for component %q", hookName, component.Name) defer spinner.Stop() - for packageNeedsWait { + + timer := time.NewTimer(0) + defer timer.Stop() + + for { select { - // On timeout, abort and return an error. - case <-timeout: - return nil, errors.New("timed out waiting for package deployment to complete") - default: - // Wait for 1 second before checking the secret again - time.Sleep(1 * time.Second) - deployedPackage, err = c.GetDeployedPackage(deployedPackage.Name) + case <-waitCtx.Done(): + return nil, fmt.Errorf("error waiting for webhook %q to complete for component %q: %w", hookName, component.Name, waitCtx.Err()) + case <-timer.C: + deployedPackage, err = c.GetDeployedPackage(ctx, deployedPackage.Name) if err != nil { return nil, err } + packageNeedsWait, _, _ = c.PackageSecretNeedsWait(deployedPackage, component, skipWebhooks) + if !packageNeedsWait { + spinner.Success() + return deployedPackage, nil + } + + timer.Reset(1 * time.Second) } } - - spinner.Success() - return deployedPackage, nil } // RecordPackageDeployment saves metadata about a package that has been deployed to the cluster. -func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int) (deployedPackage *types.DeployedPackage, err error) { +func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int) (deployedPackage *types.DeployedPackage, err error) { packageName := pkg.Metadata.Name // Generate a secret that describes the package that is being deployed @@ -179,7 +182,7 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty // Attempt to load information about webhooks for the package var componentWebhooks map[string]map[string]types.Webhook - existingPackageSecret, err := c.GetDeployedPackage(packageName) + existingPackageSecret, err := c.GetDeployedPackage(ctx, packageName) if err != nil { message.Debugf("Unable to fetch existing secret for package '%s': %s", packageName, err.Error()) } @@ -205,7 +208,7 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty // Update the package secret deployedPackageSecret.Data = map[string][]byte{"data": packageData} var updatedSecret *corev1.Secret - if updatedSecret, err = c.CreateOrUpdateSecret(deployedPackageSecret); err != nil { + if updatedSecret, err = c.CreateOrUpdateSecret(ctx, deployedPackageSecret); err != nil { return nil, fmt.Errorf("failed to record package deployment in secret '%s'", deployedPackageSecret.Name) } @@ -217,8 +220,8 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty } // EnableRegHPAScaleDown enables the HPA scale down for the Zarf Registry. -func (c *Cluster) EnableRegHPAScaleDown() error { - hpa, err := c.GetHPA(ZarfNamespaceName, "zarf-docker-registry") +func (c *Cluster) EnableRegHPAScaleDown(ctx context.Context) error { + hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") if err != nil { return err } @@ -228,7 +231,7 @@ func (c *Cluster) EnableRegHPAScaleDown() error { hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy // Save the HPA changes. - if _, err = c.UpdateHPA(hpa); err != nil { + if _, err = c.UpdateHPA(ctx, hpa); err != nil { return err } @@ -236,8 +239,8 @@ func (c *Cluster) EnableRegHPAScaleDown() error { } // DisableRegHPAScaleDown disables the HPA scale down for the Zarf Registry. -func (c *Cluster) DisableRegHPAScaleDown() error { - hpa, err := c.GetHPA(ZarfNamespaceName, "zarf-docker-registry") +func (c *Cluster) DisableRegHPAScaleDown(ctx context.Context) error { + hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") if err != nil { return err } @@ -247,7 +250,7 @@ func (c *Cluster) DisableRegHPAScaleDown() error { hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy // Save the HPA changes. - if _, err = c.UpdateHPA(hpa); err != nil { + if _, err = c.UpdateHPA(ctx, hpa); err != nil { return err } @@ -255,8 +258,8 @@ func (c *Cluster) DisableRegHPAScaleDown() error { } // GetInstalledChartsForComponent returns any installed Helm Charts for the provided package component. -func (c *Cluster) GetInstalledChartsForComponent(packageName string, component types.ZarfComponent) (installedCharts []types.InstalledChart, err error) { - deployedPackage, err := c.GetDeployedPackage(packageName) +func (c *Cluster) GetInstalledChartsForComponent(ctx context.Context, packageName string, component types.ZarfComponent) (installedCharts []types.InstalledChart, err error) { + deployedPackage, err := c.GetDeployedPackage(ctx, packageName) if err != nil { return installedCharts, err } diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index e3472d8962..44027f4492 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -5,6 +5,7 @@ package k8s import ( + "context" "fmt" "time" @@ -42,35 +43,29 @@ func New(logger Log, defaultLabels Labels) (*K8s, error) { }, nil } -// NewWithWait is a convenience function that creates a new K8s client and waits for the cluster to be healthy. -func NewWithWait(logger Log, defaultLabels Labels, timeout time.Duration) (*K8s, error) { - k, err := New(logger, defaultLabels) - if err != nil { - return nil, err - } +// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. +func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { + var ( + err error + nodes *v1.NodeList + pods *v1.PodList + ) - return k, k.WaitForHealthyCluster(timeout) -} + const waitDuration = 1 * time.Second -// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. -func (k *K8s) WaitForHealthyCluster(timeout time.Duration) error { - var err error - var nodes *v1.NodeList - var pods *v1.PodList - expired := time.After(timeout) + timer := time.NewTimer(0) + defer timer.Stop() for { select { - // on timeout abort - case <-expired: - return fmt.Errorf("timed out waiting for cluster to report healthy") - - // after delay, try running - default: + case <-ctx.Done(): + return fmt.Errorf("error waiting for cluster to report healthy: %w", ctx.Err()) + case <-timer.C: if k.RestConfig == nil || k.Clientset == nil { config, clientset, err := connect() if err != nil { k.Log("Cluster connection not available yet: %w", err) + timer.Reset(waitDuration) continue } @@ -79,31 +74,30 @@ func (k *K8s) WaitForHealthyCluster(timeout time.Duration) error { } // Make sure there is at least one running Node - nodes, err = k.GetNodes() + nodes, err = k.GetNodes(ctx) if err != nil || len(nodes.Items) < 1 { - k.Log("No nodes reporting healthy yet: %#v\n", err) + k.Log("No nodes reporting healthy yet: %v\n", err) + timer.Reset(waitDuration) continue } // Get the cluster pod list - if pods, err = k.GetAllPods(); err != nil { + if pods, err = k.GetAllPods(ctx); err != nil { k.Log("Could not get the pod list: %w", err) + timer.Reset(waitDuration) continue } // Check that at least one pod is in the 'succeeded' or 'running' state for _, pod := range pods.Items { - // If a valid pod is found, return no error if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodRunning { return nil } } k.Log("No pods reported 'succeeded' or 'running' state yet.") + timer.Reset(waitDuration) } - - // delay check 1 seconds - time.Sleep(1 * time.Second) } } diff --git a/src/pkg/k8s/configmap.go b/src/pkg/k8s/configmap.go index 93777f6b3f..57a72c65ae 100644 --- a/src/pkg/k8s/configmap.go +++ b/src/pkg/k8s/configmap.go @@ -14,16 +14,15 @@ import ( ) // ReplaceConfigmap deletes and recreates a configmap. -func (k *K8s) ReplaceConfigmap(namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { - if err := k.DeleteConfigmap(namespace, name); err != nil { +func (k *K8s) ReplaceConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { + if err := k.DeleteConfigmap(ctx, namespace, name); err != nil { return nil, err } - - return k.CreateConfigmap(namespace, name, data) + return k.CreateConfigmap(ctx, namespace, name, data) } // CreateConfigmap applies a configmap to the cluster. -func (k *K8s) CreateConfigmap(namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { +func (k *K8s) CreateConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -34,14 +33,14 @@ func (k *K8s) CreateConfigmap(namespace, name string, data map[string][]byte) (* } createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().ConfigMaps(namespace).Create(context.TODO(), configMap, createOptions) + return k.Clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, createOptions) } // DeleteConfigmap deletes a configmap by name. -func (k *K8s) DeleteConfigmap(namespace, name string) error { +func (k *K8s) DeleteConfigmap(ctx context.Context, namespace, name string) error { namespaceConfigmap := k.Clientset.CoreV1().ConfigMaps(namespace) - err := namespaceConfigmap.Delete(context.TODO(), name, metav1.DeleteOptions{}) + err := namespaceConfigmap.Delete(ctx, name, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("error deleting the configmap: %w", err) } @@ -50,7 +49,7 @@ func (k *K8s) DeleteConfigmap(namespace, name string) error { } // DeleteConfigMapsByLabel deletes a configmap by label(s). -func (k *K8s) DeleteConfigMapsByLabel(namespace string, labels Labels) error { +func (k *K8s) DeleteConfigMapsByLabel(ctx context.Context, namespace string, labels Labels) error { labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: labels, }) @@ -59,5 +58,5 @@ func (k *K8s) DeleteConfigMapsByLabel(namespace string, labels Labels) error { LabelSelector: labelSelector.String(), } - return k.Clientset.CoreV1().ConfigMaps(namespace).DeleteCollection(context.TODO(), metaOptions, listOptions) + return k.Clientset.CoreV1().ConfigMaps(namespace).DeleteCollection(ctx, metaOptions, listOptions) } diff --git a/src/pkg/k8s/dynamic.go b/src/pkg/k8s/dynamic.go index daf87c7a1a..59f295f26b 100644 --- a/src/pkg/k8s/dynamic.go +++ b/src/pkg/k8s/dynamic.go @@ -15,17 +15,17 @@ import ( ) // AddLabelsAndAnnotations adds the provided labels and annotations to the specified K8s resource -func (k *K8s) AddLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(resourceNamespace, resourceName, groupKind, labels, annotations, false) +func (k *K8s) AddLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { + return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, false) } // RemoveLabelsAndAnnotations removes the provided labels and annotations to the specified K8s resource -func (k *K8s) RemoveLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(resourceNamespace, resourceName, groupKind, labels, annotations, true) +func (k *K8s) RemoveLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { + return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, true) } // updateLabelsAndAnnotations updates the provided labels and annotations to the specified K8s resource -func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string, isRemove bool) error { +func (k *K8s) updateLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string, isRemove bool) error { dynamicClient := dynamic.NewForConfigOrDie(k.RestConfig) discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(k.RestConfig) @@ -41,7 +41,7 @@ func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName return err } - deployedResource, err := dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + deployedResource, err := dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Get(ctx, resourceName, metav1.GetOptions{}) if err != nil { return err } @@ -78,6 +78,6 @@ func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName deployedResource.SetAnnotations(deployedAnnotations) - _, err = dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Update(context.TODO(), deployedResource, metav1.UpdateOptions{}) + _, err = dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Update(ctx, deployedResource, metav1.UpdateOptions{}) return err } diff --git a/src/pkg/k8s/hpa.go b/src/pkg/k8s/hpa.go index a159823f67..902f5b1d29 100644 --- a/src/pkg/k8s/hpa.go +++ b/src/pkg/k8s/hpa.go @@ -13,24 +13,24 @@ import ( ) // GetAllHPAs returns a list of horizontal pod autoscalers for all namespaces. -func (k *K8s) GetAllHPAs() (*autoscalingV2.HorizontalPodAutoscalerList, error) { - return k.GetHPAs(corev1.NamespaceAll) +func (k *K8s) GetAllHPAs(ctx context.Context) (*autoscalingV2.HorizontalPodAutoscalerList, error) { + return k.GetHPAs(ctx, corev1.NamespaceAll) } // GetHPAs returns a list of horizontal pod autoscalers in a given namespace. -func (k *K8s) GetHPAs(namespace string) (*autoscalingV2.HorizontalPodAutoscalerList, error) { +func (k *K8s) GetHPAs(ctx context.Context, namespace string) (*autoscalingV2.HorizontalPodAutoscalerList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.TODO(), metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(ctx, metaOptions) } // GetHPA returns a single horizontal pod autoscaler by namespace and name. -func (k *K8s) GetHPA(namespace, name string) (*autoscalingV2.HorizontalPodAutoscaler, error) { +func (k *K8s) GetHPA(ctx context.Context, namespace, name string) (*autoscalingV2.HorizontalPodAutoscaler, error) { metaOptions := metav1.GetOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(context.TODO(), name, metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(ctx, name, metaOptions) } // UpdateHPA updates the given horizontal pod autoscaler in the cluster. -func (k *K8s) UpdateHPA(hpa *autoscalingV2.HorizontalPodAutoscaler) (*autoscalingV2.HorizontalPodAutoscaler, error) { +func (k *K8s) UpdateHPA(ctx context.Context, hpa *autoscalingV2.HorizontalPodAutoscaler) (*autoscalingV2.HorizontalPodAutoscaler, error) { metaOptions := metav1.UpdateOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(context.TODO(), hpa, metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(ctx, hpa, metaOptions) } diff --git a/src/pkg/k8s/info.go b/src/pkg/k8s/info.go index 61effabdaa..edb655b964 100644 --- a/src/pkg/k8s/info.go +++ b/src/pkg/k8s/info.go @@ -5,6 +5,7 @@ package k8s import ( + "context" "errors" "fmt" "regexp" @@ -29,7 +30,7 @@ const ( ) // DetectDistro returns the matching distro or unknown if not found. -func (k *K8s) DetectDistro() (string, error) { +func (k *K8s) DetectDistro(ctx context.Context) (string, error) { kindNodeRegex := regexp.MustCompile(`^kind://`) k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`) eksNodeRegex := regexp.MustCompile(`^aws:///`) @@ -38,7 +39,7 @@ func (k *K8s) DetectDistro() (string, error) { rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`) tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`) - nodes, err := k.GetNodes() + nodes, err := k.GetNodes(ctx) if err != nil { return DistroIsUnknown, errors.New("error getting cluster nodes") } @@ -99,7 +100,7 @@ func (k *K8s) DetectDistro() (string, error) { } } - namespaces, err := k.GetNamespaces() + namespaces, err := k.GetNamespaces(ctx) if err != nil { return DistroIsUnknown, errors.New("error getting namespace list") } @@ -115,8 +116,8 @@ func (k *K8s) DetectDistro() (string, error) { } // GetArchitectures returns the cluster system architectures if found. -func (k *K8s) GetArchitectures() ([]string, error) { - nodes, err := k.GetNodes() +func (k *K8s) GetArchitectures(ctx context.Context) ([]string, error) { + nodes, err := k.GetNodes(ctx) if err != nil { return nil, err } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go index 2862731e90..3a63b1ac52 100644 --- a/src/pkg/k8s/namespace.go +++ b/src/pkg/k8s/namespace.go @@ -15,26 +15,26 @@ import ( ) // GetNamespaces returns a list of namespaces in the cluster. -func (k *K8s) GetNamespaces() (*corev1.NamespaceList, error) { +func (k *K8s) GetNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Namespaces().List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().Namespaces().List(ctx, metaOptions) } // UpdateNamespace updates the given namespace in the cluster. -func (k *K8s) UpdateNamespace(namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (k *K8s) UpdateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { updateOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().Namespaces().Update(context.TODO(), namespace, updateOptions) + return k.Clientset.CoreV1().Namespaces().Update(ctx, namespace, updateOptions) } // CreateNamespace creates the given namespace or returns it if it already exists in the cluster. -func (k *K8s) CreateNamespace(namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (k *K8s) CreateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { metaOptions := metav1.GetOptions{} createOptions := metav1.CreateOptions{} - match, err := k.Clientset.CoreV1().Namespaces().Get(context.TODO(), namespace.Name, metaOptions) + match, err := k.Clientset.CoreV1().Namespaces().Get(ctx, namespace.Name, metaOptions) if err != nil || match.Name != namespace.Name { - return k.Clientset.CoreV1().Namespaces().Create(context.TODO(), namespace, createOptions) + return k.Clientset.CoreV1().Namespaces().Create(ctx, namespace, createOptions) } return match, err @@ -45,19 +45,25 @@ func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { // Attempt to delete the namespace immediately gracePeriod := int64(0) err := k.Clientset.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) - // If an error besides "not found" is returned, return it if err != nil && !errors.IsNotFound(err) { return err } - // Indefinitely wait for the namespace to be deleted, use context.WithTimeout to limit this + timer := time.NewTimer(0) + defer timer.Stop() + for { - // Keep checking for the namespace to be deleted - _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + + timer.Reset(1 * time.Second) } - time.Sleep(1 * time.Second) } } diff --git a/src/pkg/k8s/nodes.go b/src/pkg/k8s/nodes.go index c2348e06f4..134c00b140 100644 --- a/src/pkg/k8s/nodes.go +++ b/src/pkg/k8s/nodes.go @@ -12,12 +12,12 @@ import ( ) // GetNodes returns a list of nodes from the k8s cluster. -func (k *K8s) GetNodes() (*corev1.NodeList, error) { +func (k *K8s) GetNodes(ctx context.Context) (*corev1.NodeList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Nodes().List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().Nodes().List(ctx, metaOptions) } // GetNode returns a node from the k8s cluster. -func (k *K8s) GetNode(nodeName string) (*corev1.Node, error) { - return k.Clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) +func (k *K8s) GetNode(ctx context.Context, nodeName string) (*corev1.Node, error) { + return k.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) } diff --git a/src/pkg/k8s/pods.go b/src/pkg/k8s/pods.go index bf29291587..be9c72bec4 100644 --- a/src/pkg/k8s/pods.go +++ b/src/pkg/k8s/pods.go @@ -14,8 +14,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const waitLimit = 30 - // GeneratePod creates a new pod without adding it to the k8s cluster. func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod { pod := &corev1.Pod{ @@ -34,33 +32,42 @@ func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod { } // DeletePod removes a pod from the cluster by namespace & name. -func (k *K8s) DeletePod(namespace string, name string) error { +func (k *K8s) DeletePod(ctx context.Context, namespace string, name string) error { deleteGracePeriod := int64(0) deletePolicy := metav1.DeletePropagationForeground - err := k.Clientset.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{ + + err := k.Clientset.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{ GracePeriodSeconds: &deleteGracePeriod, PropagationPolicy: &deletePolicy, }) - if err != nil { return err } + timer := time.NewTimer(0) + defer timer.Stop() + for { - // Keep checking for the pod to be deleted - _, err := k.Clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := k.Clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + + timer.Reset(1 * time.Second) } - time.Sleep(1 * time.Second) } } // DeletePods removes a collection of pods from the cluster by pod lookup. -func (k *K8s) DeletePods(target PodLookup) error { +func (k *K8s) DeletePods(ctx context.Context, target PodLookup) error { deleteGracePeriod := int64(0) deletePolicy := metav1.DeletePropagationForeground - return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection(context.TODO(), + return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection( + ctx, metav1.DeleteOptions{ GracePeriodSeconds: &deleteGracePeriod, PropagationPolicy: &deletePolicy, @@ -72,111 +79,115 @@ func (k *K8s) DeletePods(target PodLookup) error { } // CreatePod inserts the given pod into the cluster. -func (k *K8s) CreatePod(pod *corev1.Pod) (*corev1.Pod, error) { +func (k *K8s) CreatePod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, error) { createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, createOptions) + return k.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, createOptions) } // GetAllPods returns a list of pods from the cluster for all namespaces. -func (k *K8s) GetAllPods() (*corev1.PodList, error) { - return k.GetPods(corev1.NamespaceAll, metav1.ListOptions{}) +func (k *K8s) GetAllPods(ctx context.Context) (*corev1.PodList, error) { + return k.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{}) } // GetPods returns a list of pods from the cluster by namespace. -func (k *K8s) GetPods(namespace string, listOpts metav1.ListOptions) (*corev1.PodList, error) { - return k.Clientset.CoreV1().Pods(namespace).List(context.TODO(), listOpts) +func (k *K8s) GetPods(ctx context.Context, namespace string, listOpts metav1.ListOptions) (*corev1.PodList, error) { + return k.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts) } // WaitForPodsAndContainers attempts to find pods matching the given selector and optional inclusion filter // It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names // If the timeout is reached, an empty list will be returned. -func (k *K8s) WaitForPodsAndContainers(target PodLookup, include PodFilter) []corev1.Pod { - for count := 0; count < waitLimit; count++ { +func (k *K8s) WaitForPodsAndContainers(ctx context.Context, target PodLookup, include PodFilter) []corev1.Pod { + waitCtx, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() - pods, err := k.Clientset.CoreV1().Pods(target.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: target.Selector, - }) - if err != nil { - k.Log("Unable to find matching pods: %w", err) - break - } + timer := time.NewTimer(0) + defer timer.Stop() - k.Log("Found %d pods for target %#v", len(pods.Items), target) + for { + select { + case <-waitCtx.Done(): + k.Log("Pod lookup failed: %v", ctx.Err()) + return nil + case <-timer.C: + pods, err := k.GetPods(ctx, target.Namespace, metav1.ListOptions{ + LabelSelector: target.Selector, + }) + if err != nil { + k.Log("Unable to find matching pods: %w", err) + return nil + } - var readyPods = []corev1.Pod{} + k.Log("Found %d pods for target %#v", len(pods.Items), target) - // Sort the pods from newest to oldest - sort.Slice(pods.Items, func(i, j int) bool { - return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time) - }) + var readyPods = []corev1.Pod{} - for _, pod := range pods.Items { - k.Log("Testing pod %q", pod.Name) + // Sort the pods from newest to oldest + sort.Slice(pods.Items, func(i, j int) bool { + return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time) + }) - // If an include function is provided, only keep pods that return true - if include != nil && !include(pod) { - continue - } + for _, pod := range pods.Items { + k.Log("Testing pod %q", pod.Name) - // Handle container targeting - if target.Container != "" { - k.Log("Testing pod %q for container %q", pod.Name, target.Container) - var matchesInitContainer bool - - // Check the status of initContainers for a running match - for _, initContainer := range pod.Status.InitContainerStatuses { - isRunning := initContainer.State.Running != nil - if isRunning && initContainer.Name == target.Container { - // On running match in initContainer break this loop - matchesInitContainer = true - readyPods = append(readyPods, pod) - break - } - } - - // Don't check any further if there's already a match - if matchesInitContainer { + // If an include function is provided, only keep pods that return true + if include != nil && !include(pod) { continue } - // Check the status of regular containers for a running match - for _, container := range pod.Status.ContainerStatuses { - isRunning := container.State.Running != nil - if isRunning && container.Name == target.Container { + // Handle container targeting + if target.Container != "" { + k.Log("Testing pod %q for container %q", pod.Name, target.Container) + + // Check the status of initContainers for a running match + for _, initContainer := range pod.Status.InitContainerStatuses { + isRunning := initContainer.State.Running != nil + if initContainer.Name == target.Container && isRunning { + // On running match in initContainer break this loop + readyPods = append(readyPods, pod) + break + } + } + + // Check the status of regular containers for a running match + for _, container := range pod.Status.ContainerStatuses { + isRunning := container.State.Running != nil + if container.Name == target.Container && isRunning { + readyPods = append(readyPods, pod) + break + } + } + } else { + status := pod.Status.Phase + k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) + // Regular status checking without a container + if status == corev1.PodRunning { readyPods = append(readyPods, pod) + break } } - } else { - status := pod.Status.Phase - k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) - // Regular status checking without a container - if status == corev1.PodRunning { - readyPods = append(readyPods, pod) - } } + if len(readyPods) > 0 { + return readyPods + } + timer.Reset(3 * time.Second) } - - if len(readyPods) > 0 { - return readyPods - } - - time.Sleep(3 * time.Second) } - - k.Log("Pod lookup timeout exceeded") - - return []corev1.Pod{} } // FindPodContainerPort will find a pod's container port from a service and return it. // // Returns 0 if no port is found. -func (k *K8s) FindPodContainerPort(svc corev1.Service) int { +func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) int { selectorLabelsOfPods := MakeLabels(svc.Spec.Selector) - pods := k.WaitForPodsAndContainers(PodLookup{ - Namespace: svc.Namespace, - Selector: selectorLabelsOfPods, - }, nil) + pods := k.WaitForPodsAndContainers( + ctx, + PodLookup{ + Namespace: svc.Namespace, + Selector: selectorLabelsOfPods, + }, + nil, + ) for _, pod := range pods { // Find the matching name on the port in the pod diff --git a/src/pkg/k8s/sa.go b/src/pkg/k8s/sa.go index 26e48d134d..38b7624130 100644 --- a/src/pkg/k8s/sa.go +++ b/src/pkg/k8s/sa.go @@ -15,48 +15,50 @@ import ( ) // GetAllServiceAccounts returns a list of services accounts for all namespaces. -func (k *K8s) GetAllServiceAccounts() (*corev1.ServiceAccountList, error) { - return k.GetServiceAccounts(corev1.NamespaceAll) +func (k *K8s) GetAllServiceAccounts(ctx context.Context) (*corev1.ServiceAccountList, error) { + return k.GetServiceAccounts(ctx, corev1.NamespaceAll) } // GetServiceAccounts returns a list of service accounts in a given namespace. -func (k *K8s) GetServiceAccounts(namespace string) (*corev1.ServiceAccountList, error) { +func (k *K8s) GetServiceAccounts(ctx context.Context, namespace string) (*corev1.ServiceAccountList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(namespace).List(ctx, metaOptions) } // GetServiceAccount returns a single service account by namespace and name. -func (k *K8s) GetServiceAccount(namespace, name string) (*corev1.ServiceAccount, error) { +func (k *K8s) GetServiceAccount(ctx context.Context, namespace, name string) (*corev1.ServiceAccount, error) { metaOptions := metav1.GetOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metaOptions) } // UpdateServiceAccount updates the given service account in the cluster. -func (k *K8s) UpdateServiceAccount(svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { +func (k *K8s) UpdateServiceAccount(ctx context.Context, svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { metaOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(context.TODO(), svcAccount, metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(ctx, svcAccount, metaOptions) } // WaitForServiceAccount waits for a service account to be created in the cluster. -func (k *K8s) WaitForServiceAccount(ns, name string, timeout time.Duration) (*corev1.ServiceAccount, error) { - expired := time.After(timeout) +func (k *K8s) WaitForServiceAccount(ctx context.Context, ns, name string) (*corev1.ServiceAccount, error) { + timer := time.NewTimer(0) + defer timer.Stop() for { select { - case <-expired: - return nil, fmt.Errorf("timed out waiting for service account %s/%s to exist", ns, name) - - default: - sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - time.Sleep(1 * time.Second) - continue - } + case <-ctx.Done(): + return nil, fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) + case <-timer.C: + sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) + if err == nil { + return sa, nil + } + + if errors.IsNotFound(err) { + k.Log("Service account %s/%s not found, retrying...", ns, name) + } else { return nil, fmt.Errorf("error getting service account %s/%s: %w", ns, name, err) } - return sa, nil + timer.Reset(1 * time.Second) } } } diff --git a/src/pkg/k8s/secrets.go b/src/pkg/k8s/secrets.go index f92882b8d0..d391b97771 100644 --- a/src/pkg/k8s/secrets.go +++ b/src/pkg/k8s/secrets.go @@ -15,14 +15,14 @@ import ( ) // GetSecret returns a Kubernetes secret. -func (k *K8s) GetSecret(namespace, name string) (*corev1.Secret, error) { - return k.Clientset.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +func (k *K8s) GetSecret(ctx context.Context, namespace, name string) (*corev1.Secret, error) { + return k.Clientset.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) } // GetSecretsWithLabel returns a list of Kubernetes secrets with the given label. -func (k *K8s) GetSecretsWithLabel(namespace, labelSelector string) (*corev1.SecretList, error) { +func (k *K8s) GetSecretsWithLabel(ctx context.Context, namespace, labelSelector string) (*corev1.SecretList, error) { listOptions := metav1.ListOptions{LabelSelector: labelSelector} - return k.Clientset.CoreV1().Secrets(namespace).List(context.TODO(), listOptions) + return k.Clientset.CoreV1().Secrets(namespace).List(ctx, listOptions) } // GenerateSecret returns a Kubernetes secret object without applying it to the cluster. @@ -58,20 +58,20 @@ func (k *K8s) GenerateTLSSecret(namespace, name string, conf GeneratedPKI) (*cor } // CreateOrUpdateTLSSecret creates or updates a Kubernetes secret with a new TLS secret. -func (k *K8s) CreateOrUpdateTLSSecret(namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { +func (k *K8s) CreateOrUpdateTLSSecret(ctx context.Context, namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { secret, err := k.GenerateTLSSecret(namespace, name, conf) if err != nil { return secret, err } - return k.CreateOrUpdateSecret(secret) + return k.CreateOrUpdateSecret(ctx, secret) } // DeleteSecret deletes a Kubernetes secret. -func (k *K8s) DeleteSecret(secret *corev1.Secret) error { +func (k *K8s) DeleteSecret(ctx context.Context, secret *corev1.Secret) error { namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - err := namespaceSecrets.Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) + err := namespaceSecrets.Delete(ctx, secret.Name, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("error deleting the secret: %w", err) } @@ -80,18 +80,18 @@ func (k *K8s) DeleteSecret(secret *corev1.Secret) error { } // CreateOrUpdateSecret creates or updates a Kubernetes secret. -func (k *K8s) CreateOrUpdateSecret(secret *corev1.Secret) (createdSecret *corev1.Secret, err error) { +func (k *K8s) CreateOrUpdateSecret(ctx context.Context, secret *corev1.Secret) (createdSecret *corev1.Secret, err error) { namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - if _, err = k.GetSecret(secret.Namespace, secret.Name); err != nil { + if _, err = k.GetSecret(ctx, secret.Namespace, secret.Name); err != nil { // create the given secret - if createdSecret, err = namespaceSecrets.Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { + if createdSecret, err = namespaceSecrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil { return createdSecret, fmt.Errorf("unable to create the secret: %w", err) } } else { // update the given secret - if createdSecret, err = namespaceSecrets.Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil { + if createdSecret, err = namespaceSecrets.Update(ctx, secret, metav1.UpdateOptions{}); err != nil { return createdSecret, fmt.Errorf("unable to update the secret: %w", err) } } diff --git a/src/pkg/k8s/services.go b/src/pkg/k8s/services.go index 725fc2788b..63b847d413 100644 --- a/src/pkg/k8s/services.go +++ b/src/pkg/k8s/services.go @@ -27,12 +27,12 @@ type ServiceInfo struct { } // ReplaceService deletes and re-creates a service. -func (k *K8s) ReplaceService(service *corev1.Service) (*corev1.Service, error) { - if err := k.DeleteService(service.Namespace, service.Name); err != nil { +func (k *K8s) ReplaceService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { + if err := k.DeleteService(ctx, service.Namespace, service.Name); err != nil { return nil, err } - return k.CreateService(service) + return k.CreateService(ctx, service) } // GenerateService returns a K8s service struct without writing to the cluster. @@ -54,28 +54,28 @@ func (k *K8s) GenerateService(namespace, name string) *corev1.Service { } // DeleteService removes a service from the cluster by namespace and name. -func (k *K8s) DeleteService(namespace, name string) error { - return k.Clientset.CoreV1().Services(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) +func (k *K8s) DeleteService(ctx context.Context, namespace, name string) error { + return k.Clientset.CoreV1().Services(namespace).Delete(ctx, name, metav1.DeleteOptions{}) } // CreateService creates the given service in the cluster. -func (k *K8s) CreateService(service *corev1.Service) (*corev1.Service, error) { +func (k *K8s) CreateService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Services(service.Namespace).Create(context.TODO(), service, createOptions) + return k.Clientset.CoreV1().Services(service.Namespace).Create(ctx, service, createOptions) } // GetService returns a Kubernetes service resource in the provided namespace with the given name. -func (k *K8s) GetService(namespace, serviceName string) (*corev1.Service, error) { - return k.Clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) +func (k *K8s) GetService(ctx context.Context, namespace, serviceName string) (*corev1.Service, error) { + return k.Clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{}) } // GetServices returns a list of services in the provided namespace. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServices(namespace string) (*corev1.ServiceList, error) { - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) +func (k *K8s) GetServices(ctx context.Context, namespace string) (*corev1.ServiceList, error) { + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{}) } // GetServicesByLabel returns a list of matched services given a label and value. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.ServiceList, error) { +func (k *K8s) GetServicesByLabel(ctx context.Context, namespace, label, value string) (*corev1.ServiceList, error) { // Create the selector and add the requirement labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: Labels{ @@ -84,11 +84,11 @@ func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.Servic }) // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) } // GetServicesByLabelExists returns a list of matched services given a label. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabelExists(namespace, label string) (*corev1.ServiceList, error) { +func (k *K8s) GetServicesByLabelExists(ctx context.Context, namespace, label string) (*corev1.ServiceList, error) { // Create the selector and add the requirement labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ @@ -98,12 +98,12 @@ func (k *K8s) GetServicesByLabelExists(namespace, label string) (*corev1.Service }) // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) } // ServiceInfoFromNodePortURL takes a nodePortURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: // Example nodePortURL: 127.0.0.1:{PORT}. -func (k *K8s) ServiceInfoFromNodePortURL(nodePortURL string) (*ServiceInfo, error) { +func (k *K8s) ServiceInfoFromNodePortURL(ctx context.Context, nodePortURL string) (*ServiceInfo, error) { // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) parsedURL, err := url.Parse(nodePortURL) if err != nil { @@ -128,7 +128,7 @@ func (k *K8s) ServiceInfoFromNodePortURL(nodePortURL string) (*ServiceInfo, erro return nil, fmt.Errorf("node port services should use the port range 30000-32767") } - services, err := k.GetServices("") + services, err := k.GetServices(ctx, "") if err != nil { return nil, err } diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go index a4b910fccf..6c4f46e0cf 100644 --- a/src/pkg/k8s/tunnel.go +++ b/src/pkg/k8s/tunnel.go @@ -7,6 +7,7 @@ package k8s // Forked from https://github.com/gruntwork-io/terratest/blob/v0.38.8/modules/k8s/tunnel.go import ( + "context" "fmt" "io" "net/http" @@ -79,24 +80,36 @@ func (tunnel *Tunnel) Wrap(function func() error) error { } // Connect will establish a tunnel to the specified target. -func (tunnel *Tunnel) Connect() (string, error) { - url, err := tunnel.establish() +func (tunnel *Tunnel) Connect(ctx context.Context) (string, error) { + url, err := tunnel.establish(ctx) // Try to establish the tunnel up to 3 times. if err != nil { tunnel.attempt++ + // If we have exceeded the number of attempts, exit with an error. if tunnel.attempt > 3 { return "", fmt.Errorf("unable to establish tunnel after 3 attempts: %w", err) } + // Otherwise, retry the connection but delay increasing intervals between attempts. delay := tunnel.attempt * 10 tunnel.kube.Log("%s", err.Error()) tunnel.kube.Log("Delay creating tunnel, waiting %d seconds...", delay) - time.Sleep(time.Duration(delay) * time.Second) - url, err = tunnel.Connect() - if err != nil { - return "", err + + timer := time.NewTimer(0) + defer timer.Stop() + + select { + case <-ctx.Done(): + return "", ctx.Err() + case <-timer.C: + url, err = tunnel.Connect(ctx) + if err != nil { + return "", err + } + + timer.Reset(time.Duration(delay) * time.Second) } } @@ -129,7 +142,7 @@ func (tunnel *Tunnel) Close() { } // establish opens a tunnel to a kubernetes resource, as specified by the provided tunnel struct. -func (tunnel *Tunnel) establish() (string, error) { +func (tunnel *Tunnel) establish(ctx context.Context) (string, error) { var err error // Track this locally as we may need to retry if the tunnel fails. @@ -163,7 +176,7 @@ func (tunnel *Tunnel) establish() (string, error) { tunnel.kube.Log(message) // Find the pod to port forward to - podName, err := tunnel.getAttachablePodForResource() + podName, err := tunnel.getAttachablePodForResource(ctx) if err != nil { return "", fmt.Errorf("unable to find pod attached to given resource: %w", err) } @@ -222,29 +235,33 @@ func (tunnel *Tunnel) establish() (string, error) { // getAttachablePodForResource will find a pod that can be port forwarded to the provided resource type and return // the name. -func (tunnel *Tunnel) getAttachablePodForResource() (string, error) { +func (tunnel *Tunnel) getAttachablePodForResource(ctx context.Context) (string, error) { switch tunnel.resourceType { case PodResource: return tunnel.resourceName, nil case SvcResource: - return tunnel.getAttachablePodForService() + return tunnel.getAttachablePodForService(ctx) default: return "", fmt.Errorf("unknown resource type: %s", tunnel.resourceType) } } // getAttachablePodForService will find an active pod associated with the Service and return the pod name. -func (tunnel *Tunnel) getAttachablePodForService() (string, error) { - service, err := tunnel.kube.GetService(tunnel.namespace, tunnel.resourceName) +func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, error) { + service, err := tunnel.kube.GetService(ctx, tunnel.namespace, tunnel.resourceName) if err != nil { return "", fmt.Errorf("unable to find the service: %w", err) } selectorLabelsOfPods := MakeLabels(service.Spec.Selector) - servicePods := tunnel.kube.WaitForPodsAndContainers(PodLookup{ - Namespace: tunnel.namespace, - Selector: selectorLabelsOfPods, - }, nil) + servicePods := tunnel.kube.WaitForPodsAndContainers( + ctx, + PodLookup{ + Namespace: tunnel.namespace, + Selector: selectorLabelsOfPods, + }, + nil, + ) if len(servicePods) < 1 { return "", fmt.Errorf("no pods found for service %s", tunnel.resourceName) diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index 53b918bde2..f432b8d423 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -5,11 +5,11 @@ package packager import ( + "context" "errors" "fmt" "os" "strings" - "time" "slices" @@ -159,17 +159,17 @@ func (p *Packager) GetVariableConfig() *variables.VariableConfig { } // connectToCluster attempts to connect to a cluster if a connection is not already established -func (p *Packager) connectToCluster(timeout time.Duration) (err error) { +func (p *Packager) connectToCluster(ctx context.Context) (err error) { if p.isConnectedToCluster() { return nil } - p.cluster, err = cluster.NewClusterWithWait(timeout) + p.cluster, err = cluster.NewClusterWithWait(ctx) if err != nil { return err } - return p.attemptClusterChecks() + return p.attemptClusterChecks(ctx) } // isConnectedToCluster returns whether the current packager instance is connected to a cluster @@ -189,19 +189,19 @@ func (p *Packager) hasImages() bool { // attemptClusterChecks attempts to connect to the cluster and check for useful metadata and config mismatches. // NOTE: attemptClusterChecks should only return an error if there is a problem significant enough to halt a deployment, otherwise it should return nil and print a warning message. -func (p *Packager) attemptClusterChecks() (err error) { +func (p *Packager) attemptClusterChecks(ctx context.Context) (err error) { spinner := message.NewProgressSpinner("Gathering additional cluster information (if available)") defer spinner.Stop() // Check if the package has already been deployed and get its generation - if existingDeployedPackage, _ := p.cluster.GetDeployedPackage(p.cfg.Pkg.Metadata.Name); existingDeployedPackage != nil { + if existingDeployedPackage, _ := p.cluster.GetDeployedPackage(ctx, p.cfg.Pkg.Metadata.Name); existingDeployedPackage != nil { // If this package has been deployed before, increment the package generation within the secret p.generation = existingDeployedPackage.Generation + 1 } // Check the clusters architecture matches the package spec - if err := p.validatePackageArchitecture(); err != nil { + if err := p.validatePackageArchitecture(ctx); err != nil { if errors.Is(err, lang.ErrUnableToCheckArch) { message.Warnf("Unable to validate package architecture: %s", err.Error()) } else { @@ -210,7 +210,7 @@ func (p *Packager) attemptClusterChecks() (err error) { } // Check for any breaking changes between the initialized Zarf version and this CLI - if existingInitPackage, _ := p.cluster.GetDeployedPackage("init"); existingInitPackage != nil { + if existingInitPackage, _ := p.cluster.GetDeployedPackage(ctx, "init"); existingInitPackage != nil { // Use the build version instead of the metadata since this will support older Zarf versions deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version) } @@ -221,13 +221,13 @@ func (p *Packager) attemptClusterChecks() (err error) { } // validatePackageArchitecture validates that the package architecture matches the target cluster architecture. -func (p *Packager) validatePackageArchitecture() error { +func (p *Packager) validatePackageArchitecture(ctx context.Context) error { // Ignore this check if we don't have a cluster connection, or the package contains no images if !p.isConnectedToCluster() || !p.hasImages() { return nil } - clusterArchitectures, err := p.cluster.GetArchitectures() + clusterArchitectures, err := p.cluster.GetArchitectures(ctx) if err != nil { return lang.ErrUnableToCheckArch } diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go index f2d085a1bd..3ead7f9b4b 100644 --- a/src/pkg/packager/common_test.go +++ b/src/pkg/packager/common_test.go @@ -4,6 +4,7 @@ package packager import ( + "context" "errors" "fmt" "testing" @@ -132,7 +133,7 @@ func TestValidatePackageArchitecture(t *testing.T) { return true, nodeList, nil }) - err := p.validatePackageArchitecture() + err := p.validatePackageArchitecture(context.TODO()) require.Equal(t, testCase.expectedError, err) }) diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index a595888414..039a63cb61 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" "path/filepath" @@ -32,16 +33,16 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (p *Packager) resetRegistryHPA() { +func (p *Packager) resetRegistryHPA(ctx context.Context) { if p.isConnectedToCluster() && p.hpaModified { - if err := p.cluster.EnableRegHPAScaleDown(); err != nil { + if err := p.cluster.EnableRegHPAScaleDown(ctx); err != nil { message.Debugf("unable to reenable the registry HPA scale down: %s", err.Error()) } } } // Deploy attempts to deploy the given PackageConfig. -func (p *Packager) Deploy() (err error) { +func (p *Packager) Deploy(ctx context.Context) (err error) { isInteractive := !config.CommonOptions.Confirm @@ -100,10 +101,10 @@ func (p *Packager) Deploy() (err error) { p.hpaModified = false p.connectStrings = make(types.ConnectStrings) // Reset registry HPA scale down whether an error occurs or not - defer p.resetRegistryHPA() + defer p.resetRegistryHPA(ctx) // Get a list of all the components we are deploying and actually deploy them - deployedComponents, err := p.deployComponents() + deployedComponents, err := p.deployComponents(ctx) if err != nil { return err } @@ -114,14 +115,13 @@ func (p *Packager) Deploy() (err error) { // Notify all the things about the successful deployment message.Successf("Zarf deployment complete") - p.printTablesForDeployment(deployedComponents) + p.printTablesForDeployment(ctx, deployedComponents) return nil } // deployComponents loops through a list of ZarfComponents and deploys them. -func (p *Packager) deployComponents() (deployedComponents []types.DeployedComponent, err error) { - +func (p *Packager) deployComponents(ctx context.Context) (deployedComponents []types.DeployedComponent, err error) { // Check if this package has been deployed before and grab relevant information about already deployed components if p.generation == 0 { p.generation = 1 // If this is the first deployment, set the generation to 1 @@ -142,15 +142,16 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon if p.cfg.Pkg.IsInitConfig() { timeout = 5 * time.Minute } - - if err := p.connectToCluster(timeout); err != nil { + connectCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + if err := p.connectToCluster(connectCtx); err != nil { return deployedComponents, fmt.Errorf("unable to connect to the Kubernetes cluster: %w", err) } } // Ensure we don't overwrite any installedCharts data when updating the package secret if p.isConnectedToCluster() { - deployedComponent.InstalledCharts, err = p.cluster.GetInstalledChartsForComponent(p.cfg.Pkg.Metadata.Name, component) + deployedComponent.InstalledCharts, err = p.cluster.GetInstalledChartsForComponent(ctx, p.cfg.Pkg.Metadata.Name, component) if err != nil { message.Debugf("Unable to fetch installed Helm charts for component '%s': %s", component.Name, err.Error()) } @@ -161,7 +162,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon // Update the package secret to indicate that we are attempting to deploy this component if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %s: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -170,9 +171,9 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon var charts []types.InstalledChart var deployErr error if p.cfg.Pkg.IsInitConfig() { - charts, deployErr = p.deployInitComponent(component) + charts, deployErr = p.deployInitComponent(ctx, component) } else { - charts, deployErr = p.deployComponent(component, false /* keep img checksum */, false /* always push images */) + charts, deployErr = p.deployComponent(ctx, component, false /* keep img checksum */, false /* always push images */) } onDeploy := component.Actions.OnDeploy @@ -189,7 +190,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon // Update the package secret to indicate that we failed to deploy this component deployedComponents[idx].Status = types.ComponentStatusFailed if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -201,7 +202,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon deployedComponents[idx].InstalledCharts = charts deployedComponents[idx].Status = types.ComponentStatusSucceeded if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -215,7 +216,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon return deployedComponents, nil } -func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts []types.InstalledChart, err error) { +func (p *Packager) deployInitComponent(ctx context.Context, component types.ZarfComponent) (charts []types.InstalledChart, err error) { hasExternalRegistry := p.cfg.InitOpts.RegistryInfo.Address != "" isSeedRegistry := component.Name == "zarf-seed-registry" isRegistry := component.Name == "zarf-registry" @@ -229,7 +230,7 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] // Always init the state before the first component that requires the cluster (on most deployments, the zarf-seed-registry) if component.RequiresCluster() && p.state == nil { - err = p.cluster.InitZarfState(p.cfg.InitOpts) + err = p.cluster.InitZarfState(ctx, p.cfg.InitOpts) if err != nil { return charts, fmt.Errorf("unable to initialize Zarf state: %w", err) } @@ -247,17 +248,17 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] // Before deploying the seed registry, start the injector if isSeedRegistry { - p.cluster.StartInjectionMadness(p.layout.Base, p.layout.Images.Base, component.Images) + p.cluster.StartInjectionMadness(ctx, p.layout.Base, p.layout.Images.Base, component.Images) } - charts, err = p.deployComponent(component, isAgent /* skip img checksum if isAgent */, isSeedRegistry /* skip image push if isSeedRegistry */) + charts, err = p.deployComponent(ctx, component, isAgent /* skip img checksum if isAgent */, isSeedRegistry /* skip image push if isSeedRegistry */) if err != nil { return charts, err } // Do cleanup for when we inject the seed registry during initialization if isSeedRegistry { - if err := p.cluster.StopInjectionMadness(); err != nil { + if err := p.cluster.StopInjectionMadness(ctx); err != nil { return charts, fmt.Errorf("unable to seed the Zarf Registry: %w", err) } } @@ -266,7 +267,7 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] } // Deploy a Zarf Component. -func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum bool, noImgPush bool) (charts []types.InstalledChart, err error) { +func (p *Packager) deployComponent(ctx context.Context, component types.ZarfComponent, noImgChecksum bool, noImgPush bool) (charts []types.InstalledChart, err error) { // Toggles for general deploy operations componentPath := p.layout.Components.Dirs[component.Name] @@ -285,7 +286,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum if component.RequiresCluster() { // Setup the state in the config if p.state == nil { - err = p.setupState() + err = p.setupState(ctx) if err != nil { return charts, err } @@ -293,7 +294,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum // Disable the registry HPA scale down if we are deploying images and it is not already disabled if hasImages && !p.hpaModified && p.state.RegistryInfo.InternalRegistry { - if err := p.cluster.DisableRegHPAScaleDown(); err != nil { + if err := p.cluster.DisableRegHPAScaleDown(ctx); err != nil { message.Debugf("unable to disable the registry HPA scale down: %s", err.Error()) } else { p.hpaModified = true @@ -317,13 +318,13 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum } if hasImages { - if err := p.pushImagesToRegistry(component.Images, noImgChecksum); err != nil { + if err := p.pushImagesToRegistry(ctx, component.Images, noImgChecksum); err != nil { return charts, fmt.Errorf("unable to push images to the registry: %w", err) } } if hasRepos { - if err = p.pushReposToRepository(componentPath.Repos, component.Repos); err != nil { + if err = p.pushReposToRepository(ctx, componentPath.Repos, component.Repos); err != nil { return charts, fmt.Errorf("unable to push the repos to the repository: %w", err) } } @@ -334,7 +335,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum for idx, data := range component.DataInjections { waitGroup.Add(1) - go p.cluster.HandleDataInjection(&waitGroup, data, componentPath, idx) + go p.cluster.HandleDataInjection(ctx, &waitGroup, data, componentPath, idx) } } @@ -431,12 +432,12 @@ func (p *Packager) processComponentFiles(component types.ZarfComponent, pkgLocat } // setupState fetches the current ZarfState from the k8s cluster and sets the packager to use it -func (p *Packager) setupState() (err error) { +func (p *Packager) setupState(ctx context.Context) (err error) { // If we are touching K8s, make sure we can talk to it once per deployment spinner := message.NewProgressSpinner("Loading the Zarf State from the Kubernetes cluster") defer spinner.Stop() - state, err := p.cluster.LoadZarfState() + state, err := p.cluster.LoadZarfState(ctx) // Return on error if we are not in YOLO mode if err != nil && !p.cfg.Pkg.Metadata.YOLO { return fmt.Errorf("%s %w", lang.ErrLoadState, err) @@ -448,7 +449,7 @@ func (p *Packager) setupState() (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") zarfNamespace := p.cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) - if _, err := p.cluster.CreateNamespace(zarfNamespace); err != nil { + if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } } @@ -480,7 +481,7 @@ func (p *Packager) populatePackageVariableConfig() error { } // Push all of the components images to the configured container registry. -func (p *Packager) pushImagesToRegistry(componentImages []string, noImgChecksum bool) error { +func (p *Packager) pushImagesToRegistry(ctx context.Context, componentImages []string, noImgChecksum bool) error { var combinedImageList []transform.Image for _, src := range componentImages { ref, err := transform.ParseImageRef(src) @@ -501,11 +502,11 @@ func (p *Packager) pushImagesToRegistry(componentImages []string, noImgChecksum Retries: p.cfg.PkgOpts.Retries, } - return images.Push(pushCfg) + return images.Push(ctx, pushCfg) } // Push all of the components git repos to the configured git server. -func (p *Packager) pushReposToRepository(reposPath string, repos []string) error { +func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, repos []string) error { for _, repoURL := range repos { // Create an anonymous function to push the repo to the Zarf git server tryPush := func() error { @@ -518,7 +519,9 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error // If this is a service (svcInfo is not nil), create a port-forward tunnel to that resource if svcInfo != nil { if !p.isConnectedToCluster() { - err := p.connectToCluster(5 * time.Second) + connectCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + err := p.connectToCluster(connectCtx) if err != nil { return err } @@ -529,7 +532,7 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -684,7 +687,7 @@ func (p *Packager) installChartAndManifests(componentPaths *layout.ComponentPath return installedCharts, nil } -func (p *Packager) printTablesForDeployment(componentsToDeploy []types.DeployedComponent) { +func (p *Packager) printTablesForDeployment(ctx context.Context, componentsToDeploy []types.DeployedComponent) { // If not init config, print the application connection table if !p.cfg.Pkg.IsInitConfig() { @@ -692,7 +695,7 @@ func (p *Packager) printTablesForDeployment(componentsToDeploy []types.DeployedC } else { if p.cluster != nil { // Grab a fresh copy of the state (if we are able) to print the most up-to-date version of the creds - freshState, err := p.cluster.LoadZarfState() + freshState, err := p.cluster.LoadZarfState(ctx) if err != nil { freshState = p.state } diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index 6f7a685e95..187f74c70c 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" "runtime" @@ -19,7 +20,7 @@ import ( ) // DevDeploy creates + deploys a package in one shot -func (p *Packager) DevDeploy() error { +func (p *Packager) DevDeploy(ctx context.Context) error { config.CommonOptions.Confirm = true p.cfg.CreateOpts.SkipSBOM = !p.cfg.CreateOpts.NoYOLO @@ -81,11 +82,11 @@ func (p *Packager) DevDeploy() error { } else { p.hpaModified = false // Reset registry HPA scale down whether an error occurs or not - defer p.resetRegistryHPA() + defer p.resetRegistryHPA(ctx) } // Get a list of all the components we are deploying and actually deploy them - deployedComponents, err := p.deployComponents() + deployedComponents, err := p.deployComponents(ctx) if err != nil { return err } diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go index 0afec2e480..658967560d 100644 --- a/src/pkg/packager/mirror.go +++ b/src/pkg/packager/mirror.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "runtime" "strings" @@ -16,7 +17,7 @@ import ( ) // Mirror pulls resources from a package (images, git repositories, etc) and pushes them to remotes in the air gap without deploying them -func (p *Packager) Mirror() (err error) { +func (p *Packager) Mirror(ctx context.Context) (err error) { filter := filters.Combine( filters.ByLocalOS(runtime.GOOS), filters.BySelectState(p.cfg.PkgOpts.OptionalComponents), @@ -46,7 +47,7 @@ func (p *Packager) Mirror() (err error) { } for _, component := range p.cfg.Pkg.Components { - if err := p.mirrorComponent(component); err != nil { + if err := p.mirrorComponent(ctx, component); err != nil { return err } } @@ -54,7 +55,7 @@ func (p *Packager) Mirror() (err error) { } // mirrorComponent mirrors a Zarf Component. -func (p *Packager) mirrorComponent(component types.ZarfComponent) error { +func (p *Packager) mirrorComponent(ctx context.Context, component types.ZarfComponent) error { componentPaths := p.layout.Components.Dirs[component.Name] // All components now require a name @@ -64,13 +65,13 @@ func (p *Packager) mirrorComponent(component types.ZarfComponent) error { hasRepos := len(component.Repos) > 0 if hasImages { - if err := p.pushImagesToRegistry(component.Images, p.cfg.MirrorOpts.NoImgChecksum); err != nil { + if err := p.pushImagesToRegistry(ctx, component.Images, p.cfg.MirrorOpts.NoImgChecksum); err != nil { return fmt.Errorf("unable to push images to the registry: %w", err) } } if hasRepos { - if err := p.pushReposToRepository(componentPaths.Repos, component.Repos); err != nil { + if err := p.pushReposToRepository(ctx, componentPaths.Repos, component.Repos); err != nil { return fmt.Errorf("unable to push the repos to the repository: %w", err) } } diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index c0329ce55d..a107147714 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -5,6 +5,7 @@ package packager import ( + "context" "encoding/json" "errors" "fmt" @@ -26,7 +27,7 @@ import ( ) // Remove removes a package that was already deployed onto a cluster, uninstalling all installed helm charts. -func (p *Packager) Remove() (err error) { +func (p *Packager) Remove(ctx context.Context) (err error) { _, isClusterSource := p.source.(*sources.ClusterSource) if isClusterSource { p.cluster = p.source.(*sources.ClusterSource).Cluster @@ -70,11 +71,13 @@ func (p *Packager) Remove() (err error) { deployedPackage := &types.DeployedPackage{} if packageRequiresCluster { - err = p.connectToCluster(cluster.DefaultTimeout) + connectCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + err = p.connectToCluster(connectCtx) if err != nil { return err } - deployedPackage, err = p.cluster.GetDeployedPackage(packageName) + deployedPackage, err = p.cluster.GetDeployedPackage(ctx, packageName) if err != nil { return fmt.Errorf("unable to load the secret for the package we are attempting to remove: %s", err.Error()) } @@ -93,7 +96,7 @@ func (p *Packager) Remove() (err error) { continue } - if deployedPackage, err = p.removeComponent(deployedPackage, dc, spinner); err != nil { + if deployedPackage, err = p.removeComponent(ctx, deployedPackage, dc, spinner); err != nil { return fmt.Errorf("unable to remove the component '%s': %w", dc.Name, err) } } @@ -101,7 +104,7 @@ func (p *Packager) Remove() (err error) { return nil } -func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { +func (p *Packager) updatePackageSecret(ctx context.Context, deployedPackage types.DeployedPackage) { // Only attempt to update the package secret if we are actually connected to a cluster if p.cluster != nil { secretName := config.ZarfPackagePrefix + deployedPackage.Name @@ -113,7 +116,7 @@ func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { newPackageSecretData, _ := json.Marshal(deployedPackage) newPackageSecret.Data["data"] = newPackageSecretData - _, err := p.cluster.CreateOrUpdateSecret(newPackageSecret) + _, err := p.cluster.CreateOrUpdateSecret(ctx, newPackageSecret) // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { @@ -122,7 +125,7 @@ func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { } } -func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deployedComponent types.DeployedComponent, spinner *message.Spinner) (*types.DeployedPackage, error) { +func (p *Packager) removeComponent(ctx context.Context, deployedPackage *types.DeployedPackage, deployedComponent types.DeployedComponent, spinner *message.Spinner) (*types.DeployedPackage, error) { components := deployedPackage.Data.Components c := helpers.Find(components, func(t types.ZarfComponent) bool { @@ -162,7 +165,7 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo deployedComponent.InstalledCharts = helpers.RemoveMatches(deployedComponent.InstalledCharts, func(t types.InstalledChart) bool { return t.ChartName == chart.ChartName }) - p.updatePackageSecret(*deployedPackage) + p.updatePackageSecret(ctx, *deployedPackage) } if err := actions.Run(onRemove.Defaults, onRemove.After, nil); err != nil { @@ -184,19 +187,19 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo secretName := config.ZarfPackagePrefix + deployedPackage.Name // All the installed components were deleted, therefore this package is no longer actually deployed - packageSecret, err := p.cluster.GetSecret(cluster.ZarfNamespaceName, secretName) + packageSecret, err := p.cluster.GetSecret(ctx, cluster.ZarfNamespaceName, secretName) // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } else { - err = p.cluster.DeleteSecret(packageSecret) + err = p.cluster.DeleteSecret(ctx, packageSecret) if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } } } else { - p.updatePackageSecret(*deployedPackage) + p.updatePackageSecret(ctx, *deployedPackage) } return deployedPackage, nil diff --git a/src/pkg/packager/sources/cluster.go b/src/pkg/packager/sources/cluster.go index 9fb74d6e81..c1478cb3da 100644 --- a/src/pkg/packager/sources/cluster.go +++ b/src/pkg/packager/sources/cluster.go @@ -5,6 +5,7 @@ package sources import ( + "context" "fmt" "github.com/defenseunicorns/pkg/helpers" @@ -25,7 +26,11 @@ func NewClusterSource(pkgOpts *types.ZarfPackageOptions) (PackageSource, error) if !types.IsLowercaseNumberHyphenNoStartHyphen(pkgOpts.PackageSource) { return nil, fmt.Errorf("invalid package name %q", pkgOpts.PackageSource) } - cluster, err := cluster.NewClusterWithWait(cluster.DefaultTimeout) + + ctx, cancel := context.WithTimeout(context.Background(), cluster.DefaultTimeout) + defer cancel() + + cluster, err := cluster.NewClusterWithWait(ctx) if err != nil { return nil, err } @@ -54,7 +59,9 @@ func (s *ClusterSource) Collect(_ string) (string, error) { // LoadPackageMetadata loads package metadata from a cluster. func (s *ClusterSource) LoadPackageMetadata(dst *layout.PackagePaths, _ bool, _ bool) (types.ZarfPackage, []string, error) { - dpkg, err := s.GetDeployedPackage(s.PackageSource) + ctx := context.Background() + + dpkg, err := s.GetDeployedPackage(ctx, s.PackageSource) if err != nil { return types.ZarfPackage{}, nil, err } diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index 4fe560f6a8..a66d390f18 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "crypto/tls" "fmt" "io" @@ -27,7 +28,9 @@ func TestConnectAndCreds(t *testing.T) { prevAgentSecretData, _, err := e2e.Kubectl("get", "secret", "agent-hook-tls", "-n", "zarf", "-o", "jsonpath={.data}") require.NoError(t, err) - connectToZarfServices(t) + ctx := context.Background() + + connectToZarfServices(ctx, t) stdOut, stdErr, err := e2e.Zarf("tools", "update-creds", "--confirm") require.NoError(t, err, stdOut, stdErr) @@ -36,7 +39,7 @@ func TestConnectAndCreds(t *testing.T) { require.NoError(t, err) require.NotEqual(t, prevAgentSecretData, newAgentSecretData, "agent secrets should not be the same") - connectToZarfServices(t) + connectToZarfServices(ctx, t) stdOut, stdErr, err = e2e.Zarf("package", "remove", "init", "--components=logging", "--confirm") require.NoError(t, err, stdOut, stdErr) @@ -68,7 +71,7 @@ func TestMetrics(t *testing.T) { tunnel, err := c.NewTunnel("zarf", "svc", "agent-hook", "", 8888, 8443) require.NoError(t, err) - _, err = tunnel.Connect() + _, err = tunnel.Connect(context.Background()) require.NoError(t, err) defer tunnel.Close() @@ -98,7 +101,7 @@ func TestMetrics(t *testing.T) { require.Equal(t, 200, resp.StatusCode) } -func connectToZarfServices(t *testing.T) { +func connectToZarfServices(ctx context.Context, t *testing.T) { // Make the Registry contains the images we expect stdOut, stdErr, err := e2e.Zarf("tools", "registry", "catalog") require.NoError(t, err, stdOut, stdErr) @@ -129,7 +132,7 @@ func connectToZarfServices(t *testing.T) { // Connect to Gitea c, err := cluster.NewCluster() require.NoError(t, err) - tunnelGit, err := c.Connect(cluster.ZarfGit) + tunnelGit, err := c.Connect(ctx, cluster.ZarfGit) require.NoError(t, err) defer tunnelGit.Close() @@ -150,7 +153,7 @@ func connectToZarfServices(t *testing.T) { // Connect to the Logging Stack c, err = cluster.NewCluster() require.NoError(t, err) - tunnelLog, err := c.Connect(cluster.ZarfLogging) + tunnelLog, err := c.Connect(ctx, cluster.ZarfLogging) require.NoError(t, err) defer tunnelLog.Close() diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index f83bd6ba05..7dc712e71a 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,6 +13,7 @@ import ( "path/filepath" "testing" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/types" @@ -35,13 +37,14 @@ func TestGit(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnelGit, err := c.Connect(cluster.ZarfGit) + ctx := context.Background() + tunnelGit, err := c.Connect(ctx, cluster.ZarfGit) require.NoError(t, err) defer tunnelGit.Close() testGitServerConnect(t, tunnelGit.HTTPEndpoint()) - testGitServerReadOnly(t, tunnelGit.HTTPEndpoint()) - testGitServerTagAndHash(t, tunnelGit.HTTPEndpoint()) + testGitServerReadOnly(ctx, t, tunnelGit.HTTPEndpoint()) + testGitServerTagAndHash(ctx, t, tunnelGit.HTTPEndpoint()) } func TestGitOpsFlux(t *testing.T) { @@ -65,9 +68,9 @@ func testGitServerConnect(t *testing.T, gitURL string) { require.Equal(t, 200, resp.StatusCode) } -func testGitServerReadOnly(t *testing.T, gitURL string) { +func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err) gitCfg := git.New(state.GitServer) @@ -88,9 +91,9 @@ func testGitServerReadOnly(t *testing.T, gitURL string) { require.True(t, permissionsMap["pull"].(bool)) } -func testGitServerTagAndHash(t *testing.T, gitURL string) { +func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") repoName := "zarf-public-test-2469062884" diff --git a/src/test/e2e/23_data_injection_test.go b/src/test/e2e/23_data_injection_test.go index ee65c49c0a..efbce9bc13 100644 --- a/src/test/e2e/23_data_injection_test.go +++ b/src/test/e2e/23_data_injection_test.go @@ -21,6 +21,8 @@ func TestDataInjection(t *testing.T) { t.Log("E2E: Data injection") e2e.SetupWithCluster(t) + ctx := context.Background() + path := fmt.Sprintf("build/zarf-package-kiwix-%s-3.5.0.tar", e2e.Arch) tmpdir := t.TempDir() @@ -28,7 +30,7 @@ func TestDataInjection(t *testing.T) { // Repeat the injection action 3 times to ensure the data injection is idempotent and doesn't fail to perform an upgrade for i := 0; i < 3; i++ { - runDataInjection(t, path) + runDataInjection(ctx, t, path) } // Verify the file and injection marker were created @@ -42,7 +44,7 @@ func TestDataInjection(t *testing.T) { // need target to equal svc that we are trying to connect to call checkForZarfConnectLabel c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("kiwix") + tunnel, err := c.Connect(ctx, "kiwix") require.NoError(t, err) defer tunnel.Close() @@ -64,9 +66,9 @@ func TestDataInjection(t *testing.T) { require.FileExists(t, filepath.Join(sbomPath, "kiwix", "zarf-component-kiwix-serve.json"), "The data-injection component should have an SBOM json") } -func runDataInjection(t *testing.T, path string) { +func runDataInjection(ctx context.Context, t *testing.T, path string) { // Limit this deploy to 5 minutes - ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) + ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() // Deploy the data injection example diff --git a/src/test/e2e/26_simple_packages_test.go b/src/test/e2e/26_simple_packages_test.go index 996982d383..c7a96e80d6 100644 --- a/src/test/e2e/26_simple_packages_test.go +++ b/src/test/e2e/26_simple_packages_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "fmt" "net/http" "path/filepath" @@ -26,7 +27,7 @@ func TestDosGames(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("doom") + tunnel, err := c.Connect(context.Background(), "doom") require.NoError(t, err) defer tunnel.Close() diff --git a/src/test/e2e/30_config_file_test.go b/src/test/e2e/30_config_file_test.go index a03844fa5a..5095efa91f 100644 --- a/src/test/e2e/30_config_file_test.go +++ b/src/test/e2e/30_config_file_test.go @@ -26,9 +26,9 @@ func TestConfigFile(t *testing.T) { e2e.CleanFiles(path) // Test the config file environment variable - os.Setenv("ZARF_CONFIG", filepath.Join(dir, config)) + t.Setenv("ZARF_CONFIG", filepath.Join(dir, config)) + defer os.Unsetenv("ZARF_CONFIG") configFileTests(t, dir, path) - os.Unsetenv("ZARF_CONFIG") configFileDefaultTests(t) @@ -39,6 +39,8 @@ func TestConfigFile(t *testing.T) { } func configFileTests(t *testing.T, dir, path string) { + t.Helper() + _, stdErr, err := e2e.Zarf("package", "create", dir, "--confirm") require.NoError(t, err) require.Contains(t, string(stdErr), "This is a zebra and they have stripes") @@ -94,6 +96,7 @@ H4RxbE+FpmsMAUCpdrzvFkc= } func configFileDefaultTests(t *testing.T) { + t.Helper() globalFlags := []string{ "architecture: 509a38f0", @@ -136,7 +139,8 @@ func configFileDefaultTests(t *testing.T) { } // Test remaining default initializers - os.Setenv("ZARF_CONFIG", filepath.Join("src", "test", "zarf-config-test.toml")) + t.Setenv("ZARF_CONFIG", filepath.Join("src", "test", "zarf-config-test.toml")) + defer os.Unsetenv("ZARF_CONFIG") // Test global flags stdOut, _, _ := e2e.Zarf("--help") @@ -161,6 +165,4 @@ func configFileDefaultTests(t *testing.T) { for _, test := range packageDeployFlags { require.Contains(t, string(stdOut), test) } - - os.Unsetenv("ZARF_CONFIG") } diff --git a/src/test/e2e/33_component_webhooks_test.go b/src/test/e2e/33_component_webhooks_test.go index 349e0d0770..cbb8321b08 100644 --- a/src/test/e2e/33_component_webhooks_test.go +++ b/src/test/e2e/33_component_webhooks_test.go @@ -27,12 +27,12 @@ func TestComponentWebhooks(t *testing.T) { gamesPath := fmt.Sprintf("build/zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) stdOut, stdErr, err = e2e.Zarf("package", "deploy", gamesPath, "--confirm") require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Waiting for webhook 'test-webhook' to complete for component 'baseline'") + require.Contains(t, stdErr, "Waiting for webhook \"test-webhook\" to complete for component \"baseline\"") // Ensure package deployments with the '--skip-webhooks' flag do not wait on webhooks to complete. stdOut, stdErr, err = e2e.Zarf("package", "deploy", gamesPath, "--skip-webhooks", "--confirm") require.NoError(t, err, stdOut, stdErr) - require.NotContains(t, stdErr, "Waiting for webhook 'test-webhook' to complete for component 'baseline'") + require.NotContains(t, stdErr, "Waiting for webhook \"test-webhook\" to complete for component \"baseline\"") // Remove the Pepr webhook package. stdOut, stdErr, err = e2e.Zarf("package", "remove", "component-webhooks", "--confirm") diff --git a/src/test/e2e/99_yolo_test.go b/src/test/e2e/99_yolo_test.go index a4044c53a9..bce9ab1450 100644 --- a/src/test/e2e/99_yolo_test.go +++ b/src/test/e2e/99_yolo_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "fmt" "net/http" "testing" @@ -35,7 +36,7 @@ func TestYOLOMode(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("doom") + tunnel, err := c.Connect(context.Background(), "doom") require.NoError(t, err) defer tunnel.Close() diff --git a/src/test/external/ext_in_cluster_test.go b/src/test/external/ext_in_cluster_test.go index a0a4aae7c4..3338b5474f 100644 --- a/src/test/external/ext_in_cluster_test.go +++ b/src/test/external/ext_in_cluster_test.go @@ -81,10 +81,12 @@ func (suite *ExtInClusterTestSuite) Test_0_Mirror() { c, err := cluster.NewCluster() suite.NoError(err) + ctx := context.TODO() + // Check that the registry contains the images we want tunnelReg, err := c.NewTunnel("external-registry", "svc", "external-registry-docker-registry", "", 0, 5000) suite.NoError(err) - _, err = tunnelReg.Connect() + _, err = tunnelReg.Connect(ctx) suite.NoError(err) defer tunnelReg.Close() @@ -101,7 +103,7 @@ func (suite *ExtInClusterTestSuite) Test_0_Mirror() { tunnelGit, err := c.NewTunnel("git-server", "svc", "gitea-http", "", 0, 3000) suite.NoError(err) - _, err = tunnelGit.Connect() + _, err = tunnelGit.Connect(ctx) suite.NoError(err) defer tunnelGit.Close() diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index 7d086bc137..2fd4178cf3 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -174,7 +174,8 @@ func (suite *ExtOutClusterTestSuite) Test_2_AuthToPrivateHelmChart() { URL: chartURL, } repoFile.Add(entry) - utils.WriteYaml(repoPath, repoFile, helpers.ReadWriteUser) + err = utils.WriteYaml(repoPath, repoFile, helpers.ReadWriteUser) + suite.NoError(err) err = exec.CmdWithPrint(zarfBinPath, findImageArgs...) suite.NoError(err, "Unable to find images, helm auth likely failed") @@ -192,7 +193,8 @@ func (suite *ExtOutClusterTestSuite) createHelmChartInGitea(baseURL string, user podinfoTarballPath := filepath.Join(tempDir, fmt.Sprintf("podinfo-%s.tgz", podInfoVersion)) suite.NoError(err, "Unable to package chart") - utils.DownloadToFile(fmt.Sprintf("https://stefanprodan.github.io/podinfo/podinfo-%s.tgz", podInfoVersion), podinfoTarballPath, "") + err = utils.DownloadToFile(fmt.Sprintf("https://stefanprodan.github.io/podinfo/podinfo-%s.tgz", podInfoVersion), podinfoTarballPath, "") + suite.NoError(err) url := fmt.Sprintf("%s/api/packages/%s/helm/api/charts", baseURL, username) file, err := os.Open(podinfoTarballPath) From 04d9e0ed8417b1eb6c08e742adec70227d75cfd9 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 15 May 2024 19:03:38 +0200 Subject: [PATCH 002/132] ci: run revive using golang-lint-ci (#2499) ## Description This replaces revive with golang-ci-lint, and instead runs revive through the aforementioned tool. Additionally it replaces the header check script with the goheader linter. The benefit of this change is that we can enable more linters along with revive in the future. ## Related Issue Part of #2503 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Co-authored-by: razzle --- .github/CONTRIBUTING.md | 7 ++-- .github/workflows/scan-lint.yml | 11 ++---- .golangci.yaml | 62 ++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 24 +++++-------- Makefile | 5 ++- hack/check-spdx-go.sh | 63 --------------------------------- hack/revive.toml | 31 ---------------- src/pkg/utils/bytes.go | 1 + 8 files changed, 77 insertions(+), 127 deletions(-) create mode 100644 .golangci.yaml delete mode 100755 hack/check-spdx-go.sh delete mode 100644 hack/revive.toml diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a931273d47..4dda9e1668 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,11 +23,8 @@ We use [pre-commit](https://pre-commit.com/) to manage our pre-commit hooks. Thi # install hooks pre-commit install -# install goimports -go install golang.org/x/tools/cmd/goimports@latest - -# install revive -go install github.com/mgechev/revive@latest +# install golang-ci-lint +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest ``` Now every time you commit, the hooks will run and format your code, linting can be called via `make lint-go`. diff --git a/.github/workflows/scan-lint.yml b/.github/workflows/scan-lint.yml index fb826ae737..e35d4f7330 100644 --- a/.github/workflows/scan-lint.yml +++ b/.github/workflows/scan-lint.yml @@ -10,12 +10,5 @@ jobs: steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Run Revive Action by pulling pre-built image - uses: docker://morphy/revive-action:v2.5.7@sha256:087d4e61077087755711ab7e9fae3cc899b7bb07ff8f6a30c3dfb240b1620ae8 - with: - config: hack/revive.toml - # Exclude patterns, separated by semicolons (optional) - exclude: "src/cmd/viper.go" - # Path pattern (default: ./...) - path: "./src/..." + - name: Run golangci-lint + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000000..274226cc10 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,62 @@ +run: + timeout: 5m +linters: + disable-all: true + enable: + - gosimple + - govet + - staticcheck + - unused + - revive + - goheader + - goimports + - nolintlint +linters-settings: + govet: + enable-all: true + disable: + - shadow + - fieldalignment + - unusedwrite + nolintlint: + require-specific: true + goheader: + template: |- + SPDX-License-Identifier: Apache-2.0 + SPDX-FileCopyrightText: 2021-Present The Zarf Authors + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id +issues: + # Revive rules that are disabled by default. + include: + - EXC0012 + - EXC0013 + - EXC0014 + - EXC0015 + # Exclude linting code copied from Helm. + exclude-dirs: + - "src/cmd/tools/helm" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38ae99b9bd..93759bdb25 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,24 +30,16 @@ repos: language: script - id: goimports name: goimports - entry: goimports - files: .go$ - args: - - -l - - -w - language: system + entry: golangci-lint run --enable-only goimports --fix + types: [go] + language: golang pass_filenames: true - id: lint - name: revive go lint - entry: revive - args: - - "-config" - - "hack/revive.toml" - - "-exclude" - - "src/cmd/viper.go" - files: .go$ - language: system - pass_filenames: true + name: golangci-lint go lint + entry: golangci-lint run + types: [go] + language: golang + pass_filenames: false - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.14.0 hooks: diff --git a/Makefile b/Makefile index cc82f9d56e..5683962afc 100644 --- a/Makefile +++ b/Makefile @@ -225,6 +225,5 @@ cve-report: ## Create a CVE report for the current project (must `brew install g @test -d ./build || mkdir ./build go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype -o template -t hack/grype.tmpl > build/zarf-known-cves.csv -lint-go: ## Run revive to lint the go code (must `brew install revive` first) - revive -config hack/revive.toml -exclude src/cmd/viper.go -formatter stylish ./src/... - @hack/check-spdx-go.sh src >/dev/null || (echo "SPDX check for go failed, please run 'hack/check-spdx-go.sh src' to see the errors" && exit 1) +lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golang-ci-lint` first) + golangci-lint run diff --git a/hack/check-spdx-go.sh b/hack/check-spdx-go.sh deleted file mode 100755 index 7db63e2ff1..0000000000 --- a/hack/check-spdx-go.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Directory containing the Go files -DIRECTORY="$1" - -# Array of paths to exclude from the check -EXCLUDE_PATHS=( - "src/cmd/tools/helm/repo_update.go" - "src/cmd/tools/helm/repo_remove.go" - "src/cmd/tools/helm/load_plugins.go" - "src/cmd/tools/helm/repo_list.go" - "src/cmd/tools/helm/flags.go" - "src/cmd/tools/helm/repo_add.go" - "src/cmd/tools/helm/dependency.go" - "src/cmd/tools/helm/repo_index.go" - "src/cmd/tools/helm/repo.go" - "src/cmd/tools/helm/dependency_build.go" - "src/cmd/tools/helm/dependency_update.go" - "src/cmd/tools/helm/root.go" -) - -BLACK='\033[0;30m' -RED='\033[0;31m' -RESET='\033[0m' - -# Function to check if a path is in the EXCLUDE_PATHS array -is_excluded() { - local path="$1" - for exclude in "${EXCLUDE_PATHS[@]}"; do - if [[ "$path" == "$exclude"* ]]; then - return 0 # 0 means true/success in shell script - fi - done - return 1 # 1 means false/failure in shell script -} - -# Flag to track if any file meets the condition -found=0 - -# Use process substitution to avoid subshell issue with the 'found' variable -while IFS= read -r file; do - if is_excluded "$file"; then - echo -e "$BLACK$file$RESET" - continue - fi - - # Use `head` to grab the first two lines and compare them directly - firstLine=$(head -n 1 "$file") - secondLine=$(head -n 2 "$file" | tail -n 1) - - # Check if the lines do not match the specified strings - if [[ "$firstLine" != "// SPDX-License-Identifier: Apache-2.0" || "$secondLine" != "// SPDX-FileCopyrightText: 2021-Present The Zarf Authors" ]]; then - echo -e "$RED$file$RESET" - found=1 - fi -done < <(find "$DIRECTORY" -type f -name "*.go") - -# If any file met the condition, exit with status 1 -if [ "$found" -eq 1 ]; then - exit 1 -fi diff --git a/hack/revive.toml b/hack/revive.toml deleted file mode 100644 index 222a780640..0000000000 --- a/hack/revive.toml +++ /dev/null @@ -1,31 +0,0 @@ -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -errorCode = 1 -warningCode = 1 -formatter = "stylish" - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] -[rule.empty-block] -[rule.superfluous-else] -[rule.unused-parameter] -[rule.unreachable-code] -[rule.redefines-builtin-id] diff --git a/src/pkg/utils/bytes.go b/src/pkg/utils/bytes.go index d5fd24e5c6..7ef3ef5c8c 100644 --- a/src/pkg/utils/bytes.go +++ b/src/pkg/utils/bytes.go @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors + // forked from https://www.socketloop.com/tutorials/golang-byte-format-example // Package utils provides generic utility functions. From 75c982683edf955f6df725ffeb408be16f9db9b3 Mon Sep 17 00:00:00 2001 From: schristoff <28318173+schristoff@users.noreply.github.com> Date: Wed, 15 May 2024 15:54:09 -0600 Subject: [PATCH 003/132] feat: update injector away from rouille to axum (#2457) Fixes #592 --------- Signed-off-by: schristoff <28318173+schristoff@users.noreply.github.com> --- .github/actions/k3d/action.yaml | 4 +- .github/workflows/build-rust-injector.yml | 3 +- src/injector/.cargo/config.toml | 5 + src/injector/Cargo.lock | 1033 ++++++++++----------- src/injector/Cargo.toml | 12 +- src/injector/Makefile | 23 + src/injector/README.md | 86 ++ src/injector/src/main.rs | 245 +++-- src/pkg/cluster/injector.go | 11 +- zarf-config.toml | 7 +- 10 files changed, 776 insertions(+), 653 deletions(-) create mode 100644 src/injector/.cargo/config.toml diff --git a/.github/actions/k3d/action.yaml b/.github/actions/k3d/action.yaml index c6bfb5e92a..951caf7756 100644 --- a/.github/actions/k3d/action.yaml +++ b/.github/actions/k3d/action.yaml @@ -7,5 +7,5 @@ runs: - run: "curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash" shell: bash - - run: k3d cluster delete && k3d cluster create --k3s-arg="--disable=traefik@server:0" --image="rancher/k3s:v1.28.4-k3s2" - shell: bash + - run: k3d cluster delete && k3d cluster create --k3s-arg="--disable=traefik@server:0" + shell: bash \ No newline at end of file diff --git a/.github/workflows/build-rust-injector.yml b/.github/workflows/build-rust-injector.yml index 375dc62bb3..270a091fcf 100644 --- a/.github/workflows/build-rust-injector.yml +++ b/.github/workflows/build-rust-injector.yml @@ -27,8 +27,9 @@ jobs: - name: "Build Rust Binary for x86_64 and arm64" run: | + cd src/injector make build-injector-linux - cd src/injector/target + cd target mkdir -p ../dist cp x86_64-unknown-linux-musl/release/zarf-injector ../dist/zarf-injector-amd64 cp aarch64-unknown-linux-musl/release/zarf-injector ../dist/zarf-injector-arm64 diff --git a/src/injector/.cargo/config.toml b/src/injector/.cargo/config.toml new file mode 100644 index 0000000000..d5bd8214f3 --- /dev/null +++ b/src/injector/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "relocation-model=static", "-C", "strip=symbols"] + +[target.aarch64-unknown-linux-musl] +rustflags = ["-C", "relocation-model=static", "-C", "strip=symbols"] diff --git a/src/injector/Cargo.lock b/src/injector/Cargo.lock index 04aa2408f3..4915654980 100644 --- a/src/injector/Cargo.lock +++ b/src/injector/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -9,53 +18,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler32" -version = "1.2.0" +name = "async-trait" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "axum" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] [[package]] -name = "alloc-stdlib" -version = "0.2.2" +name = "axum-core" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ - "alloc-no-stdlib", + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ + "addr2line", + "cc", + "cfg-if", "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "bitflags" version = "1.3.2" @@ -63,56 +105,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.2" +name = "bitflags" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] -name = "buf_redux" -version = "0.8.4" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "memchr", - "safemem", + "generic-array", ] [[package]] -name = "bumpalo" -version = "3.11.1" +name = "bytes" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] name = "cfg-if" @@ -120,122 +137,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" -dependencies = [ - "iana-time-zone", - "num-integer", - "num-traits", - "winapi", -] - -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] -[[package]] -name = "cxx" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.103", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "deflate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" -dependencies = [ - "adler32", - "gzip-header", -] - [[package]] name = "digest" version = "0.10.7" @@ -247,24 +176,25 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "instant", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "filetime" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -277,404 +207,373 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] -name = "generic-array" -version = "0.14.5" +name = "futures-channel" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ - "typenum", - "version_check", + "futures-core", ] [[package]] -name = "getrandom" -version = "0.2.8" +name = "futures-core" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] -name = "glob" -version = "0.3.1" +name = "futures-sink" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] -name = "gzip-header" -version = "1.0.0" +name = "futures-task" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "crc32fast", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "libc", + "typenum", + "version_check", ] [[package]] -name = "hex" -version = "0.4.3" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] -name = "httparse" -version = "1.8.0" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "httpdate" -version = "1.0.2" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "iana-time-zone" -version = "0.1.51" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ - "cxx", - "cxx-build", + "bytes", + "http", ] [[package]] -name = "idna" -version = "0.3.0" +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "instant" -version = "0.1.12" +name = "httparse" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] -name = "itoa" -version = "1.0.4" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "js-sys" -version = "0.3.60" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ - "wasm-bindgen", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", ] [[package]] -name = "libc" -version = "0.2.148" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "link-cplusplus" -version = "1.0.7" +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ - "cc", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", ] [[package]] -name = "log" -version = "0.4.17" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "memchr" -version = "2.5.0" +name = "libc" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] -name = "mime" -version = "0.3.16" +name = "linux-raw-sys" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] -name = "mime_guess" -version = "2.0.4" +name = "log" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "multipart" -version = "0.18.0" +name = "memchr" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand", - "safemem", - "tempfile", - "twoway", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "num-integer" -version = "0.1.45" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "num-traits" -version = "0.2.15" +name = "miniz_oxide" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ - "autocfg", + "adler", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "mio" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "hermit-abi", "libc", + "wasi", + "windows-sys 0.48.0", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "libc", + "memchr", ] [[package]] name = "once_cell" -version = "1.15.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "ppv-lite86" -version = "0.2.16" +name = "pin-project" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "pin-project-internal" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "quote" -version = "1.0.35" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "rand" -version = "0.8.5" +name = "proc-macro2" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "unicode-ident", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "ppv-lite86", - "rand_core", + "proc-macro2", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "getrandom", + "bitflags 1.3.2", ] [[package]] -name = "redox_syscall" -version = "0.2.10" +name = "regex-lite" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rouille" -version = "3.6.2" +name = "rustix" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "base64", - "brotli", - "chrono", - "deflate", - "filetime", - "multipart", - "percent-encoding", - "rand", - "serde", - "serde_derive", - "serde_json", - "sha1_smol", - "threadpool", - "time", - "tiny_http", - "url", + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "safemem" -version = "0.3.3" +name = "rustversion" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] -name = "scratch" -version = "1.0.2" +name = "ryu" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -682,10 +581,26 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.0" +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] [[package]] name = "sha2" @@ -699,27 +614,44 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.103" +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "tar" version = "0.4.40" @@ -732,259 +664,278 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "tokio" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ - "cfg-if", - "fastrand", + "backtrace", "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "tokio-macros" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "threadpool" -version = "1.8.1" +name = "tokio-util" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ - "num_cpus", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "time" -version = "0.3.16" +name = "tower" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "libc", - "num_threads", - "serde", - "time-core", + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "time-core" -version = "0.1.0" +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] -name = "tiny_http" -version = "0.12.0" +name = "tracing" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", "log", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tracing-core" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "tinyvec_macros", + "once_cell", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "twoway" -version = "0.1.8" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "typenum" -version = "1.15.0" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "unicase" -version = "2.6.0" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "version_check", + "windows-targets 0.48.5", ] [[package]] -name = "unicode-bidi" -version = "0.3.8" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] [[package]] -name = "unicode-ident" -version = "1.0.5" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "windows-targets" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "tinyvec", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] -name = "unicode-width" -version = "0.1.10" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "url" -version = "2.3.1" +name = "windows_aarch64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] -name = "version_check" -version = "0.9.4" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows_aarch64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] -name = "wasm-bindgen" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-shared", -] +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" +name = "windows_i686_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] [[package]] name = "zarf-injector" version = "0.5.0" dependencies = [ + "axum", "flate2", "glob", "hex", - "rouille", + "regex-lite", "serde_json", "sha2", "tar", + "tokio", + "tokio-util", ] diff --git a/src/injector/Cargo.toml b/src/injector/Cargo.toml index fb671ca2d5..050f6e314a 100644 --- a/src/injector/Cargo.toml +++ b/src/injector/Cargo.toml @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: 2021-Present The Zarf Authors + [profile.release] opt-level = "z" # Optimize for size. lto = true @@ -8,6 +9,8 @@ codegen-units = 1 panic = "abort" strip = true + + [package] name = "zarf-injector" version = "0.5.0" @@ -20,6 +23,9 @@ glob = "0.3.1" flate2 = "1.0.28" tar = "0.4.40" sha2 = "0.10.8" -hex = "0.4.3" -rouille = "3.6.2" -serde_json = "1.0.113" +hex = {version = "0.4.3", default-features = false} +serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } +axum = {version = "0.7.5", features = ["tokio"]} +tokio = { version = "1.35.0", features = ["fs", "rt"] } +tokio-util = { version = "0.7.10", features = ["io"]} +regex-lite = "0.1.5" diff --git a/src/injector/Makefile b/src/injector/Makefile index a0ba3f467e..19e47d5869 100644 --- a/src/injector/Makefile +++ b/src/injector/Makefile @@ -10,6 +10,29 @@ help: ## Display this help information clean: ## Clean the build directory rm -rf target +cross-injector-linux: cross-injector-amd cross-injector-arm + +cross-injector-amd: + rustup target add x86_64-unknown-linux-musl + test -s x86_64-linux-musl-cross || curl https://zarf-public.s3-us-gov-west-1.amazonaws.com/pipelines/x86_64-linux-musl-cross.tgz | tar -xz + export PATH="$$PWD/x86_64-linux-musl-cross/bin:$$PATH" + export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-cc + + cargo install cross --git https://github.com/cross-rs/cross + + cross build --target x86_64-unknown-linux-musl --release + + +cross-injector-arm: + rustup target add aarch64-unknown-linux-musl + test -s aarch64-linux-musl-cross || curl https://zarf-public.s3-us-gov-west-1.amazonaws.com/pipelines/aarch64-linux-musl-cross.tgz | tar -xz + export PATH="$$PWD/aarch64-linux-musl-cross/bin:$$PATH" + export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-cc + + cross build --target aarch64-unknown-linux-musl --release + + + build-injector-linux: build-injector-linux-amd build-injector-linux-arm ## Build the Zarf injector for AMD64 and ARM64 build-injector-linux-amd: ## Build the Zarf injector for AMD64 diff --git a/src/injector/README.md b/src/injector/README.md index 0310ef0467..56ba9c51f2 100644 --- a/src/injector/README.md +++ b/src/injector/README.md @@ -1,3 +1,4 @@ + # zarf-injector > If using VSCode w/ the official Rust extension, make sure to open a new window in the `src/injector` directory to make `rust-analyzer` happy. @@ -37,6 +38,38 @@ Install [Rust](https://rustup.rs/) and `build-essential`. make build-injector-linux list-sizes ``` +## Building on Debian-based Systems + +Install [Rust](https://rustup.rs/) and `build-essential`. + +```bash +make build-injector-linux list-sizes +``` + +## Building on Apple Silicon + +* Install Cross +* Install Docker & have it running +* Rust must be installed via Rustup (Check `which rustc` if you're unsure) + +``` +cargo install cross --git https://github.com/cross-rs/cross +``` + +Whichever arch. of `musl` used, add to toolchain +``` +rustup toolchain install --force-non-host stable-x86_64-unknown-linux-musl +``` +``` +cross build --target x86_64-unknown-linux-musl --release + +cross build --target aarch64-unknown-linux-musl --release +``` + +This will build into `target/*--unknown-linux-musl/release` + + + ## Checking Binary Size Due to the ConfigMap size limit (1MiB for binary data), we need to make sure the binary is small enough to fit. @@ -54,3 +87,56 @@ Size of Zarf injector binaries: 840k target/x86_64-unknown-linux-musl/release/zarf-injector 713k target/aarch64-unknown-linux-musl/release/zarf-injector ``` + +```sh +$ make build-with-docker +... + +Size of Zarf injector binaries: + +840k target/x86_64-unknown-linux-musl/release/zarf-injector +713k target/aarch64-unknown-linux-musl/release/zarf-injector +``` + +## Testing your injector + +Build your injector by following the steps above, or running one of the following: +``` +make build-injector-linux + +## OR +## works on apple silicon +make cross-injector-linux + +``` + +Point the [zarf-registry/zarf.yaml](../../packages/zarf-registry/zarf.yaml) to +the locally built injector image. + +``` + files: + # Rust Injector Binary + - source: ../../src/injector/target/x86_64-unknown-linux-musl/release/zarf-injector + target: "###ZARF_TEMP###/zarf-injector" + + executable: true + + files: + # Rust Injector Binary + - source: ../../src/injector/target/aarch64-unknown-linux-musl/release/zarf-injector + target: "###ZARF_TEMP###/zarf-injector" + + executable: true +``` + +In Zarf Root Directory, run: +``` +zarf tools clear-cache +make clean +make && make init-package +``` + +If you are running on an Apple Silicon, add the `ARCH` flag: `make init-package ARCH=arm64` + +This builds all artifacts within the `/build` directory. Running `zarf init` would look like: +`.build/zarf-mac-apple init --components git-server -l trace` diff --git a/src/injector/src/main.rs b/src/injector/src/main.rs index cf8526e5bb..e1b9cc3dc5 100644 --- a/src/injector/src/main.rs +++ b/src/injector/src/main.rs @@ -7,16 +7,24 @@ use std::fs::File; use std::io; use std::io::Read; use std::io::Write; -use std::path::{Path, PathBuf}; - +use std::path::PathBuf; + +use axum::{ + body::Body, + extract::Path, + http::StatusCode, + response::{IntoResponse, Response}, + routing::get, + Router, +}; use flate2::read::GzDecoder; use glob::glob; use hex::ToHex; -use rouille::{accept, router, Request, Response}; +use regex_lite::Regex; use serde_json::Value; use sha2::{Digest, Sha256}; use tar::Archive; - +use tokio_util::io::ReaderStream; const OCI_MIME_TYPE: &str = "application/vnd.oci.image.manifest.v1+json"; // Reads the binary contents of a file @@ -93,83 +101,77 @@ fn unpack(sha_sum: &String) { /// index.json - the image index /// blobs/sha256/ - the image layers /// oci-layout - the OCI image layout -fn start_seed_registry() { - let root = PathBuf::from("/zarf-seed"); - println!("Starting seed registry at {} on port 5000", root.display()); - rouille::start_server("0.0.0.0:5000", move |request| { - rouille::log(request, io::stdout(), || { - router!(request, - (GET) (/v2/) => { - // returns empty json w/ Docker-Distribution-Api-Version header set - Response::text("{}") - .with_unique_header("Content-Type", "application/json; charset=utf-8") - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") - .with_additional_header("X-Content-Type-Options", "nosniff") - }, - - _ => { - handle_request(&root, &request) - } - ) - }) - }); +fn start_seed_registry() -> Router { + // The name and reference parameter identify the image + // The reference may include a tag or digest. + Router::new() + .route("/v2/*path", get(handler)) + .route( + "/v2/", + get(|| async { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json; charset=utf-8") + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("X-Content-Type-Options", "nosniff") + .body(Body::empty()) + .unwrap() + }), + ) + .route( + "/v2", + get(|| async { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json; charset=utf-8") + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("X-Content-Type-Options", "nosniff") + .body(Body::empty()) + .unwrap() + }), + ) } -fn handle_request(root: &Path, request: &Request) -> Response { - let url = request.url(); - let url_segments: Vec<_> = url.split("/").collect(); - let url_seg_len = url_segments.len(); - - if url_seg_len >= 4 && url_segments[1] == "v2" { - let tag_index = url_seg_len - 1; - let object_index = url_seg_len - 2; - - let object_type = url_segments[object_index]; - - if object_type == "manifests" { - let tag_or_digest = url_segments[tag_index].to_owned(); - - let namespaced_name = url_segments[2..object_index].join("/"); - - // this route handles (GET) (/v2/**/manifests/) - if request.method() == "GET" { - return handle_get_manifest(&root, &namespaced_name, &tag_or_digest); - // this route handles (HEAD) (/v2/**/manifests/) - } else if request.method() == "HEAD" { - // a normal HEAD response has an empty body, but due to rouille not allowing for an override - // on Content-Length, we respond the same as a GET - return accept!( - request, - OCI_MIME_TYPE => { - handle_get_manifest(&root, &namespaced_name, &tag_or_digest) - }, - "*/*" => Response::empty_406() - ); - } - // this route handles (GET) (/v2/**/blobs/) - } else if object_type == "blobs" && request.method() == "GET" { - let digest = url_segments[tag_index].to_owned(); - return handle_get_digest(&root, &digest); - } +async fn handler(Path(path): Path) -> Response { + println!("request: {}", path); + let path = &path; + let manifest = Regex::new("(.+)/manifests/(.+)").unwrap(); + let blob = Regex::new(".+/([^/]+)").unwrap(); + + if manifest.is_match(path) { + let caps = manifest.captures(path).unwrap(); + let name = caps.get(1).unwrap().as_str().to_string(); + let reference = caps.get(2).unwrap().as_str().to_string(); + handle_get_manifest(name, reference).await + } else if blob.is_match(&path) { + let caps = blob.captures(path).unwrap(); + let tag = caps.get(1).unwrap().as_str().to_string(); + handle_get_digest(tag).await + } else { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("Not Found")) + .unwrap() + .into_response() } - - Response::empty_404() } /// Handles the GET request for the manifest (only returns a OCI manifest regardless of Accept header) -fn handle_get_manifest(root: &Path, name: &String, tag: &String) -> Response { - let index = fs::read_to_string(root.join("index.json")).expect("read index.json"); +async fn handle_get_manifest(name: String, reference: String) -> Response { + let index = fs::read_to_string(PathBuf::from("/zarf-seed").join("index.json")) + .expect("index.json is read"); let json: Value = serde_json::from_str(&index).expect("unable to parse index.json"); - let mut sha_manifest = "".to_owned(); - if tag.starts_with("sha256:") { - sha_manifest = tag.strip_prefix("sha256:").unwrap().to_owned(); + let mut sha_manifest: String = "".to_owned(); + + if reference.starts_with("sha256:") { + sha_manifest = reference.strip_prefix("sha256:").unwrap().to_owned(); } else { for manifest in json["manifests"].as_array().unwrap() { let image_base_name = manifest["annotations"]["org.opencontainers.image.base.name"] .as_str() .unwrap(); - let requested_reference = name.to_owned() + ":" + tag; + let requested_reference = name.to_owned() + ":" + &reference; if requested_reference == image_base_name { sha_manifest = manifest["digest"] .as_str() @@ -180,46 +182,89 @@ fn handle_get_manifest(root: &Path, name: &String, tag: &String) -> Response { } } } - - if sha_manifest != "" { - let file = File::open(&root.join("blobs").join("sha256").join(&sha_manifest)).unwrap(); - Response::from_file(OCI_MIME_TYPE, file) - .with_additional_header( - "Docker-Content-Digest", - format!("sha256:{}", sha_manifest.to_owned()), - ) - .with_additional_header("Etag", format!("sha256:{}", sha_manifest)) - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") + if sha_manifest.is_empty() { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("Not Found")) + .unwrap() + .into_response() } else { - Response::empty_404() + let file_path = PathBuf::from("/zarf-seed") + .to_owned() + .join("blobs") + .join("sha256") + .join(&sha_manifest); + match tokio::fs::File::open(&file_path).await { + Ok(file) => { + let metadata = match file.metadata().await { + Ok(meta) => meta, + Err(_) => { + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("Failed to get file metadata".into()) + .unwrap() + } + }; + let stream = ReaderStream::new(file); + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", OCI_MIME_TYPE) + .header("Content-Length", metadata.len()) + .header( + "Docker-Content-Digest", + format!("sha256:{}", sha_manifest.clone()), + ) + .header("Etag", format!("sha256:{}", sha_manifest)) + .header("Docker-Distribution-Api-Version", "registry/2.0") + .body(Body::from_stream(stream)) + .unwrap() + } + Err(err) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("File not found: {}", err)) + .unwrap() + .into_response(), + } } } /// Handles the GET request for a blob -fn handle_get_digest(root: &Path, digest: &String) -> Response { - let blob_root = root.join("blobs").join("sha256"); - let path = blob_root.join(digest.strip_prefix("sha256:").unwrap()); - - let file = File::open(&path).unwrap(); - Response::from_file("application/octet-stream", file) - .with_additional_header("Docker-Content-Digest", digest.to_owned()) - .with_additional_header("Etag", digest.to_owned()) - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") - .with_additional_header("Cache-Control", "max-age=31536000") +async fn handle_get_digest(tag: String) -> Response { + let blob_root = PathBuf::from("/zarf-seed").join("blobs").join("sha256"); + let path = blob_root.join(tag.strip_prefix("sha256:").unwrap()); + + match tokio::fs::File::open(&path).await { + Ok(file) => { + let stream = ReaderStream::new(file); + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/octet-stream") + .header("Docker-Content-Digest", tag.to_owned()) + .header("Etag", tag.to_owned()) + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("Cache-Control", "max-age=31536000") + .body(Body::from_stream(stream)) + .unwrap() + } + Err(err) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("File not found: {}", err)) + .unwrap() + .into_response(), + } } -fn main() { +#[tokio::main(flavor = "current_thread")] +async fn main() { let args: Vec = env::args().collect(); - match args.len() { - 2 => { - let payload_sha = &args[1]; - unpack(payload_sha); + println!("unpacking: {}", args[1]); + let payload_sha = &args[1]; - start_seed_registry(); - } - _ => { - println!("Usage: {} ", args[0]); - } - } + unpack(payload_sha); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:5000").await.unwrap(); + println!("listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, start_seed_registry()).await.unwrap(); + println!("Usage: {} ", args[1]); } diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index dd2a918f98..264a39648e 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -107,13 +107,17 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) // Make sure the pod is not there first - _ = c.DeletePod(ctx, ZarfNamespaceName, "injector") + err = c.DeletePod(ctx, ZarfNamespaceName, "injector") + if err != nil { + message.Debug("could not delete pod injector:", err) + } // Update the podspec image path and use the first node found + pod, err := c.buildInjectionPod(node[0], image, payloadConfigmaps, sha256sum) if err != nil { // Just debug log the output because failures just result in trying the next image - message.Debug(err) + message.Debug("error making injection pod:", err) continue } @@ -121,7 +125,7 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag pod, err = c.CreatePod(ctx, pod) if err != nil { // Just debug log the output because failures just result in trying the next image - message.Debug(pod, err) + message.Debug("error creating pod in cluster:", pod, err) continue } @@ -265,6 +269,7 @@ func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Im var resp *http.Response var err error err = tunnel.Wrap(func() error { + message.Debug("getting seed registry %v", seedRegistry) resp, err = http.Get(seedRegistry) return err }) diff --git a/zarf-config.toml b/zarf-config.toml index 19cefd4686..eb549dac54 100644 --- a/zarf-config.toml +++ b/zarf-config.toml @@ -5,9 +5,10 @@ agent_image = 'defenseunicorns/zarf/agent' agent_image_tag = 'local' # Tag for the zarf injector binary to use -injector_version = '2024-05-03' -injector_amd64_shasum = 'e5a3d380bac4bf6c68ba18275d6a92bb002e86c116eb364f960d393fd2f44da8' -injector_arm64_shasum = '866b5b1911dd920618be55164c4f95abe05753932ac6d0f2270d26e569d797a2' +injector_version = '2024-05-15' +injector_amd64_shasum = '1b34519ac30daf0e5a4a2f0a0766dbcd0852c0b5364b35576eea4ac9e22d9e82' +injector_arm64_shasum = 'ca20f427f9cf91ff42646a785c4772be5892a6752fa14924c5085b2d0109b008' + # The image reference to use for the registry that Zarf deploys into the cluster registry_image_domain = '' From ae47e9e46d111c1c3fde732e1f9ea16a6ae687a3 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 16 May 2024 13:25:55 +0200 Subject: [PATCH 004/132] refactor: enable testifylint linter (#2504) ## Description Adds testifylint and fixes any issues related to it. ## Related Issue Part of #2503 Depends on #2499 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- .golangci.yaml | 3 +++ src/extensions/bigbang/bigbang_test.go | 23 ++++++++++------------- src/pkg/packager/lint/lint_test.go | 7 +++---- src/test/e2e/00_use_cli_test.go | 2 +- src/test/e2e/05_tarball_test.go | 2 +- src/test/e2e/21_connect_creds_test.go | 8 ++++---- src/test/e2e/51_oci_compose_test.go | 2 ++ src/test/external/ext_out_cluster_test.go | 4 ++++ 8 files changed, 28 insertions(+), 23 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 274226cc10..276faf17ae 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -11,6 +11,7 @@ linters: - goheader - goimports - nolintlint + - testifylint linters-settings: govet: enable-all: true @@ -50,6 +51,8 @@ linters-settings: - name: unused-parameter - name: unreachable-code - name: redefines-builtin-id + testifylint: + enable-all: true issues: # Revive rules that are disabled by default. include: diff --git a/src/extensions/bigbang/bigbang_test.go b/src/extensions/bigbang/bigbang_test.go index 21d09939f4..9d17e49831 100644 --- a/src/extensions/bigbang/bigbang_test.go +++ b/src/extensions/bigbang/bigbang_test.go @@ -12,29 +12,26 @@ import ( func TestRequiredBigBangVersions(t *testing.T) { // Support 1.54.0 and beyond vv, err := isValidVersion("1.54.0") - require.Equal(t, err, nil) - require.Equal(t, vv, true) + require.NoError(t, err) + require.True(t, vv) // Do not support earlier than 1.54.0 vv, err = isValidVersion("1.53.0") - require.Equal(t, err, nil) - require.Equal(t, vv, false) + require.NoError(t, err) + require.False(t, vv) // Support for Big Bang release candidates vv, err = isValidVersion("1.57.0-rc.0") - require.Equal(t, err, nil) - require.Equal(t, vv, true) + require.NoError(t, err) + require.True(t, vv) // Support for Big Bang 2.0.0 vv, err = isValidVersion("2.0.0") - require.Equal(t, err, nil) - require.Equal(t, vv, true) + require.NoError(t, err) + require.True(t, vv) // Fail on non-semantic versions vv, err = isValidVersion("1.57b") - Expected := "Invalid Semantic Version" - if err.Error() != Expected { - t.Errorf("Error actual = %v, and Expected = %v.", err, Expected) - } - require.Equal(t, vv, false) + require.EqualError(t, err, "Invalid Semantic Version") + require.False(t, vv) } diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go index a0afd1a8a9..eba52b7743 100644 --- a/src/pkg/packager/lint/lint_test.go +++ b/src/pkg/packager/lint/lint_test.go @@ -119,7 +119,7 @@ func TestValidateSchema(t *testing.T) { "https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1"}} checkForUnpinnedRepos(&validator, &composer.Node{ZarfComponent: component}) require.Equal(t, unpinnedRepo, validator.findings[0].item) - require.Equal(t, len(validator.findings), 1) + require.Len(t, validator.findings, 1) }) t.Run("Unpinnned image warning", func(t *testing.T) { @@ -133,8 +133,7 @@ func TestValidateSchema(t *testing.T) { checkForUnpinnedImages(&validator, &composer.Node{ZarfComponent: component}) require.Equal(t, unpinnedImage, validator.findings[0].item) require.Equal(t, badImage, validator.findings[1].item) - require.Equal(t, 2, len(validator.findings)) - + require.Len(t, validator.findings, 2) }) t.Run("Unpinnned file warning", func(t *testing.T) { @@ -156,7 +155,7 @@ func TestValidateSchema(t *testing.T) { component := types.ZarfComponent{Files: zarfFiles} checkForUnpinnedFiles(&validator, &composer.Node{ZarfComponent: component}) require.Equal(t, fileURL, validator.findings[0].item) - require.Equal(t, 1, len(validator.findings)) + require.Len(t, validator.findings, 1) }) t.Run("Wrap standalone numbers in bracket", func(t *testing.T) { diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index 6b5a0e387c..e830139086 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -55,7 +55,7 @@ func TestUseCLI(t *testing.T) { // Test `zarf version` version, _, err := e2e.Zarf("version") require.NoError(t, err) - require.NotEqual(t, len(version), 0, "Zarf version should not be an empty string") + require.NotEmpty(t, version, "Zarf version should not be an empty string") version = strings.Trim(version, "\n") // test `zarf version --output=json` diff --git a/src/test/e2e/05_tarball_test.go b/src/test/e2e/05_tarball_test.go index 23eb39a7df..e929d3b420 100644 --- a/src/test/e2e/05_tarball_test.go +++ b/src/test/e2e/05_tarball_test.go @@ -51,7 +51,7 @@ func TestMultiPartPackage(t *testing.T) { require.NoError(t, err) err = json.Unmarshal(part0File, &pkgData) require.NoError(t, err) - require.Equal(t, pkgData.Count, 3) + require.Equal(t, 3, pkgData.Count) fmt.Printf("%#v", pkgData) stdOut, stdErr, err = e2e.Zarf("package", "deploy", deployPath, "--confirm") diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index a66d390f18..f7bc4ce25a 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -53,13 +53,13 @@ func TestConnectAndCreds(t *testing.T) { require.Contains(t, stdOut, "2.8.3") stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/promtail") require.NoError(t, err, stdOut, stdErr) - require.Equal(t, stdOut, "") + require.Empty(t, stdOut) stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/grafana") require.NoError(t, err, stdOut, stdErr) - require.Equal(t, stdOut, "") + require.Empty(t, stdOut) stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/loki") require.NoError(t, err, stdOut, stdErr) - require.Equal(t, stdOut, "") + require.Empty(t, stdOut) } func TestMetrics(t *testing.T) { @@ -96,7 +96,7 @@ func TestMetrics(t *testing.T) { } desiredString := "go_gc_duration_seconds_count" - require.Equal(t, true, strings.Contains(string(body), desiredString)) + require.Contains(t, string(body), desiredString) require.NoError(t, err, resp) require.Equal(t, 200, resp.StatusCode) } diff --git a/src/test/e2e/51_oci_compose_test.go b/src/test/e2e/51_oci_compose_test.go index 052c5a1ee4..7c55e3238d 100644 --- a/src/test/e2e/51_oci_compose_test.go +++ b/src/test/e2e/51_oci_compose_test.go @@ -178,6 +178,8 @@ func (suite *SkeletonSuite) Test_2_FilePaths() { } func (suite *SkeletonSuite) DirOrFileExists(path string) { + suite.T().Helper() + invalid := helpers.InvalidPath(path) suite.Falsef(invalid, "path specified does not exist: %s", path) } diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index 2fd4178cf3..eeef4b94db 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -186,6 +186,8 @@ func (suite *ExtOutClusterTestSuite) Test_2_AuthToPrivateHelmChart() { } func (suite *ExtOutClusterTestSuite) createHelmChartInGitea(baseURL string, username string, password string) { + suite.T().Helper() + tempDir := suite.T().TempDir() podInfoVersion := "6.4.0" podinfoChartPath := filepath.Join("..", "..", "..", "examples", "helm-charts", "chart") @@ -223,6 +225,8 @@ func (suite *ExtOutClusterTestSuite) createHelmChartInGitea(baseURL string, user } func (suite *ExtOutClusterTestSuite) makeGiteaUserPrivate(baseURL string, username string, password string) { + suite.T().Helper() + url := fmt.Sprintf("%s/api/v1/admin/users/%s", baseURL, username) userOption := map[string]interface{}{ From f6fe020e34eed24ea34f0d830dc24e640d04a351 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 16 May 2024 13:45:27 -0500 Subject: [PATCH 005/132] chore: remove rouille CVE from grype ignore (#2515) ## Description #2457 removed rouille as a dependency ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- .grype.yaml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.grype.yaml b/.grype.yaml index dcc070020c..1ed8fe29e9 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -1,6 +1,4 @@ ignore: - # From rouille - The Zarf injector does not expose endpoints that use multipart form data - - vulnerability: GHSA-mc8h-8q98-g5hr - # From helm - This behavior was introduced intentionally, and cannot be removed without breaking backwards compatibility (some users may be relying on these values). + # https://helm.sh/blog/response-cve-2019-25210/ - vulnerability: GHSA-jw44-4f3j-q396 From 5bf7e018525452146ef526dc9e9fdec8b204684e Mon Sep 17 00:00:00 2001 From: Brandt Keller <43887158+brandtkeller@users.noreply.github.com> Date: Fri, 17 May 2024 10:13:48 -0700 Subject: [PATCH 006/132] fix(agent): missing path for pod without labels (#2518) ## Description identify when a pod has no labels (and thus has no `/metadata/labels` for jsonpatch replace operations. use the `add` jsonpath operation to add the labels path with the map[string]string value of `{"zarf-agent": "patched"}` ## Related Issue Fixes #2517 ## Testing Deploy a pod without labels to test: ``` apiVersion: v1 kind: Pod metadata: creationTimestamp: null name: test namespace: test spec: containers: - image: nginx name: test resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {} ``` ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/agent/hooks/pods.go | 10 +++++- src/test/e2e/37_pod_without_labels_test.go | 35 +++++++++++++++++++ .../packages/37-pod-without-labels/pod.yaml | 13 +++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/test/e2e/37_pod_without_labels_test.go create mode 100644 src/test/packages/37-pod-without-labels/pod.yaml diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index 2e90709bfd..a7b6911991 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -99,7 +99,15 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { } // Add a label noting the zarf mutation - patchOperations = append(patchOperations, operations.ReplacePatchOperation("/metadata/labels/zarf-agent", "patched")) + if pod.Labels == nil { + // If the labels path does not exist - create with map[string]string value + patchOperations = append(patchOperations, operations.AddPatchOperation("/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + })) + } else { + patchOperations = append(patchOperations, operations.ReplacePatchOperation("/metadata/labels/zarf-agent", "patched")) + } return &operations.Result{ Allowed: true, diff --git a/src/test/e2e/37_pod_without_labels_test.go b/src/test/e2e/37_pod_without_labels_test.go new file mode 100644 index 0000000000..4d249dd3ee --- /dev/null +++ b/src/test/e2e/37_pod_without_labels_test.go @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package test provides e2e tests for Zarf. +package test + +import ( + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPodWithoutLabels(t *testing.T) { + t.Log("E2E: Pod Without Labels") + e2e.SetupWithCluster(t) + + // Path to pod manifest containing 0 lavbels + buildPath := filepath.Join("src", "test", "packages", "37-pod-without-labels", "pod.yaml") + + // Create the testing namespace + _, _, err := e2e.Kubectl("create", "ns", "pod-label") + require.NoError(t, err) + + // Create the pod without labels + // This is not an image zarf will have in the registry - but the agent was failing to admit on an internal server error before completing admission + _, _, err = e2e.Kubectl("create", "-f", buildPath, "-n", "pod-label") + require.NoError(t, err) + + // Cleanup + _, _, err = e2e.Kubectl("delete", "-f", buildPath, "-n", "pod-label") + require.NoError(t, err) + _, _, err = e2e.Kubectl("delete", "ns", "pod-label") + require.NoError(t, err) +} diff --git a/src/test/packages/37-pod-without-labels/pod.yaml b/src/test/packages/37-pod-without-labels/pod.yaml new file mode 100644 index 0000000000..a1ea2ac32b --- /dev/null +++ b/src/test/packages/37-pod-without-labels/pod.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + creationTimestamp: null + name: test +spec: + containers: + - image: nginx + name: test + resources: {} + dnsPolicy: ClusterFirst + restartPolicy: Always +status: {} \ No newline at end of file From 41c0ad79224e19d86127f4e217697a27d1d136c8 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 20 May 2024 08:22:38 -0400 Subject: [PATCH 007/132] fix: adopt namespace metadata (#2494) ## Description Changes behavior to keep existing metadata on namespaces rather than steamrolling them ## Related Issue Fixes #2489 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Lucas Rodriguez Co-authored-by: Philip Laine --- .pre-commit-config.yaml | 2 +- src/config/config.go | 1 - src/internal/packager/helm/post-render.go | 47 +++++++++++-------- src/pkg/cluster/common.go | 10 +--- src/pkg/cluster/injector.go | 2 +- src/pkg/cluster/namespace.go | 28 +++++++++++ src/pkg/cluster/secrets.go | 9 ++-- src/pkg/cluster/state.go | 6 +-- src/pkg/cluster/zarf.go | 7 +-- src/pkg/k8s/common.go | 11 +++-- src/pkg/k8s/namespace.go | 16 ------- src/pkg/k8s/secrets.go | 2 +- src/pkg/k8s/types.go | 1 - src/pkg/packager/deploy.go | 2 +- src/test/e2e/25_helm_test.go | 7 +++ .../25-manifest-adoption/deployment.yaml | 4 ++ 16 files changed, 91 insertions(+), 64 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 93759bdb25..b77cc86c1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: entry: golangci-lint run --enable-only goimports --fix types: [go] language: golang - pass_filenames: true + pass_filenames: false - id: lint name: golangci-lint go lint entry: golangci-lint run diff --git a/src/config/config.go b/src/config/config.go index 8aedce6abb..f603e317a2 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -29,7 +29,6 @@ const ( ZarfConnectAnnotationDescription = "zarf.dev/connect-description" ZarfConnectAnnotationURL = "zarf.dev/connect-url" - ZarfManagedByLabel = "app.kubernetes.io/managed-by" ZarfCleanupScriptsPath = "/opt/zarf" ZarfPackagePrefix = "zarf-package-" diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 8b99cd14f0..4be00a1b11 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -14,6 +14,7 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" @@ -21,6 +22,8 @@ import ( corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -34,16 +37,27 @@ type renderer struct { func (h *Helm) newRenderer() (*renderer, error) { message.Debugf("helm.NewRenderer()") - namespaces := make(map[string]*corev1.Namespace) - if h.cluster != nil { - namespaces[h.chart.Namespace] = h.cluster.NewZarfManagedNamespace(h.chart.Namespace) + rend := &renderer{ + Helm: h, + connectStrings: types.ConnectStrings{}, + namespaces: map[string]*corev1.Namespace{}, + } + if h.cluster == nil { + return rend, nil } - return &renderer{ - Helm: h, - connectStrings: make(types.ConnectStrings), - namespaces: namespaces, - }, nil + namespace, err := h.cluster.Clientset.CoreV1().Namespaces().Get(context.TODO(), h.chart.Namespace, metav1.GetOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return nil, fmt.Errorf("unable to check for existing namespace %q in cluster: %w", h.chart.Namespace, err) + } + if kerrors.IsNotFound(err) { + rend.namespaces[h.chart.Namespace] = cluster.NewZarfManagedNamespace(h.chart.Namespace) + } else if h.cfg.DeployOpts.AdoptExistingResources { + namespace.Labels = cluster.AdoptZarfManagedLabels(namespace.Labels) + rend.namespaces[h.chart.Namespace] = namespace + } + + return rend, nil } func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { @@ -169,22 +183,15 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti switch rawData.GetKind() { case "Namespace": - var namespace corev1.Namespace + namespace := &corev1.Namespace{} // parse the namespace resource so it can be applied out-of-band by zarf instead of helm to avoid helm ns shenanigans - if err := runtime.DefaultUnstructuredConverter.FromUnstructured(rawData.UnstructuredContent(), &namespace); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(rawData.UnstructuredContent(), namespace); err != nil { message.WarnErrf(err, "could not parse namespace %s", rawData.GetName()) } else { message.Debugf("Matched helm namespace %s for zarf annotation", namespace.Name) - if namespace.Labels == nil { - // Ensure label map exists to avoid nil panic - namespace.Labels = make(map[string]string) - } - // Now track this namespace by zarf - namespace.Labels[config.ZarfManagedByLabel] = "zarf" - namespace.Labels["zarf-helm-release"] = r.chart.ReleaseName - + namespace.Labels = cluster.AdoptZarfManagedLabels(namespace.Labels) // Add it to the stack - r.namespaces[namespace.Name] = &namespace + r.namespaces[namespace.Name] = namespace } // skip so we can strip namespaces from helm's brain continue @@ -209,7 +216,7 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti namespace := rawData.GetNamespace() if _, exists := r.namespaces[namespace]; !exists && namespace != "" { // if this is the first time seeing this ns, we need to track that to create it as well - r.namespaces[namespace] = r.cluster.NewZarfManagedNamespace(namespace) + r.namespaces[namespace] = cluster.NewZarfManagedNamespace(namespace) } // If we have been asked to adopt existing resources, process those now as well diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go index 8b2153999b..e0a295cd3c 100644 --- a/src/pkg/cluster/common.go +++ b/src/pkg/cluster/common.go @@ -8,7 +8,6 @@ import ( "context" "time" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -21,13 +20,8 @@ type Cluster struct { const ( // DefaultTimeout is the default time to wait for a cluster to be ready. DefaultTimeout = 30 * time.Second - agentLabel = "zarf.dev/agent" ) -var labels = k8s.Labels{ - config.ZarfManagedByLabel: "zarf", -} - // NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. func NewClusterWithWait(ctx context.Context) (*Cluster, error) { spinner := message.NewProgressSpinner("Waiting for cluster connection") @@ -36,7 +30,7 @@ func NewClusterWithWait(ctx context.Context) (*Cluster, error) { c := &Cluster{} var err error - c.K8s, err = k8s.New(message.Debugf, labels) + c.K8s, err = k8s.New(message.Debugf) if err != nil { return nil, err } @@ -56,7 +50,7 @@ func NewCluster() (*Cluster, error) { c := &Cluster{} var err error - c.K8s, err = k8s.New(message.Debugf, labels) + c.K8s, err = k8s.New(message.Debugf) if err != nil { return nil, err } diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 264a39648e..3abd010b8f 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -330,7 +330,7 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri pod.Labels["app"] = "zarf-injector" // Ensure zarf agent doesn't break the injector on future runs - pod.Labels[agentLabel] = "ignore" + pod.Labels[k8s.AgentLabel] = "ignore" // Bind the pod to the node the image was found on pod.Spec.NodeName = node diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index a7209936b3..56a8566834 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -7,7 +7,10 @@ package cluster import ( "context" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster. @@ -17,3 +20,28 @@ func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { return c.DeleteNamespace(ctx, ZarfNamespaceName) } + +// NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels +func NewZarfManagedNamespace(name string) *corev1.Namespace { + namespace := &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Namespace", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + } + namespace.Labels = AdoptZarfManagedLabels(namespace.Labels) + return namespace +} + +// AdoptZarfManagedLabels adds & deletes the necessary labels that signal to Zarf it should manage a namespace +func AdoptZarfManagedLabels(labels map[string]string) map[string]string { + if labels == nil { + labels = make(map[string]string) + } + labels[k8s.ZarfManagedByLabel] = "zarf" + delete(labels, k8s.AgentLabel) + return labels +} diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 17183d5e62..4b58d841d0 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -13,6 +13,7 @@ import ( corev1 "k8s.io/api/core/v1" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -89,8 +90,8 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentRegistrySecret.Labels[config.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[agentLabel] != "skip" && namespace.Labels[agentLabel] != "ignore") { + if currentRegistrySecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || + (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) // Create the secret @@ -123,8 +124,8 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentGitSecret.Labels[config.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[agentLabel] != "skip" && namespace.Labels[agentLabel] != "ignore") { + if currentGitSecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || + (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed git secret for namespace: '%s'", namespace.Name) // Create the secret diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index a2585c4361..2d51aa38d4 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -92,7 +92,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO namespace.Labels = make(map[string]string) } // This label will tell the Zarf Agent to ignore this namespace. - namespace.Labels[agentLabel] = "ignore" + namespace.Labels[k8s.AgentLabel] = "ignore" namespaceCopy := namespace if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it. @@ -102,7 +102,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") - zarfNamespace := c.NewZarfManagedNamespace(ZarfNamespaceName) + zarfNamespace := NewZarfManagedNamespace(ZarfNamespaceName) if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { return fmt.Errorf("unable to create the zarf namespace: %w", err) } @@ -244,7 +244,7 @@ func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) err Name: ZarfStateSecretName, Namespace: ZarfNamespaceName, Labels: map[string]string{ - config.ZarfManagedByLabel: "zarf", + k8s.ZarfManagedByLabel: "zarf", }, }, Type: corev1.SecretTypeOpaque, diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 3522dde6b5..4e558c6f67 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -12,6 +12,7 @@ import ( "time" "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" autoscalingV2 "k8s.io/api/autoscaling/v2" @@ -68,16 +69,16 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { deleteOptions := metav1.DeleteOptions{} listOptions := metav1.ListOptions{ - LabelSelector: config.ZarfManagedByLabel + "=zarf", + LabelSelector: k8s.ZarfManagedByLabel + "=zarf", } if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { for _, namespace := range namespaces.Items { - if _, ok := namespace.Labels[agentLabel]; ok { + if _, ok := namespace.Labels[k8s.AgentLabel]; ok { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) - delete(namespace.Labels, agentLabel) + delete(namespace.Labels, k8s.AgentLabel) namespaceCopy := namespace if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index 44027f4492..66434f3caa 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -21,11 +21,15 @@ import ( "k8s.io/client-go/tools/clientcmd" ) -// cannot import config.ZarfManagedByLabel due to import cycle -const zarfManagedByLabel = "app.kubernetes.io/managed-by" +const ( + // ZarfManagedByLabel is used to denote Zarf manages the lifecycle of a resource + ZarfManagedByLabel = "app.kubernetes.io/managed-by" + // AgentLabel is used to give instructions to the Zarf agent + AgentLabel = "zarf.dev/agent" +) // New creates a new K8s client. -func New(logger Log, defaultLabels Labels) (*K8s, error) { +func New(logger Log) (*K8s, error) { klog.SetLogger(funcr.New(func(_, args string) { logger(args) }, funcr.Options{})) @@ -39,7 +43,6 @@ func New(logger Log, defaultLabels Labels) (*K8s, error) { RestConfig: config, Clientset: clientset, Log: logger, - Labels: defaultLabels, }, nil } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go index 3a63b1ac52..2f1d43d019 100644 --- a/src/pkg/k8s/namespace.go +++ b/src/pkg/k8s/namespace.go @@ -67,22 +67,6 @@ func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { } } -// NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels -func (k *K8s) NewZarfManagedNamespace(name string) *corev1.Namespace { - return &corev1.Namespace{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Namespace", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Labels: map[string]string{ - zarfManagedByLabel: "zarf", - }, - }, - } -} - // IsInitialNamespace returns true if the given namespace name is an initial k8s namespace: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces func (k *K8s) IsInitialNamespace(name string) bool { if name == "default" { diff --git a/src/pkg/k8s/secrets.go b/src/pkg/k8s/secrets.go index d391b97771..82b9ef52de 100644 --- a/src/pkg/k8s/secrets.go +++ b/src/pkg/k8s/secrets.go @@ -36,7 +36,7 @@ func (k *K8s) GenerateSecret(namespace, name string, secretType corev1.SecretTyp Name: name, Namespace: namespace, Labels: map[string]string{ - zarfManagedByLabel: "zarf", + ZarfManagedByLabel: "zarf", }, }, Type: secretType, diff --git a/src/pkg/k8s/types.go b/src/pkg/k8s/types.go index 2326edbc98..d52ea6389b 100644 --- a/src/pkg/k8s/types.go +++ b/src/pkg/k8s/types.go @@ -21,7 +21,6 @@ type K8s struct { Clientset kubernetes.Interface RestConfig *rest.Config Log Log - Labels Labels } // PodLookup is a struct for specifying a pod to target for data injection or lookups. diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 039a63cb61..a7c516fb43 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -448,7 +448,7 @@ func (p *Packager) setupState(ctx context.Context) (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") - zarfNamespace := p.cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) + zarfNamespace := cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } diff --git a/src/test/e2e/25_helm_test.go b/src/test/e2e/25_helm_test.go index e09d6cf47f..7c643288c0 100644 --- a/src/test/e2e/25_helm_test.go +++ b/src/test/e2e/25_helm_test.go @@ -189,6 +189,13 @@ func testHelmAdoption(t *testing.T) { require.NoError(t, err) require.Contains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866") + existingLabel, _, err := e2e.Kubectl("get", "ns", "dos-games", "-o=jsonpath={.metadata.labels.keep-this}") + require.Equal(t, "label", existingLabel) + require.NoError(t, err) + existingAnnotation, _, err := e2e.Kubectl("get", "ns", "dos-games", "-o=jsonpath={.metadata.annotations.keep-this}") + require.Equal(t, "annotation", existingAnnotation) + require.NoError(t, err) + // Remove the package. stdOut, stdErr, err = e2e.Zarf("package", "remove", "dos-games", "--confirm") require.NoError(t, err, stdOut, stdErr) diff --git a/src/test/packages/25-manifest-adoption/deployment.yaml b/src/test/packages/25-manifest-adoption/deployment.yaml index c5779b20f0..9cd8c5b754 100644 --- a/src/test/packages/25-manifest-adoption/deployment.yaml +++ b/src/test/packages/25-manifest-adoption/deployment.yaml @@ -2,6 +2,10 @@ apiVersion: v1 kind: Namespace metadata: name: dos-games + labels: + keep-this: label + annotations: + keep-this: annotation --- # This is a normal deployment manifest for dos-games that should be "adopted" by Helm/Zarf apiVersion: apps/v1 From 153726043f53baec00c4c72a75ef6936846cb02f Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 20 May 2024 15:31:28 +0200 Subject: [PATCH 008/132] refactor: enable ineffassign linter (#2500) ## Description This change enables the ineffassign linter and fixes the only complaint by refactoring the select state filter. ## Related Issue Part of #2503 Depends on #2499 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- .golangci.yaml | 1 + src/pkg/packager/filters/select.go | 18 ++++-------------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 276faf17ae..cd775b13e2 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -5,6 +5,7 @@ linters: enable: - gosimple - govet + - ineffassign - staticcheck - unused - revive diff --git a/src/pkg/packager/filters/select.go b/src/pkg/packager/filters/select.go index 457643a3c6..2927fb827c 100644 --- a/src/pkg/packager/filters/select.go +++ b/src/pkg/packager/filters/select.go @@ -26,26 +26,16 @@ type selectStateFilter struct { // Apply applies the filter. func (f *selectStateFilter) Apply(pkg types.ZarfPackage) ([]types.ZarfComponent, error) { isPartial := len(f.requestedComponents) > 0 && f.requestedComponents[0] != "" - result := []types.ZarfComponent{} - for _, component := range pkg.Components { - selectState := unknown - + selectState := included if isPartial { selectState, _ = includedOrExcluded(component.Name, f.requestedComponents) - - if selectState == excluded { - continue - } - } else { - selectState = included } - - if selectState == included { - result = append(result, component) + if selectState != included { + continue } + result = append(result, component) } - return result, nil } From 514e82e49a913c22320ad99c6ec72e8d88a4683e Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 20 May 2024 11:48:33 -0400 Subject: [PATCH 009/132] test: cluster getDeployedPackages (#2523) ## Description Adds test for GetDeployedPackage and GetDeployedZarfPackages ## Related Issue Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/cmd/package.go | 10 +++---- src/cmd/tools/crane.go | 4 +-- src/pkg/cluster/zarf.go | 32 ++++++++++----------- src/pkg/cluster/zarf_test.go | 56 ++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/cmd/package.go b/src/cmd/package.go index e8817bf60b..a62f11e1c7 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -133,9 +133,9 @@ var packageListCmd = &cobra.Command{ Short: lang.CmdPackageListShort, Run: func(cmd *cobra.Command, _ []string) { ctx := cmd.Context() - deployedZarfPackages, errs := common.NewClusterOrDie(ctx).GetDeployedZarfPackages(ctx) - if len(errs) > 0 && len(deployedZarfPackages) == 0 { - message.Fatalf(errs, lang.CmdPackageListNoPackageWarn) + deployedZarfPackages, err := common.NewClusterOrDie(ctx).GetDeployedZarfPackages(ctx) + if err != nil && len(deployedZarfPackages) == 0 { + message.Fatalf(err, lang.CmdPackageListNoPackageWarn) } // Populate a matrix of all the deployed packages @@ -157,8 +157,8 @@ var packageListCmd = &cobra.Command{ message.Table(header, packageData) // Print out any unmarshalling errors - if len(errs) > 0 { - message.Fatalf(errs, lang.CmdPackageListUnmarshalErr) + if err != nil { + message.Fatalf(err, lang.CmdPackageListUnmarshalErr) } }, } diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index b501aeac7a..3b750964d3 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -227,8 +227,8 @@ func pruneImages(cmd *cobra.Command, _ []string) error { return err } - zarfPackages, errs := c.GetDeployedZarfPackages(ctx) - if len(errs) > 0 { + zarfPackages, err := c.GetDeployedZarfPackages(ctx) + if err != nil { return lang.ErrUnableToGetPackages } diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 4e558c6f67..4d01891f9d 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -7,6 +7,7 @@ package cluster import ( "context" "encoding/json" + "errors" "fmt" "strings" "time" @@ -23,31 +24,30 @@ import ( // GetDeployedZarfPackages gets metadata information about packages that have been deployed to the cluster. // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. // Returns a list of DeployedPackage structs and a list of errors. -func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.DeployedPackage, []error) { - var deployedPackages = []types.DeployedPackage{} - var errorList []error +func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.DeployedPackage, error) { // Get the secrets that describe the deployed packages secrets, err := c.GetSecretsWithLabel(ctx, ZarfNamespaceName, ZarfPackageInfoLabel) if err != nil { - return deployedPackages, append(errorList, err) + return nil, err } - // Process the k8s secret into our internal structs + errs := []error{} + deployedPackages := []types.DeployedPackage{} for _, secret := range secrets.Items { - if strings.HasPrefix(secret.Name, config.ZarfPackagePrefix) { - var deployedPackage types.DeployedPackage - err := json.Unmarshal(secret.Data["data"], &deployedPackage) - // add the error to the error list - if err != nil { - errorList = append(errorList, fmt.Errorf("unable to unmarshal the secret %s/%s", secret.Namespace, secret.Name)) - } else { - deployedPackages = append(deployedPackages, deployedPackage) - } + if !strings.HasPrefix(secret.Name, config.ZarfPackagePrefix) { + continue + } + var deployedPackage types.DeployedPackage + // Process the k8s secret into our internal structs + err := json.Unmarshal(secret.Data["data"], &deployedPackage) + if err != nil { + errs = append(errs, fmt.Errorf("unable to unmarshal the secret %s/%s", secret.Namespace, secret.Name)) + continue } + deployedPackages = append(deployedPackages, deployedPackage) } - // TODO: If we move this function out of `internal` we should return a more standard singular error. - return deployedPackages, errorList + return deployedPackages, errors.Join(errs...) } // GetDeployedPackage gets the metadata information about the package name provided (if it exists in the cluster). diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index 69136a530f..b9451f3e72 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -5,10 +5,18 @@ package cluster import ( + "context" + "encoding/json" + "strings" "testing" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" ) // TestPackageSecretNeedsWait verifies that Zarf waits for webhooks to complete correctly. @@ -191,3 +199,51 @@ func TestPackageSecretNeedsWait(t *testing.T) { }) } } + +func TestGetDeployedPackage(t *testing.T) { + t.Parallel() + ctx := context.Background() + c := &Cluster{&k8s.K8s{Clientset: fake.NewSimpleClientset()}} + + packages := []types.DeployedPackage{ + {Name: "package1"}, + {Name: "package2"}, + } + + for _, p := range packages { + b, err := json.Marshal(p) + require.NoError(t, err) + data := map[string][]byte{ + "data": b, + } + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.Join([]string{config.ZarfPackagePrefix, p.Name}, ""), + Namespace: "zarf", + Labels: map[string]string{ + ZarfPackageInfoLabel: p.Name, + }, + }, + Data: data, + } + c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{}) + actual, err := c.GetDeployedPackage(ctx, p.Name) + require.NoError(t, err) + require.Equal(t, p, *actual) + } + + nonPackageSecret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "hello-world", + Namespace: "zarf", + Labels: map[string]string{ + ZarfPackageInfoLabel: "whatever", + }, + }, + } + c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &nonPackageSecret, metav1.CreateOptions{}) + + actualList, err := c.GetDeployedZarfPackages(ctx) + require.NoError(t, err) + require.ElementsMatch(t, packages, actualList) +} From 29d3ee39dfcbe173405f6e0010cd0dfb9468cd6e Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 20 May 2024 18:17:24 +0200 Subject: [PATCH 010/132] test: add unit tests for merge zarf state (#2522) ## Description Add tests for merge zarf state. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/tools/zarf.go | 12 +- src/pkg/cluster/state.go | 8 +- src/pkg/cluster/state_test.go | 317 ++++++++++++++++++++++++++++++++++ 3 files changed, 331 insertions(+), 6 deletions(-) create mode 100644 src/pkg/cluster/state_test.go diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 20655089bc..bab8eb17a5 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -7,25 +7,27 @@ package tools import ( "fmt" "os" - "slices" "github.com/AlecAivazis/survey/v2" + "github.com/sigstore/cosign/v2/pkg/cosign" + "github.com/spf13/cobra" + "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/pkg/oci" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/internal/packager/template" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/pkg/zoci" "github.com/defenseunicorns/zarf/src/types" - "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/spf13/cobra" ) var subAltNames []string @@ -92,8 +94,8 @@ var updateCredsCmd = &cobra.Command{ // If no distro the zarf secret did not load properly message.Fatalf(nil, lang.ErrLoadState) } - var newState *types.ZarfState - if newState, err = c.MergeZarfState(oldState, updateCredsInitOpts, args); err != nil { + newState, err := cluster.MergeZarfState(oldState, updateCredsInitOpts, args) + if err != nil { message.Fatal(err, lang.CmdToolsUpdateCredsUnableUpdateCreds) } diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 2d51aa38d4..a0414f8793 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -260,12 +260,14 @@ func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) err } // MergeZarfState merges init options for provided services into the provided state to create a new state struct -func (c *Cluster) MergeZarfState(oldState *types.ZarfState, initOptions types.ZarfInitOptions, services []string) (*types.ZarfState, error) { +func MergeZarfState(oldState *types.ZarfState, initOptions types.ZarfInitOptions, services []string) (*types.ZarfState, error) { newState := *oldState var err error if slices.Contains(services, message.RegistryKey) { + // TODO: Replace use of reflections with explicit setting newState.RegistryInfo = helpers.MergeNonZero(newState.RegistryInfo, initOptions.RegistryInfo) // Set the state of the internal registry if it has changed + // TODO: Internal registry should be a function of the address and not a property. if newState.RegistryInfo.Address == fmt.Sprintf("%s:%d", helpers.IPV4Localhost, newState.RegistryInfo.NodePort) { newState.RegistryInfo.InternalRegistry = true } else { @@ -285,9 +287,11 @@ func (c *Cluster) MergeZarfState(oldState *types.ZarfState, initOptions types.Za } } if slices.Contains(services, message.GitKey) { + // TODO: Replace use of reflections with explicit setting newState.GitServer = helpers.MergeNonZero(newState.GitServer, initOptions.GitServer) // Set the state of the internal git server if it has changed + // TODO: Internal server should be a function of the address and not a property. if newState.GitServer.Address == types.ZarfInClusterGitServiceURL { newState.GitServer.InternalServer = true } else { @@ -307,9 +311,11 @@ func (c *Cluster) MergeZarfState(oldState *types.ZarfState, initOptions types.Za } } if slices.Contains(services, message.ArtifactKey) { + // TODO: Replace use of reflections with explicit setting newState.ArtifactServer = helpers.MergeNonZero(newState.ArtifactServer, initOptions.ArtifactServer) // Set the state of the internal artifact server if it has changed + // TODO: Internal server should be a function of the address and not a property. if newState.ArtifactServer.Address == types.ZarfInClusterArtifactServiceURL { newState.ArtifactServer.InternalServer = true } else { diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go new file mode 100644 index 0000000000..72f10cd873 --- /dev/null +++ b/src/pkg/cluster/state_test.go @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package cluster contains Zarf-specific cluster management functions. +package cluster + +import ( + "fmt" + "testing" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/pki" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +// TODO: Change password gen method to make testing possible. +func TestMergeZarfStateRegistry(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initRegistry types.RegistryInfo + oldRegistry types.RegistryInfo + expectedRegistry types.RegistryInfo + }{ + { + name: "username is unmodified", + oldRegistry: types.RegistryInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + }, + expectedRegistry: types.RegistryInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + }, + }, + { + name: "internal server auto generate", + oldRegistry: types.RegistryInfo{ + Address: fmt.Sprintf("%s:%d", helpers.IPV4Localhost, 1), + NodePort: 1, + InternalRegistry: true, + }, + expectedRegistry: types.RegistryInfo{ + Address: fmt.Sprintf("%s:%d", helpers.IPV4Localhost, 1), + NodePort: 1, + InternalRegistry: true, + }, + }, + { + name: "external server", + oldRegistry: types.RegistryInfo{ + Address: "example.com", + InternalRegistry: false, + PushPassword: "push", + PullPassword: "pull", + }, + expectedRegistry: types.RegistryInfo{ + Address: "example.com", + InternalRegistry: false, + PushPassword: "push", + PullPassword: "pull", + }, + }, + { + name: "init options merged", + initRegistry: types.RegistryInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + Address: "address", + NodePort: 1, + InternalRegistry: false, + Secret: "secret", + }, + expectedRegistry: types.RegistryInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + Address: "address", + NodePort: 1, + InternalRegistry: false, + Secret: "secret", + }, + }, + { + name: "init options not merged", + expectedRegistry: types.RegistryInfo{ + PushUsername: "", + PullUsername: "", + Address: "", + NodePort: 0, + InternalRegistry: false, + Secret: "", + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + oldState := &types.ZarfState{ + RegistryInfo: tt.oldRegistry, + } + newState, err := MergeZarfState(oldState, types.ZarfInitOptions{RegistryInfo: tt.initRegistry}, []string{message.RegistryKey}) + require.NoError(t, err) + require.Equal(t, tt.expectedRegistry.PushUsername, newState.RegistryInfo.PushUsername) + require.Equal(t, tt.expectedRegistry.PullUsername, newState.RegistryInfo.PullUsername) + require.Equal(t, tt.expectedRegistry.Address, newState.RegistryInfo.Address) + require.Equal(t, tt.expectedRegistry.NodePort, newState.RegistryInfo.NodePort) + require.Equal(t, tt.expectedRegistry.InternalRegistry, newState.RegistryInfo.InternalRegistry) + require.Equal(t, tt.expectedRegistry.Secret, newState.RegistryInfo.Secret) + }) + } +} + +// TODO: Change password gen method to make testing possible. +func TestMergeZarfStateGit(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initGitServer types.GitServerInfo + oldGitServer types.GitServerInfo + expectedGitServer types.GitServerInfo + }{ + { + name: "username is unmodified", + oldGitServer: types.GitServerInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + }, + expectedGitServer: types.GitServerInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + }, + }, + { + name: "internal server auto generate", + oldGitServer: types.GitServerInfo{ + Address: types.ZarfInClusterGitServiceURL, + InternalServer: true, + }, + expectedGitServer: types.GitServerInfo{ + Address: types.ZarfInClusterGitServiceURL, + InternalServer: true, + }, + }, + { + name: "external server", + oldGitServer: types.GitServerInfo{ + Address: "example.com", + InternalServer: false, + PushPassword: "push", + PullPassword: "pull", + }, + expectedGitServer: types.GitServerInfo{ + Address: "example.com", + InternalServer: false, + PushPassword: "push", + PullPassword: "pull", + }, + }, + { + name: "init options merged", + initGitServer: types.GitServerInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + Address: "address", + InternalServer: false, + }, + expectedGitServer: types.GitServerInfo{ + PushUsername: "push-user", + PullUsername: "pull-user", + Address: "address", + InternalServer: false, + }, + }, + { + name: "empty init options not merged", + expectedGitServer: types.GitServerInfo{ + PushUsername: "", + PullUsername: "", + Address: "", + InternalServer: false, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + oldState := &types.ZarfState{ + GitServer: tt.oldGitServer, + } + newState, err := MergeZarfState(oldState, types.ZarfInitOptions{GitServer: tt.initGitServer}, []string{message.GitKey}) + require.NoError(t, err) + require.Equal(t, tt.expectedGitServer.PushUsername, newState.GitServer.PushUsername) + require.Equal(t, tt.expectedGitServer.PullUsername, newState.GitServer.PullUsername) + require.Equal(t, tt.expectedGitServer.Address, newState.GitServer.Address) + require.Equal(t, tt.expectedGitServer.InternalServer, newState.GitServer.InternalServer) + }) + } +} + +func TestMergeZarfStateArtifact(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + initArtifactServer types.ArtifactServerInfo + oldArtifactServer types.ArtifactServerInfo + expectedArtifactServer types.ArtifactServerInfo + }{ + { + name: "username is unmodified", + oldArtifactServer: types.ArtifactServerInfo{ + PushUsername: "push-user", + }, + expectedArtifactServer: types.ArtifactServerInfo{ + PushUsername: "push-user", + }, + }, + { + name: "old state is internal server auto generate push token", + oldArtifactServer: types.ArtifactServerInfo{ + PushToken: "foobar", + Address: types.ZarfInClusterArtifactServiceURL, + InternalServer: true, + }, + expectedArtifactServer: types.ArtifactServerInfo{ + PushToken: "", + Address: types.ZarfInClusterArtifactServiceURL, + InternalServer: true, + }, + }, + { + name: "old state is not internal server auto generate push token but init options does not match", + initArtifactServer: types.ArtifactServerInfo{ + PushToken: "hello world", + }, + oldArtifactServer: types.ArtifactServerInfo{ + PushToken: "foobar", + Address: types.ZarfInClusterArtifactServiceURL, + InternalServer: false, + }, + expectedArtifactServer: types.ArtifactServerInfo{ + PushToken: "hello world", + Address: types.ZarfInClusterArtifactServiceURL, + InternalServer: true, + }, + }, + { + name: "external server same push token", + oldArtifactServer: types.ArtifactServerInfo{ + PushToken: "foobar", + Address: "http://example.com", + InternalServer: false, + }, + expectedArtifactServer: types.ArtifactServerInfo{ + PushToken: "foobar", + Address: "http://example.com", + InternalServer: false, + }, + }, + { + name: "init options merged", + initArtifactServer: types.ArtifactServerInfo{ + PushUsername: "user", + PushToken: "token", + Address: "address", + InternalServer: false, + }, + expectedArtifactServer: types.ArtifactServerInfo{ + PushUsername: "user", + PushToken: "token", + Address: "address", + InternalServer: false, + }, + }, + { + name: "empty init options not merged", + expectedArtifactServer: types.ArtifactServerInfo{ + PushUsername: "", + PushToken: "", + Address: "", + InternalServer: false, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + oldState := &types.ZarfState{ + ArtifactServer: tt.oldArtifactServer, + } + newState, err := MergeZarfState(oldState, types.ZarfInitOptions{ArtifactServer: tt.initArtifactServer}, []string{message.ArtifactKey}) + require.NoError(t, err) + require.Equal(t, tt.expectedArtifactServer, newState.ArtifactServer) + }) + } +} + +func TestMergeZarfStateAgent(t *testing.T) { + t.Parallel() + + oldState := &types.ZarfState{ + AgentTLS: pki.GeneratePKI("example.com"), + } + newState, err := MergeZarfState(oldState, types.ZarfInitOptions{}, []string{message.AgentKey}) + require.NoError(t, err) + require.NotEqual(t, oldState.AgentTLS, newState.AgentTLS) +} From 097a6ee41bf0e9395f9d0ca6b1cfd5b6b15cb2b3 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Tue, 21 May 2024 13:32:17 -0400 Subject: [PATCH 011/132] test: pod agent unit tests (#2526) ## Description This also changes how we the agent gets secrets from the cluster. Instead of mounting secrets in the deployment and reading from a file the agent will instead read the secret directly from the cluster. This is done for simplicity, and easier testing. Additionally, the agent will need to read from the cluster in #1974 as well so this helps prepare for that change. This only changes and tests pods to keep the scope of the PR slim. This means temporarily the agent will both read from mounted file secrets and regular kubernetes secrets depending on the hook, but this will be changed shortly in future PRs. ## Related Issue Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Lucas Rodriguez --- packages/zarf-agent/manifests/deployment.yaml | 1 + packages/zarf-agent/manifests/role.yaml | 12 ++ .../zarf-agent/manifests/rolebinding.yaml | 13 ++ .../zarf-agent/manifests/serviceaccount.yaml | 5 + packages/zarf-agent/zarf.yaml | 3 + src/config/lang/english.go | 2 +- src/internal/agent/hooks/pods.go | 39 +++-- src/internal/agent/hooks/pods_test.go | 149 ++++++++++++++++++ src/internal/agent/hooks/utils_test.go | 70 ++++++++ .../{admission.go => admission/handler.go} | 24 +-- src/internal/agent/http/server.go | 16 +- src/pkg/cluster/zarf_test.go | 7 +- 12 files changed, 305 insertions(+), 36 deletions(-) create mode 100644 packages/zarf-agent/manifests/role.yaml create mode 100644 packages/zarf-agent/manifests/rolebinding.yaml create mode 100644 packages/zarf-agent/manifests/serviceaccount.yaml create mode 100644 src/internal/agent/hooks/pods_test.go create mode 100644 src/internal/agent/hooks/utils_test.go rename src/internal/agent/http/{admission.go => admission/handler.go} (80%) diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index 2d4767ba56..a4113812f9 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -20,6 +20,7 @@ spec: imagePullSecrets: - name: private-registry priorityClassName: system-node-critical + serviceAccountName: zarf containers: - name: server image: "###ZARF_REGISTRY###/###ZARF_CONST_AGENT_IMAGE###:###ZARF_CONST_AGENT_IMAGE_TAG###" diff --git a/packages/zarf-agent/manifests/role.yaml b/packages/zarf-agent/manifests/role.yaml new file mode 100644 index 0000000000..c310ca19b1 --- /dev/null +++ b/packages/zarf-agent/manifests/role.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: zarf-agent + namespace: zarf +rules: +- apiGroups: + - "" + resources: + - secrets + verbs: + - get diff --git a/packages/zarf-agent/manifests/rolebinding.yaml b/packages/zarf-agent/manifests/rolebinding.yaml new file mode 100644 index 0000000000..c46cae1b72 --- /dev/null +++ b/packages/zarf-agent/manifests/rolebinding.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: zarf-agent-binding + namespace: zarf +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: zarf-agent +subjects: +- kind: ServiceAccount + name: zarf + namespace: zarf diff --git a/packages/zarf-agent/manifests/serviceaccount.yaml b/packages/zarf-agent/manifests/serviceaccount.yaml new file mode 100644 index 0000000000..2f9b060094 --- /dev/null +++ b/packages/zarf-agent/manifests/serviceaccount.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: zarf + namespace: zarf diff --git a/packages/zarf-agent/zarf.yaml b/packages/zarf-agent/zarf.yaml index 86799ff03f..32a6c34997 100644 --- a/packages/zarf-agent/zarf.yaml +++ b/packages/zarf-agent/zarf.yaml @@ -27,6 +27,9 @@ components: - manifests/secret.yaml - manifests/deployment.yaml - manifests/webhook.yaml + - manifests/role.yaml + - manifests/rolebinding.yaml + - manifests/serviceaccount.yaml actions: onCreate: before: diff --git a/src/config/lang/english.go b/src/config/lang/english.go index b72ffa1461..c5deeffc0c 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -664,7 +664,7 @@ const ( AgentErrBadRequest = "could not read request body: %s" AgentErrBindHandler = "Unable to bind the webhook handler" AgentErrCouldNotDeserializeReq = "could not deserialize request: %s" - AgentErrGetState = "failed to load zarf state from file: %w" + AgentErrGetState = "failed to load zarf state: %w" AgentErrHostnameMatch = "failed to complete hostname matching: %w" AgentErrImageSwap = "Unable to swap the host for (%s)" AgentErrInvalidMethod = "invalid method only POST requests are allowed" diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index a7b6911991..cefb024df0 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -5,13 +5,14 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" v1 "k8s.io/api/admission/v1" @@ -20,11 +21,15 @@ import ( ) // NewPodMutationHook creates a new instance of pods mutation hook. -func NewPodMutationHook() operations.Hook { +func NewPodMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewMutationHook()") return operations.Hook{ - Create: mutatePod, - Update: mutatePod, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutatePod(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutatePod(ctx, r, cluster) + }, } } @@ -34,14 +39,12 @@ func parsePod(object []byte) (*corev1.Pod, error) { if err := json.Unmarshal(object, &pod); err != nil { return nil, err } - return &pod, nil } -func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { +func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) { message.Debugf("hooks.mutatePod()(*v1.AdmissionRequest) - %#v , %s/%s: %#v", r.Kind, r.Namespace, r.Name, r.Operation) - var patchOperations []operations.PatchOperation pod, err := parsePod(r.Object.Raw) if err != nil { return &operations.Result{Msg: err.Error()}, nil @@ -51,24 +54,26 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { // We've already played with this pod, just keep swimming 🐟 return &operations.Result{ Allowed: true, - PatchOps: patchOperations, + PatchOps: []operations.PatchOperation{}, }, nil } - // Add the zarf secret to the podspec - zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}} - patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) - - zarfState, err := state.GetZarfStateFromAgentPod() + state, err := cluster.LoadZarfState(ctx) if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - containerRegistryURL := zarfState.RegistryInfo.Address + registryURL := state.RegistryInfo.Address + + var patchOperations []operations.PatchOperation + + // Add the zarf secret to the podspec + zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}} + patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) // update the image host for each init container for idx, container := range pod.Spec.InitContainers { path := fmt.Sprintf("/spec/initContainers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod @@ -79,7 +84,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { // update the image host for each ephemeral container for idx, container := range pod.Spec.EphemeralContainers { path := fmt.Sprintf("/spec/ephemeralContainers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod @@ -90,7 +95,7 @@ func mutatePod(r *v1.AdmissionRequest) (*operations.Result, error) { // update the image host for each normal container for idx, container := range pod.Spec.Containers { path := fmt.Sprintf("/spec/containers/%d/image", idx) - replacement, err := transform.ImageTransformHost(containerRegistryURL, container.Image) + replacement, err := transform.ImageTransformHost(registryURL, container.Image) if err != nil { message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go new file mode 100644 index 0000000000..eb3480b415 --- /dev/null +++ b/src/internal/agent/hooks/pods_test.go @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createPodAdmissionRequest(t *testing.T, op v1.Operation, pod *corev1.Pod) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(pod) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestPodMutationWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) + + tests := []struct { + name string + admissionReq *v1.AdmissionRequest + expectedPatch []operations.PatchOperation + code int + }{ + { + name: "pod with label should be mutated", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"should-be": "mutated"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + InitContainers: []corev1.Container{{Image: "busybox"}}, + EphemeralContainers: []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + Image: "alpine", + }, + }, + }, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/imagePullSecrets", + []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, + ), + operations.ReplacePatchOperation( + "/spec/initContainers/0/image", + "127.0.0.1:31999/library/busybox:latest-zarf-2140033595", + ), + operations.ReplacePatchOperation( + "/spec/ephemeralContainers/0/image", + "127.0.0.1:31999/library/alpine:latest-zarf-1117969859", + ), + operations.ReplacePatchOperation( + "/spec/containers/0/image", + "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", + ), + operations.ReplacePatchOperation( + "/metadata/labels/zarf-agent", + "patched", + ), + }, + code: http.StatusOK, + }, + { + name: "pod with zarf-agent patched label should not be mutated", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"zarf-agent": "patched"}, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + }, + }), + expectedPatch: nil, + code: http.StatusOK, + }, + { + name: "pod with no labels should not error", + admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Labels: nil, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{Image: "nginx"}}, + }, + }), + expectedPatch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/imagePullSecrets", + []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, + ), + operations.ReplacePatchOperation( + "/spec/containers/0/image", + "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", + ), + operations.AddPatchOperation( + "/metadata/labels", + map[string]string{"zarf-agent": "patched"}, + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) + if tt.expectedPatch == nil { + require.Empty(t, string(resp.Patch)) + } else { + expectedPatchJSON, err := json.Marshal(tt.expectedPatch) + require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + } + }) + } +} diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go new file mode 100644 index 0000000000..909cd1f301 --- /dev/null +++ b/src/internal/agent/hooks/utils_test.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/k8s" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" +) + +func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { + t.Helper() + c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + stateData, err := json.Marshal(state) + require.NoError(t, err) + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: cluster.ZarfStateSecretName, + Namespace: cluster.ZarfNamespaceName, + }, + Data: map[string][]byte{ + cluster.ZarfStateDataKey: stateData, + }, + } + _, err = c.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Create(ctx, secret, metav1.CreateOptions{}) + require.NoError(t, err) + return c +} + +// sendAdmissionRequest sends an admission request to the handler and returns the response. +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *v1.AdmissionResponse { + t.Helper() + + b, err := json.Marshal(&v1.AdmissionReview{ + Request: admissionReq, + }) + require.NoError(t, err) + + // Note: The URL ("/test") doesn't matter here because we are directly invoking the handler. + // The handler processes the request based on the HTTP method and body content, not the URL path. + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(b)) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + require.Equal(t, code, rr.Code) + + var admissionReview v1.AdmissionReview + if rr.Code == http.StatusOK { + err = json.NewDecoder(rr.Body).Decode(&admissionReview) + require.NoError(t, err) + } + + return admissionReview.Response +} diff --git a/src/internal/agent/http/admission.go b/src/internal/agent/http/admission/handler.go similarity index 80% rename from src/internal/agent/http/admission.go rename to src/internal/agent/http/admission/handler.go index fbeeb3b983..b4e3109af7 100644 --- a/src/internal/agent/http/admission.go +++ b/src/internal/agent/http/admission/handler.go @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors -// Package http provides a http server for the webhook and proxy. -package http +// Package admission provides an HTTP handler for a Kubernetes admission webhook. +// It includes functionality to decode incoming admission requests, execute +// the corresponding operations, and return appropriate admission responses. +package admission import ( "encoding/json" @@ -19,20 +21,20 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" ) -// admissionHandler represents the HTTP handler for an admission webhook. -type admissionHandler struct { +// Handler represents the HTTP handler for an admission webhook. +type Handler struct { decoder runtime.Decoder } -// newAdmissionHandler returns an instance of AdmissionHandler. -func newAdmissionHandler() *admissionHandler { - return &admissionHandler{ +// NewHandler returns a new admission Handler. +func NewHandler() *Handler { + return &Handler{ decoder: serializer.NewCodecFactory(runtime.NewScheme()).UniversalDeserializer(), } } -// Serve returns a http.HandlerFunc for an admission webhook. -func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { +// Serve returns an http.HandlerFunc for an admission webhook. +func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Debugf("http.Serve(%#v)", hook) return func(w http.ResponseWriter, r *http.Request) { message.Debugf("http.Serve()(writer, %#v)", r.URL) @@ -67,7 +69,7 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { result, err := hook.Execute(review.Request) if err != nil { - message.WarnErr(err, lang.AgentErrBindHandler) + message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) w.WriteHeader(http.StatusInternalServerError) return } @@ -84,7 +86,7 @@ func (h *admissionHandler) Serve(hook operations.Hook) http.HandlerFunc { }, } - // set the patch operations for mutating admission + // Set the patch operations for mutating admission if len(result.PatchOps) > 0 { jsonPatchType := v1.PatchTypeJSONPatch patchBytes, err := json.Marshal(result.PatchOps) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 86ff5e828f..7b07b1445d 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -5,27 +5,37 @@ package http import ( + "context" "fmt" "net/http" "time" "github.com/defenseunicorns/zarf/src/internal/agent/hooks" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/prometheus/client_golang/prometheus/promhttp" ) // NewAdmissionServer creates a http.Server for the mutating webhook admission handler. func NewAdmissionServer(port string) *http.Server { - message.Debugf("http.NewServer(%s)", port) + message.Debugf("http.NewAdmissionServer(%s)", port) + + c, err := cluster.NewCluster() + if err != nil { + message.Fatalf(err, err.Error()) + } + + ctx := context.Background() // Instances hooks - podsMutation := hooks.NewPodMutationHook() + podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() // Routers - ah := newAdmissionHandler() + ah := admission.NewHandler() mux := http.NewServeMux() mux.Handle("/healthz", healthz()) mux.Handle("/mutate/pod", ah.Serve(podsMutation)) diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index b9451f3e72..0d40f58bfa 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -213,9 +213,6 @@ func TestGetDeployedPackage(t *testing.T) { for _, p := range packages { b, err := json.Marshal(p) require.NoError(t, err) - data := map[string][]byte{ - "data": b, - } secret := corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: strings.Join([]string{config.ZarfPackagePrefix, p.Name}, ""), @@ -224,7 +221,9 @@ func TestGetDeployedPackage(t *testing.T) { ZarfPackageInfoLabel: p.Name, }, }, - Data: data, + Data: map[string][]byte{ + "data": b, + }, } c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{}) actual, err := c.GetDeployedPackage(ctx, p.Name) From f2d360261da70e656b090aa2f3b9bf036cf1dc19 Mon Sep 17 00:00:00 2001 From: Xander Grzywinski Date: Tue, 21 May 2024 12:14:53 -0700 Subject: [PATCH 012/132] docs: add google analytics for docs pages (#2530) ## Description Add Google Analytics for docs pages ## Related Issue Fixes n/a ## Checklist before merging - [X] Test, docs, adr added or updated as needed - [X] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Signed-off-by: Xander Grzywinski --- site/astro.config.ts | 13 +++++++++++++ site/src/components/SkipLink.astro | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 site/src/components/SkipLink.astro diff --git a/site/astro.config.ts b/site/astro.config.ts index 6e54f621a0..ac3b8dec24 100644 --- a/site/astro.config.ts +++ b/site/astro.config.ts @@ -25,6 +25,19 @@ export default defineConfig({ integrations: [ starlight({ title: "Zarf", + head: [ + { + tag: "script", + content: `(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','G-N1XZ8ZXCWL');`, + }, + ], + components: { + SkipLink: "./src/components/SkipLink.astro", + }, social: { github: "https://github.com/defenseunicorns/zarf", slack: "https://kubernetes.slack.com/archives/C03B6BJAUJ3", diff --git a/site/src/components/SkipLink.astro b/site/src/components/SkipLink.astro new file mode 100644 index 0000000000..7d863429e4 --- /dev/null +++ b/site/src/components/SkipLink.astro @@ -0,0 +1,18 @@ +--- +import type { Props } from '@astrojs/starlight/props' +import Default from '@astrojs/starlight/components/SkipLink.astro' +--- + + + + + + \ No newline at end of file From 04e1c78a612ad6e6c8aa8ceac6dea56ede9d4745 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 22 May 2024 13:22:39 +0200 Subject: [PATCH 013/132] test: add unit tests for detect distro (#2521) ## Description Adds tests to detect distro logic. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: razzle --- src/pkg/cluster/distro.go | 101 ++++++++++++++++++ src/pkg/cluster/distro_test.go | 181 +++++++++++++++++++++++++++++++++ src/pkg/cluster/state.go | 35 ++++--- src/pkg/k8s/info.go | 104 ------------------- 4 files changed, 301 insertions(+), 120 deletions(-) create mode 100644 src/pkg/cluster/distro.go create mode 100644 src/pkg/cluster/distro_test.go diff --git a/src/pkg/cluster/distro.go b/src/pkg/cluster/distro.go new file mode 100644 index 0000000000..8c90ccfc49 --- /dev/null +++ b/src/pkg/cluster/distro.go @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package cluster contains Zarf-specific cluster management functions. +package cluster + +import ( + "regexp" + + corev1 "k8s.io/api/core/v1" +) + +// List of supported distros via distro detection. +const ( + DistroIsUnknown = "unknown" + DistroIsK3s = "k3s" + DistroIsK3d = "k3d" + DistroIsKind = "kind" + DistroIsMicroK8s = "microk8s" + DistroIsEKS = "eks" + DistroIsEKSAnywhere = "eksanywhere" + DistroIsDockerDesktop = "dockerdesktop" + DistroIsGKE = "gke" + DistroIsAKS = "aks" + DistroIsRKE2 = "rke2" + DistroIsTKG = "tkg" +) + +// DetectDistro returns the matching distro or unknown if not found. +func detectDistro(node corev1.Node, namespaces []corev1.Namespace) string { + kindNodeRegex := regexp.MustCompile(`^kind://`) + k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`) + eksNodeRegex := regexp.MustCompile(`^aws:///`) + gkeNodeRegex := regexp.MustCompile(`^gce://`) + aksNodeRegex := regexp.MustCompile(`^azure:///subscriptions`) + rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`) + tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`) + + // Regex explanation: https://regex101.com/r/TIUQVe/1 + // https://github.com/rancher/k3d/blob/v5.2.2/cmd/node/nodeCreate.go#L187 + if k3dNodeRegex.MatchString(node.Spec.ProviderID) { + return DistroIsK3d + } + + // Regex explanation: https://regex101.com/r/le7PRB/1 + // https://github.com/kubernetes-sigs/kind/pull/1805 + if kindNodeRegex.MatchString(node.Spec.ProviderID) { + return DistroIsKind + } + + // https://github.com/kubernetes/cloud-provider-aws/blob/454ed784c33b974c873c7d762f9d30e7c4caf935/pkg/providers/v2/instances.go#L234 + if eksNodeRegex.MatchString(node.Spec.ProviderID) { + return DistroIsEKS + } + + if gkeNodeRegex.MatchString(node.Spec.ProviderID) { + return DistroIsGKE + } + + // https://github.com/kubernetes/kubernetes/blob/v1.23.4/staging/src/k8s.io/legacy-cloud-providers/azure/azure_wrap.go#L46 + if aksNodeRegex.MatchString(node.Spec.ProviderID) { + return DistroIsAKS + } + + labels := node.GetLabels() + for k, v := range labels { + // kubectl get nodes --selector node.kubernetes.io/instance-type=k3s for K3s + if k == "node.kubernetes.io/instance-type" && v == "k3s" { + return DistroIsK3s + } + // kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s + if k == "microk8s.io/cluster" && v == "true" { + return DistroIsMicroK8s + } + } + + if node.GetName() == "docker-desktop" { + return DistroIsDockerDesktop + } + + // TODO: Find a new detection method, by default the amount of images in the node status is limited. + for _, images := range node.Status.Images { + for _, image := range images.Names { + if rke2Regex.MatchString(image) { + return DistroIsRKE2 + } + if tkgRegex.MatchString(image) { + return DistroIsTKG + } + } + } + + // kubectl get ns eksa-system for EKS Anywhere + for _, namespace := range namespaces { + if namespace.Name == "eksa-system" { + return DistroIsEKSAnywhere + } + } + + return DistroIsUnknown +} diff --git a/src/pkg/cluster/distro_test.go b/src/pkg/cluster/distro_test.go new file mode 100644 index 0000000000..88e7711493 --- /dev/null +++ b/src/pkg/cluster/distro_test.go @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package cluster contains Zarf-specific cluster management functions. +package cluster + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestDetectDistro(t *testing.T) { + t.Parallel() + + tests := []struct { + distro string + node corev1.Node + namespaces []corev1.Namespace + }{ + { + distro: DistroIsUnknown, + node: corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: corev1.NodeSpec{ + ProviderID: "hello world", + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + }, + }, + }, + { + distro: DistroIsK3s, + node: corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "node.kubernetes.io/instance-type": "k3s", + }, + }, + }, + }, + { + distro: DistroIsK3d, + node: corev1.Node{ + Spec: corev1.NodeSpec{ + ProviderID: "k3s://k3d-k3s-default-server-0", + }, + }, + }, + { + distro: DistroIsKind, + node: corev1.Node{ + Spec: corev1.NodeSpec{ + ProviderID: "kind://docker/kind/kind-control-plane", + }, + }, + }, + { + distro: DistroIsMicroK8s, + node: corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "microk8s.io/cluster": "true", + }, + }, + }, + }, + { + distro: DistroIsEKS, + node: corev1.Node{ + Spec: corev1.NodeSpec{ + ProviderID: "aws:////i-112bac41a19da1819", + }, + }, + }, + { + distro: DistroIsEKSAnywhere, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "eksa-system", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "baz", + }, + }, + }, + }, + { + distro: DistroIsDockerDesktop, + node: corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "docker-desktop", + }, + }, + }, + { + distro: DistroIsGKE, + node: corev1.Node{ + Spec: corev1.NodeSpec{ + ProviderID: "gce://kthw-239419/us-central1-f/gk3-autopilot-cluster-1-pool-2-e87e560a-7gvw", + }, + }, + }, + { + distro: DistroIsAKS, + node: corev1.Node{ + Spec: corev1.NodeSpec{ + ProviderID: "azure:///subscriptions/9107f2fb-e486-a434-a948-52e2929b6f18/resourceGroups/MC_rg_capz-managed-aks_eastus/providers/Microsoft.Compute/virtualMachineScaleSets/aks-agentpool0-10226072-vmss/virtualMachines/0", + }, + }, + }, + { + distro: DistroIsRKE2, + node: corev1.Node{ + Status: corev1.NodeStatus{ + Images: []corev1.ContainerImage{ + { + Names: []string{"docker.io/library/ubuntu:latest"}, + }, + { + Names: []string{"rancher/rancher-agent:v2"}, + }, + }, + }, + }, + }, + { + distro: DistroIsTKG, + node: corev1.Node{ + Status: corev1.NodeStatus{ + Images: []corev1.ContainerImage{ + { + Names: []string{"docker.io/library/ubuntu:latest"}, + }, + { + Names: []string{"projects.registry.vmware.com/tkg/tanzu_core/"}, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.distro, func(t *testing.T) { + t.Parallel() + + distro := detectDistro(tt.node, tt.namespaces) + require.Equal(t, tt.distro, distro) + }) + } +} diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index a0414f8793..39efa83008 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -8,21 +8,20 @@ import ( "context" "encoding/json" "fmt" - "time" - "slices" + "time" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/types" "github.com/fatih/color" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/zarf/src/types" ) // Zarf Cluster Constants. @@ -54,20 +53,24 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO state = &types.ZarfState{} spinner.Updatef("New cluster, no prior Zarf deployments found") - // If the K3s component is being deployed, skip distro detection. if initOptions.ApplianceMode { - distro = k8s.DistroIsK3s + // If the K3s component is being deployed, skip distro detection. + distro = DistroIsK3s state.ZarfAppliance = true } else { // Otherwise, trying to detect the K8s distro type. - distro, err = c.DetectDistro(ctx) + nodeList, err := c.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { - // This is a basic failure right now but likely could be polished to provide user guidance to resolve. - return fmt.Errorf("unable to connect to the cluster to verify the distro: %w", err) + return err } + distro = detectDistro(nodeList.Items[0], namespaceList.Items) } - if distro != k8s.DistroIsUnknown { + if distro != DistroIsUnknown { spinner.Updatef("Detected K8s distro %s", distro) } @@ -144,13 +147,13 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO } switch state.Distro { - case k8s.DistroIsK3s, k8s.DistroIsK3d: + case DistroIsK3s, DistroIsK3d: state.StorageClass = "local-path" - case k8s.DistroIsKind, k8s.DistroIsGKE: + case DistroIsKind, DistroIsGKE: state.StorageClass = "standard" - case k8s.DistroIsDockerDesktop: + case DistroIsDockerDesktop: state.StorageClass = "hostpath" } diff --git a/src/pkg/k8s/info.go b/src/pkg/k8s/info.go index edb655b964..1a19a837e1 100644 --- a/src/pkg/k8s/info.go +++ b/src/pkg/k8s/info.go @@ -8,113 +8,9 @@ import ( "context" "errors" "fmt" - "regexp" - "strings" ) -// List of supported distros via distro detection. -const ( - DistroIsUnknown = "unknown" - DistroIsK3s = "k3s" - DistroIsK3d = "k3d" - DistroIsKind = "kind" - DistroIsMicroK8s = "microk8s" - DistroIsEKS = "eks" - DistroIsEKSAnywhere = "eksanywhere" - DistroIsDockerDesktop = "dockerdesktop" - DistroIsGKE = "gke" - DistroIsAKS = "aks" - DistroIsRKE2 = "rke2" - DistroIsTKG = "tkg" -) - -// DetectDistro returns the matching distro or unknown if not found. -func (k *K8s) DetectDistro(ctx context.Context) (string, error) { - kindNodeRegex := regexp.MustCompile(`^kind://`) - k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`) - eksNodeRegex := regexp.MustCompile(`^aws:///`) - gkeNodeRegex := regexp.MustCompile(`^gce://`) - aksNodeRegex := regexp.MustCompile(`^azure:///subscriptions`) - rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`) - tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`) - - nodes, err := k.GetNodes(ctx) - if err != nil { - return DistroIsUnknown, errors.New("error getting cluster nodes") - } - - // All nodes should be the same for what we are looking for - node := nodes.Items[0] - - // Regex explanation: https://regex101.com/r/TIUQVe/1 - // https://github.com/rancher/k3d/blob/v5.2.2/cmd/node/nodeCreate.go#L187 - if k3dNodeRegex.MatchString(node.Spec.ProviderID) { - return DistroIsK3d, nil - } - - // Regex explanation: https://regex101.com/r/le7PRB/1 - // https://github.com/kubernetes-sigs/kind/pull/1805 - if kindNodeRegex.MatchString(node.Spec.ProviderID) { - return DistroIsKind, nil - } - - // https://github.com/kubernetes/cloud-provider-aws/blob/454ed784c33b974c873c7d762f9d30e7c4caf935/pkg/providers/v2/instances.go#L234 - if eksNodeRegex.MatchString(node.Spec.ProviderID) { - return DistroIsEKS, nil - } - - if gkeNodeRegex.MatchString(node.Spec.ProviderID) { - return DistroIsGKE, nil - } - - // https://github.com/kubernetes/kubernetes/blob/v1.23.4/staging/src/k8s.io/legacy-cloud-providers/azure/azure_wrap.go#L46 - if aksNodeRegex.MatchString(node.Spec.ProviderID) { - return DistroIsAKS, nil - } - - labels := node.GetLabels() - for _, label := range labels { - // kubectl get nodes --selector node.kubernetes.io/instance-type=k3s for K3s - if label == "node.kubernetes.io/instance-type=k3s" { - return DistroIsK3s, nil - } - // kubectl get nodes --selector microk8s.io/cluster=true for MicroK8s - if label == "microk8s.io/cluster=true" { - return DistroIsMicroK8s, nil - } - } - - if node.GetName() == "docker-desktop" { - return DistroIsDockerDesktop, nil - } - - for _, images := range node.Status.Images { - for _, image := range images.Names { - if rke2Regex.MatchString(image) { - return DistroIsRKE2, nil - } - if tkgRegex.MatchString(image) { - return DistroIsTKG, nil - } - } - } - - namespaces, err := k.GetNamespaces(ctx) - if err != nil { - return DistroIsUnknown, errors.New("error getting namespace list") - } - - // kubectl get ns eksa-system for EKS Anywhere - for _, namespace := range namespaces.Items { - if namespace.Name == "eksa-system" { - return DistroIsEKSAnywhere, nil - } - } - - return DistroIsUnknown, nil -} - // GetArchitectures returns the cluster system architectures if found. func (k *K8s) GetArchitectures(ctx context.Context) ([]string, error) { nodes, err := k.GetNodes(ctx) From 3c77d11244a7eaf457cf62a5d1c9eea44461044c Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 22 May 2024 19:10:53 +0200 Subject: [PATCH 014/132] test: add tests for injector (#2534) ## Description Adds tests to injector. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/cluster/injector.go | 23 +- src/pkg/cluster/injector_test.go | 204 ++++++++++++++++++ .../testdata/expected-injection-pod.json | 1 + .../testdata/expected-injection-service.json | 1 + 4 files changed, 221 insertions(+), 8 deletions(-) create mode 100644 src/pkg/cluster/injector_test.go create mode 100644 src/pkg/cluster/testdata/expected-injection-pod.json create mode 100644 src/pkg/cluster/testdata/expected-injection-service.json diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 3abd010b8f..b2a8569318 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -73,7 +73,7 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag } spinner.Updatef("Creating the injector configmap") - if err = c.createInjectorConfigmap(ctx, tmp.InjectionBinary); err != nil { + if err = c.createInjectorConfigMap(ctx, tmp.InjectionBinary); err != nil { spinner.Fatalf(err, "Unable to create the injector configmap") } @@ -90,7 +90,7 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag } spinner.Updatef("Loading the seed registry configmaps") - if payloadConfigmaps, sha256sum, err = c.createPayloadConfigmaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { + if payloadConfigmaps, sha256sum, err = c.createPayloadConfigMaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { spinner.Fatalf(err, "Unable to generate the injector payload configmaps") } @@ -197,7 +197,7 @@ func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSr return seedImages, nil } -func (c *Cluster) createPayloadConfigmaps(ctx context.Context, seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { +func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { var configMaps []string // Chunk size has to accommodate base64 encoding & etcd 1MB limit @@ -285,7 +285,7 @@ func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Im return true } -func (c *Cluster) createInjectorConfigmap(ctx context.Context, binaryPath string) error { +func (c *Cluster) createInjectorConfigMap(ctx context.Context, binaryPath string) error { var err error configData := make(map[string][]byte) @@ -473,10 +473,8 @@ func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeM continue } - for _, taint := range nodeDetails.Spec.Taints { - if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { - continue - } + if hasBlockingTaints(nodeDetails.Spec.Taints) { + continue } for _, container := range pod.Spec.InitContainers { @@ -499,3 +497,12 @@ func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeM } } } + +func hasBlockingTaints(taints []corev1.Taint) bool { + for _, taint := range taints { + if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { + return true + } + } + return false +} diff --git a/src/pkg/cluster/injector_test.go b/src/pkg/cluster/injector_test.go new file mode 100644 index 0000000000..3d9ccb1a36 --- /dev/null +++ b/src/pkg/cluster/injector_test.go @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cluster + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/defenseunicorns/zarf/src/pkg/k8s" +) + +func TestCreateInjectorConfigMap(t *testing.T) { + t.Parallel() + + binData := []byte("foobar") + binPath := filepath.Join(t.TempDir(), "bin") + err := os.WriteFile(binPath, binData, 0o644) + require.NoError(t, err) + + cs := fake.NewSimpleClientset() + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + }, + } + + ctx := context.Background() + for i := 0; i < 2; i++ { + err = c.createInjectorConfigMap(ctx, binPath) + require.NoError(t, err) + cm, err := cs.CoreV1().ConfigMaps(ZarfNamespaceName).Get(ctx, "rust-binary", metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, binData, cm.BinaryData["zarf-injector"]) + } +} + +func TestCreateService(t *testing.T) { + t.Parallel() + + cs := fake.NewSimpleClientset() + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + }, + } + + expected, err := os.ReadFile("./testdata/expected-injection-service.json") + require.NoError(t, err) + ctx := context.Background() + for i := 0; i < 2; i++ { + _, err := c.createService(ctx) + require.NoError(t, err) + svc, err := cs.CoreV1().Services(ZarfNamespaceName).Get(ctx, "zarf-injector", metav1.GetOptions{}) + require.NoError(t, err) + b, err := json.Marshal(svc) + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(string(expected)), string(b)) + } +} + +func TestBuildInjectionPod(t *testing.T) { + t.Parallel() + + c := &Cluster{} + pod, err := c.buildInjectionPod("injection-node", "docker.io/library/ubuntu:latest", []string{"foo", "bar"}, "shasum") + require.NoError(t, err) + b, err := json.Marshal(pod) + require.NoError(t, err) + expected, err := os.ReadFile("./testdata/expected-injection-pod.json") + require.NoError(t, err) + require.Equal(t, strings.TrimSpace(string(expected)), string(b)) +} + +func TestImagesAndNodesForInjection(t *testing.T) { + t.Parallel() + + ctx := context.Background() + cs := fake.NewSimpleClientset() + + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + Log: func(string, ...any) {}, + }, + } + + nodes := []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "no-resources", + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("400m"), + corev1.ResourceMemory: resource.MustParse("50Mi"), + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "no-schedule-taint", + }, + Spec: corev1.NodeSpec{ + Taints: []corev1.Taint{ + { + Effect: corev1.TaintEffectNoSchedule, + }, + }, + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1000m"), + corev1.ResourceMemory: resource.MustParse("10Gi"), + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "good", + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1000m"), + corev1.ResourceMemory: resource.MustParse("10Gi"), + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "no-execute-taint", + }, + Spec: corev1.NodeSpec{ + Taints: []corev1.Taint{ + { + Effect: corev1.TaintEffectNoExecute, + }, + }, + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1000m"), + corev1.ResourceMemory: resource.MustParse("10Gi"), + }, + }, + }, + } + for i, node := range nodes { + _, err := cs.CoreV1().Nodes().Create(ctx, &node, metav1.CreateOptions{}) + require.NoError(t, err) + podName := fmt.Sprintf("pod-%d", i) + pod := corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: node.ObjectMeta.Name, + InitContainers: []corev1.Container{ + { + Image: podName + "-init", + }, + }, + Containers: []corev1.Container{ + { + Image: podName + "-container", + }, + }, + EphemeralContainers: []corev1.EphemeralContainer{ + { + EphemeralContainerCommon: corev1.EphemeralContainerCommon{ + Image: podName + "-ephemeral", + }, + }, + }, + }, + } + _, err = cs.CoreV1().Pods(pod.Namespace).Create(ctx, &pod, metav1.CreateOptions{}) + require.NoError(t, err) + } + + getCtx, getCancel := context.WithTimeout(ctx, 1*time.Second) + defer getCancel() + result, err := c.getImagesAndNodesForInjection(getCtx) + require.NoError(t, err) + expected := imageNodeMap{ + "pod-2-init": []string{"good"}, + "pod-2-container": []string{"good"}, + "pod-2-ephemeral": []string{"good"}, + } + require.Equal(t, expected, result) +} diff --git a/src/pkg/cluster/testdata/expected-injection-pod.json b/src/pkg/cluster/testdata/expected-injection-pod.json new file mode 100644 index 0000000000..30f2e5b1f1 --- /dev/null +++ b/src/pkg/cluster/testdata/expected-injection-pod.json @@ -0,0 +1 @@ +{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} diff --git a/src/pkg/cluster/testdata/expected-injection-service.json b/src/pkg/cluster/testdata/expected-injection-service.json new file mode 100644 index 0000000000..dd826cbb4c --- /dev/null +++ b/src/pkg/cluster/testdata/expected-injection-service.json @@ -0,0 +1 @@ +{"kind":"Service","apiVersion":"v1","metadata":{"name":"zarf-injector","namespace":"zarf","creationTimestamp":null},"spec":{"ports":[{"port":5000,"targetPort":0}],"selector":{"app":"zarf-injector"},"type":"NodePort"},"status":{"loadBalancer":{}}} From b8dd1bb03824c09d2e62aa976bfe228a6fa5b84b Mon Sep 17 00:00:00 2001 From: schristoff <167717759+schristoff-du@users.noreply.github.com> Date: Wed, 22 May 2024 11:50:35 -0600 Subject: [PATCH 015/132] chore: add codecov (#2529) Signed-off-by: schristoff <28318173+schristoff@users.noreply.github.com> Co-authored-by: schristoff <28318173+schristoff@users.noreply.github.com> --- .github/workflows/test-unit.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 327e7052ad..f3425c1098 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -25,9 +25,17 @@ jobs: steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Run coverage + run: go test -race -coverprofile=coverage.out -covermode=atomic + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} - name: Setup golang uses: ./.github/actions/golang - name: Run unit tests - run: make test-unit + run: make test-unit \ No newline at end of file From 21995e9fc2d794ce53a299065705a7876df12694 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Wed, 22 May 2024 13:42:02 -0500 Subject: [PATCH 016/132] chore: add unit tests for creator.LoadPackageDefinition (#2531) Relates to #2512 --- src/pkg/packager/create.go | 5 -- src/pkg/packager/creator/creator.go | 2 +- src/pkg/packager/creator/normal.go | 10 +++- src/pkg/packager/creator/normal_test.go | 43 +++++++++++++++ src/pkg/packager/creator/skeleton.go | 10 +++- src/pkg/packager/creator/skeleton_test.go | 55 +++++++++++++++++++ .../creator/testdata/invalid/zarf.yaml | 4 ++ .../packager/creator/testdata/valid/zarf.yaml | 6 ++ src/types/validate.go | 5 ++ 9 files changed, 128 insertions(+), 12 deletions(-) create mode 100644 src/pkg/packager/creator/skeleton_test.go create mode 100644 src/pkg/packager/creator/testdata/invalid/zarf.yaml create mode 100644 src/pkg/packager/creator/testdata/valid/zarf.yaml diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index bc10fe8d73..bad88d5d4e 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -39,11 +39,6 @@ func (p *Packager) Create() (err error) { return err } - // Perform early package validation. - if err := p.cfg.Pkg.Validate(); err != nil { - return fmt.Errorf("unable to validate package: %w", err) - } - if !p.confirmAction(config.ZarfCreateStage) { return fmt.Errorf("package creation canceled") } diff --git a/src/pkg/packager/creator/creator.go b/src/pkg/packager/creator/creator.go index 5b0f3c27b1..5e34fd46c1 100644 --- a/src/pkg/packager/creator/creator.go +++ b/src/pkg/packager/creator/creator.go @@ -11,7 +11,7 @@ import ( // Creator is an interface for creating Zarf packages. type Creator interface { - LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) + LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) error } diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 0cb5c7d921..7747cfdd4d 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -61,8 +61,8 @@ func NewPackageCreator(createOpts types.ZarfCreateOptions, cwd string) *PackageC } // LoadPackageDefinition loads and configures a zarf.yaml file during package create. -func (pc *PackageCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { - pkg, warnings, err = dst.ReadZarfYAML() +func (pc *PackageCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { + pkg, warnings, err = src.ReadZarfYAML() if err != nil { return types.ZarfPackage{}, nil, err } @@ -86,7 +86,7 @@ func (pc *PackageCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg t warnings = append(warnings, templateWarnings...) // After templates are filled process any create extensions - pkg.Components, err = pc.processExtensions(pkg.Components, dst, pkg.Metadata.YOLO) + pkg.Components, err = pc.processExtensions(pkg.Components, src, pkg.Metadata.YOLO) if err != nil { return types.ZarfPackage{}, nil, err } @@ -119,6 +119,10 @@ func (pc *PackageCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg t } } + if err := pkg.Validate(); err != nil { + return types.ZarfPackage{}, nil, err + } + return pkg, warnings, nil } diff --git a/src/pkg/packager/creator/normal_test.go b/src/pkg/packager/creator/normal_test.go index 9a4830cb1f..95998ba1ca 100644 --- a/src/pkg/packager/creator/normal_test.go +++ b/src/pkg/packager/creator/normal_test.go @@ -8,6 +8,8 @@ import ( "path/filepath" "testing" + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" ) @@ -56,3 +58,44 @@ func TestDifferentialPackagePathSetCorrectly(t *testing.T) { }) } } + +func TestLoadPackageDefinition(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + testDir string + expectedErr string + }{ + { + name: "valid package definition", + testDir: "valid", + expectedErr: "", + }, + { + name: "invalid package definition", + testDir: "invalid", + expectedErr: "package must have at least 1 component", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + src := layout.New(filepath.Join("testdata", tt.testDir)) + pc := NewPackageCreator(types.ZarfCreateOptions{}, "") + pkg, _, err := pc.LoadPackageDefinition(src) + + if tt.expectedErr == "" { + require.NoError(t, err) + require.NotEmpty(t, pkg) + return + } + + require.EqualError(t, err, tt.expectedErr) + require.Empty(t, pkg) + }) + } +} diff --git a/src/pkg/packager/creator/skeleton.go b/src/pkg/packager/creator/skeleton.go index 74dc19fd35..b6e5498322 100644 --- a/src/pkg/packager/creator/skeleton.go +++ b/src/pkg/packager/creator/skeleton.go @@ -42,8 +42,8 @@ func NewSkeletonCreator(createOpts types.ZarfCreateOptions, publishOpts types.Za } // LoadPackageDefinition loads and configure a zarf.yaml file when creating and publishing a skeleton package. -func (sc *SkeletonCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { - pkg, warnings, err = dst.ReadZarfYAML() +func (sc *SkeletonCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { + pkg, warnings, err = src.ReadZarfYAML() if err != nil { return types.ZarfPackage{}, nil, err } @@ -60,7 +60,7 @@ func (sc *SkeletonCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg warnings = append(warnings, composeWarnings...) - pkg.Components, err = sc.processExtensions(pkg.Components, dst) + pkg.Components, err = sc.processExtensions(pkg.Components, src) if err != nil { return types.ZarfPackage{}, nil, err } @@ -69,6 +69,10 @@ func (sc *SkeletonCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg message.Warn(warning) } + if err := pkg.Validate(); err != nil { + return types.ZarfPackage{}, nil, err + } + return pkg, warnings, nil } diff --git a/src/pkg/packager/creator/skeleton_test.go b/src/pkg/packager/creator/skeleton_test.go new file mode 100644 index 0000000000..1fed6f985d --- /dev/null +++ b/src/pkg/packager/creator/skeleton_test.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package creator contains functions for creating Zarf packages. +package creator + +import ( + "path/filepath" + "testing" + + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestSkeletonLoadPackageDefinition(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + testDir string + expectedErr string + }{ + { + name: "valid package definition", + testDir: "valid", + expectedErr: "", + }, + { + name: "invalid package definition", + testDir: "invalid", + expectedErr: "package must have at least 1 component", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + src := layout.New(filepath.Join("testdata", tt.testDir)) + sc := NewSkeletonCreator(types.ZarfCreateOptions{}, types.ZarfPublishOptions{}) + pkg, _, err := sc.LoadPackageDefinition(src) + + if tt.expectedErr == "" { + require.NoError(t, err) + require.NotEmpty(t, pkg) + return + } + + require.EqualError(t, err, tt.expectedErr) + require.Empty(t, pkg) + }) + } +} diff --git a/src/pkg/packager/creator/testdata/invalid/zarf.yaml b/src/pkg/packager/creator/testdata/invalid/zarf.yaml new file mode 100644 index 0000000000..ae4c915b6e --- /dev/null +++ b/src/pkg/packager/creator/testdata/invalid/zarf.yaml @@ -0,0 +1,4 @@ +kind: ZarfPackageConfig +metadata: + name: minimal-invalid + description: Must have at least 1 component diff --git a/src/pkg/packager/creator/testdata/valid/zarf.yaml b/src/pkg/packager/creator/testdata/valid/zarf.yaml new file mode 100644 index 0000000000..a9b00bb04d --- /dev/null +++ b/src/pkg/packager/creator/testdata/valid/zarf.yaml @@ -0,0 +1,6 @@ +kind: ZarfPackageConfig +metadata: + name: minimal-valid + description: Minimal valid package +components: + - name: component1 diff --git a/src/types/validate.go b/src/types/validate.go index ae6236227f..fbef1dab65 100644 --- a/src/types/validate.go +++ b/src/types/validate.go @@ -5,6 +5,7 @@ package types import ( + "errors" "fmt" "path/filepath" "regexp" @@ -47,6 +48,10 @@ func (pkg ZarfPackage) Validate() error { return fmt.Errorf(lang.PkgValidateErrPkgName, pkg.Metadata.Name) } + if len(pkg.Components) == 0 { + return errors.New("package must have at least 1 component") + } + for _, variable := range pkg.Variables { if err := variable.Validate(); err != nil { return fmt.Errorf(lang.PkgValidateErrVariable, err) From 6b047b42ad6ffe57fccff6c19c59e1ad370a58a1 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 22 May 2024 21:17:55 +0200 Subject: [PATCH 017/132] test: refactor network test (#2533) ## Description This change refactors the network tests to run in parallel and not depend on files served by GitHub. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: razzle --- src/pkg/utils/network_test.go | 208 +++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 76 deletions(-) diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go index fdcf47b043..4722993312 100644 --- a/src/pkg/utils/network_test.go +++ b/src/pkg/utils/network_test.go @@ -5,92 +5,148 @@ package utils import ( - "os" + "fmt" + "net/http" + "net/http/httptest" + "path" "path/filepath" + "strings" "testing" - "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" + + "github.com/defenseunicorns/pkg/helpers" ) -type TestNetworkSuite struct { - suite.Suite - *require.Assertions -} +func TestParseChecksum(t *testing.T) { + t.Parallel() -func (suite *TestNetworkSuite) SetupSuite() { - suite.Assertions = require.New(suite.T()) -} - -func (suite *TestNetworkSuite) Test_0_parseChecksum() { - // zarf prepare sha256sum .adr-dir adr := "https://raw.githubusercontent.com/defenseunicorns/zarf/main/.adr-dir" sum := "930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3" - url := adr + "@" + sum - uri, checksum, err := parseChecksum(url) - suite.NoError(err) - suite.Equal(adr, uri) - suite.Equal(sum, checksum) - - url = adr + "?foo=bar@" + sum - uri, checksum, err = parseChecksum(url) - suite.NoError(err) - suite.Equal(adr+"?foo=bar", uri) - suite.Equal(sum, checksum) - - url = "https://user:pass@hello.world?foo=bar" - uri, checksum, err = parseChecksum(url) - suite.NoError(err) - suite.Equal("https://user:pass@hello.world?foo=bar", uri) - suite.Equal("", checksum) - - url = "https://user:pass@hello.world?foo=bar@" + sum - uri, checksum, err = parseChecksum(url) - suite.NoError(err) - suite.Equal("https://user:pass@hello.world?foo=bar", uri) - suite.Equal(sum, checksum) -} - -func (suite *TestNetworkSuite) Test_1_DownloadToFile() { - readme := "https://raw.githubusercontent.com/defenseunicorns/zarf/main/README.md" - tmp := suite.T().TempDir() - path := filepath.Join(tmp, "README.md") - suite.NoError(DownloadToFile(readme, path, "")) - suite.FileExists(path) - - path = filepath.Join(tmp, "README.md.bad") - bad := "https://raw.githubusercontent.com/defenseunicorns/zarf/main/README.md.bad" - suite.Error(DownloadToFile(bad, path, "")) - - // zarf prepare sha256sum .adr-dir - path = filepath.Join(tmp, ".adr-dir") - sum := "930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3" - adr := "https://raw.githubusercontent.com/defenseunicorns/zarf/main/.adr-dir" - url := adr + "@" + sum - err := DownloadToFile(url, path, "") - suite.NoError(err) - suite.FileExists(path) - content, err := os.ReadFile(path) - suite.NoError(err) - suite.Contains(string(content), "adr") - - check, err := helpers.GetSHA256OfFile(path) - suite.NoError(err) - suite.Equal(sum, check) - - url = adr + "@" + "badsha" - path = filepath.Join(tmp, ".adr-dir.bad") - suite.Error(DownloadToFile(url, path, "")) - url = adr + "?foo=bar@" + sum - path = filepath.Join(tmp, ".adr-dir.good") - suite.NoError(DownloadToFile(url, path, "")) - suite.FileExists(path) + tests := []struct { + name string + url string + expectedURI string + expectedSum string + }{ + { + name: "url with checksum", + url: adr + "@" + sum, + expectedURI: adr, + expectedSum: sum, + }, + { + name: "url with query parameters and checksum", + url: adr + "?foo=bar@" + sum, + expectedURI: adr + "?foo=bar", + expectedSum: sum, + }, + { + name: "url with auth but without checksum", + url: "https://user:pass@hello.world?foo=bar", + expectedURI: "https://user:pass@hello.world?foo=bar", + expectedSum: "", + }, + { + name: "url with auth and checksum", + url: "https://user:pass@hello.world?foo=bar@" + sum, + expectedURI: "https://user:pass@hello.world?foo=bar", + expectedSum: sum, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + uri, checksum, err := parseChecksum(tt.url) + require.NoError(t, err) + require.Equal(t, tt.expectedURI, uri) + require.Equal(t, tt.expectedSum, checksum) + }) + } } -func TestNetwork(t *testing.T) { - message.SetLogLevel(message.DebugLevel) - suite.Run(t, new(TestNetworkSuite)) +func TestDownloadToFile(t *testing.T) { + t.Parallel() + + // TODO: Explore replacing client transport instead of spinning up http server. + files := map[string]string{ + "README.md": "Hello World\n", + ".adr-dir": "adr\n", + } + srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, fileName := path.Split(req.URL.Path) + content, ok := files[fileName] + if !ok { + rw.WriteHeader(http.StatusNotFound) + return + } + rw.Write([]byte(content)) + })) + + tests := []struct { + name string + fileName string + queryParams string + shasum string + expectedErr string + }{ + { + name: "existing file", + fileName: "README.md", + }, + { + name: "non existing file", + fileName: "README.md.bad", + expectedErr: "bad HTTP status: 404 Not Found", + }, + { + name: "existing file with shasum", + fileName: ".adr-dir", + shasum: "930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3", + }, + { + name: "existing file with wrong shasum", + fileName: ".adr-dir", + shasum: "badsha", + expectedErr: "expected badsha, got 930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3", + }, + { + name: "existing file with shasum and query parameters", + fileName: ".adr-dir", + queryParams: "foo=bar", + shasum: "930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3", + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + src := fmt.Sprintf("%s/%s", srv.URL, tt.fileName) + if tt.queryParams != "" { + src = strings.Join([]string{src, tt.queryParams}, "?") + } + if tt.shasum != "" { + src = strings.Join([]string{src, tt.shasum}, "@") + } + fmt.Println(src) + dst := filepath.Join(t.TempDir(), tt.fileName) + err := DownloadToFile(src, dst, "") + if tt.expectedErr != "" { + require.ErrorContains(t, err, tt.expectedErr) + return + } + require.NoError(t, err) + require.FileExists(t, dst) + if tt.shasum == "" { + return + } + check, err := helpers.GetSHA256OfFile(dst) + require.NoError(t, err) + require.Equal(t, tt.shasum, check) + }) + } } From 42e1bf93ec64445c9877fd2e6e0d10a2f8b2bac3 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Wed, 22 May 2024 16:03:07 -0400 Subject: [PATCH 018/132] test: agent flux unit test (#2528) ## Description Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Lucas Rodriguez Co-authored-by: razzle --- go.mod | 2 +- src/config/lang/english.go | 1 + src/internal/agent/hooks/flux.go | 68 +++++------ src/internal/agent/hooks/flux_test.go | 116 +++++++++++++++++++ src/internal/agent/hooks/pods.go | 2 +- src/internal/agent/hooks/pods_test.go | 27 ++--- src/internal/agent/hooks/utils_test.go | 40 ++++++- src/internal/agent/http/admission/handler.go | 36 ++++-- src/internal/agent/http/server.go | 2 +- 9 files changed, 215 insertions(+), 79 deletions(-) create mode 100644 src/internal/agent/hooks/flux_test.go diff --git a/go.mod b/go.mod index 9fe6c7a968..1275140145 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/fairwindsops/pluto/v5 v5.18.4 github.com/fatih/color v1.16.0 github.com/fluxcd/helm-controller/api v0.37.4 + github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fluxcd/source-controller/api v1.2.4 github.com/go-git/go-git/v5 v5.11.0 github.com/go-logr/logr v1.4.1 @@ -227,7 +228,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.3.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect diff --git a/src/config/lang/english.go b/src/config/lang/english.go index c5deeffc0c..adae1f1a41 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -665,6 +665,7 @@ const ( AgentErrBindHandler = "Unable to bind the webhook handler" AgentErrCouldNotDeserializeReq = "could not deserialize request: %s" AgentErrGetState = "failed to load zarf state: %w" + AgentErrParsePod = "failed to parse pod: %w" AgentErrHostnameMatch = "failed to complete hostname matching: %w" AgentErrImageSwap = "Unable to swap the host for (%s)" AgentErrInvalidMethod = "invalid method only POST requests are allowed" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index c4efce1846..617b91901c 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/json" "fmt" @@ -12,40 +13,34 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" - "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" v1 "k8s.io/api/admission/v1" ) -// SecretRef contains the name used to reference a git repository secret. -type SecretRef struct { - Name string `json:"name"` -} - -// GenericGitRepo contains the URL of a git repo and the secret that corresponds to it for use with Flux. -type GenericGitRepo struct { - Spec struct { - URL string `json:"url"` - SecretRef SecretRef `json:"secretRef,omitempty"` - } `json:"spec"` -} +// AgentErrTransformGitURL is thrown when the agent fails to make the git url a Zarf compatible url +const AgentErrTransformGitURL = "unable to transform the git url" // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. -func NewGitRepositoryMutationHook() operations.Hook { +func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewGitRepositoryMutationHook()") return operations.Hook{ - Create: mutateGitRepo, - Update: mutateGitRepo, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateGitRepo(ctx, r, cluster) + }, } } // mutateGitRepoCreate mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error) { +func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { var ( - zarfState *types.ZarfState patches []operations.PatchOperation isPatched bool @@ -53,43 +48,43 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error isUpdate = r.Operation == v1.Update ) - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the flux repository", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the flux repository", state.GitServer.Address) - // parse to simple struct to read the git url - src := &GenericGitRepo{} - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + repo := flux.GitRepository{} + if err = json.Unmarshal(r.Object.Raw, &repo); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - patchedURL := src.Spec.URL // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different than the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Spec.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repo.Spec.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } + patchedURL := repo.Spec.URL + // Mutate the git URL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, patchedURL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the git url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("%s: %w", AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() - message.Debugf("original git URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) + message.Debugf("original git URL of (%s) got mutated to (%s)", repo.Spec.URL, patchedURL) } // Patch updates of the repo spec - patches = populatePatchOperations(patchedURL, src.Spec.SecretRef.Name) + patches = populatePatchOperations(patchedURL) return &operations.Result{ Allowed: true, @@ -98,17 +93,12 @@ func mutateGitRepo(r *v1.AdmissionRequest) (result *operations.Result, err error } // Patch updates of the repo spec. -func populatePatchOperations(repoURL string, secretName string) []operations.PatchOperation { +func populatePatchOperations(repoURL string) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) - // If a prior secret exists, replace it - if secretName != "" { - patches = append(patches, operations.ReplacePatchOperation("/spec/secretRef/name", config.ZarfGitServerSecretName)) - } else { - // Otherwise, add the new secret - patches = append(patches, operations.AddPatchOperation("/spec/secretRef", SecretRef{Name: config.ZarfGitServerSecretName})) - } + newSecretRef := fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName} + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", newSecretRef)) return patches } diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go new file mode 100644 index 0000000000..cf56ac844a --- /dev/null +++ b/src/internal/agent/hooks/flux_test.go @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createFluxGitRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxGitRepo *flux.GitRepository) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(fluxGitRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestFluxMutationWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewGitRepositoryMutationHook(ctx, c)) + + tests := []admissionTest{ + { + name: "should be mutated", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Create, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://github.com/stefanprodan/podinfo.git", + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo-1646971829.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, + ), + }, + code: http.StatusOK, + }, + { + name: "should not mutate invalid git url", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.GitRepositorySpec{ + URL: "not-a-git-url", + }, + }), + patch: nil, + code: http.StatusInternalServerError, + errContains: AgentErrTransformGitURL, + }, + { + name: "should patch to same url and update secret if hostname matches", + admissionReq: createFluxGitRepoAdmissionRequest(t, v1.Update, &flux.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-mutate", + }, + Spec: flux.GitRepositorySpec{ + URL: "https://git-server.com/a-push-user/podinfo.git", + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "https://git-server.com/a-push-user/podinfo.git", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index cefb024df0..658c585b12 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -47,7 +47,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu pod, err := parsePod(r.Object.Raw) if err != nil { - return &operations.Result{Msg: err.Error()}, nil + return nil, fmt.Errorf(lang.AgentErrParsePod, err) } if pod.Labels != nil && pod.Labels["zarf-agent"] == "patched" { diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index eb3480b415..8eca704f59 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -41,12 +41,7 @@ func TestPodMutationWebhook(t *testing.T) { c := createTestClientWithZarfState(ctx, t, state) handler := admission.NewHandler().Serve(NewPodMutationHook(ctx, c)) - tests := []struct { - name string - admissionReq *v1.AdmissionRequest - expectedPatch []operations.PatchOperation - code int - }{ + tests := []admissionTest{ { name: "pod with label should be mutated", admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ @@ -65,7 +60,7 @@ func TestPodMutationWebhook(t *testing.T) { }, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/imagePullSecrets", []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, @@ -99,8 +94,8 @@ func TestPodMutationWebhook(t *testing.T) { Containers: []corev1.Container{{Image: "nginx"}}, }, }), - expectedPatch: nil, - code: http.StatusOK, + patch: nil, + code: http.StatusOK, }, { name: "pod with no labels should not error", @@ -112,7 +107,7 @@ func TestPodMutationWebhook(t *testing.T) { Containers: []corev1.Container{{Image: "nginx"}}, }, }), - expectedPatch: []operations.PatchOperation{ + patch: []operations.PatchOperation{ operations.ReplacePatchOperation( "/spec/imagePullSecrets", []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}}, @@ -134,16 +129,8 @@ func TestPodMutationWebhook(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() - resp := sendAdmissionRequest(t, tt.admissionReq, handler, tt.code) - if tt.expectedPatch == nil { - require.Empty(t, string(resp.Patch)) - } else { - expectedPatchJSON, err := json.Marshal(tt.expectedPatch) - require.NoError(t, err) - require.NotNil(t, resp) - require.True(t, resp.Allowed) - require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) - } + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) }) } } diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 909cd1f301..7cc1c912a6 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -11,6 +11,7 @@ import ( "net/http/httptest" "testing" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" @@ -21,6 +22,14 @@ import ( "k8s.io/client-go/kubernetes/fake" ) +type admissionTest struct { + name string + admissionReq *v1.AdmissionRequest + patch []operations.PatchOperation + code int + errContains string +} + func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { t.Helper() c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} @@ -42,7 +51,7 @@ func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *typ } // sendAdmissionRequest sends an admission request to the handler and returns the response. -func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc, code int) *v1.AdmissionResponse { +func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handler http.HandlerFunc) *httptest.ResponseRecorder { t.Helper() b, err := json.Marshal(&v1.AdmissionReview{ @@ -58,13 +67,32 @@ func sendAdmissionRequest(t *testing.T, admissionReq *v1.AdmissionRequest, handl rr := httptest.NewRecorder() handler.ServeHTTP(rr, req) - require.Equal(t, code, rr.Code) + return rr +} + +func verifyAdmission(t *testing.T, rr *httptest.ResponseRecorder, expected admissionTest) { + t.Helper() + + require.Equal(t, expected.code, rr.Code) var admissionReview v1.AdmissionReview - if rr.Code == http.StatusOK { - err = json.NewDecoder(rr.Body).Decode(&admissionReview) - require.NoError(t, err) + + err := json.NewDecoder(rr.Body).Decode(&admissionReview) + + if expected.errContains != "" { + require.Contains(t, admissionReview.Response.Result.Message, expected.errContains) + return } - return admissionReview.Response + resp := admissionReview.Response + require.NoError(t, err) + if expected.patch == nil { + require.Empty(t, string(resp.Patch)) + } else { + expectedPatchJSON, err := json.Marshal(expected.patch) + require.NoError(t, err) + require.NotNil(t, resp) + require.True(t, resp.Allowed) + require.JSONEq(t, string(expectedPatchJSON), string(resp.Patch)) + } } diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index b4e3109af7..4839073038 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -15,8 +15,8 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/message" - v1 "k8s.io/api/admission/v1" - meta "k8s.io/apimachinery/pkg/apis/meta/v1" + corev1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" ) @@ -56,7 +56,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { return } - var review v1.AdmissionReview + var review corev1.AdmissionReview if _, _, err := h.decoder.Decode(body, nil, &review); err != nil { http.Error(w, fmt.Sprintf(lang.AgentErrCouldNotDeserializeReq, err), http.StatusBadRequest) return @@ -68,27 +68,41 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { } result, err := hook.Execute(review.Request) + admissionMeta := metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "AdmissionReview", + } if err != nil { message.Warnf("%s: %s", lang.AgentErrBindHandler, err.Error()) + admissionResponse := corev1.AdmissionReview{ + TypeMeta: admissionMeta, + Response: &corev1.AdmissionResponse{ + Result: &metav1.Status{Message: err.Error(), Status: string(metav1.StatusReasonInternalError)}, + }, + } + jsonResponse, err := json.Marshal(admissionResponse) + if err != nil { + message.WarnErr(err, lang.AgentErrMarshalResponse) + http.Error(w, lang.AgentErrMarshalResponse, http.StatusInternalServerError) + return + } w.WriteHeader(http.StatusInternalServerError) + w.Write(jsonResponse) return } - admissionResponse := v1.AdmissionReview{ - TypeMeta: meta.TypeMeta{ - APIVersion: v1.SchemeGroupVersion.String(), - Kind: "AdmissionReview", - }, - Response: &v1.AdmissionResponse{ + admissionResponse := corev1.AdmissionReview{ + TypeMeta: admissionMeta, + Response: &corev1.AdmissionResponse{ UID: review.Request.UID, Allowed: result.Allowed, - Result: &meta.Status{Message: result.Msg}, + Result: &metav1.Status{Message: result.Msg}, }, } // Set the patch operations for mutating admission if len(result.PatchOps) > 0 { - jsonPatchType := v1.PatchTypeJSONPatch + jsonPatchType := corev1.PatchTypeJSONPatch patchBytes, err := json.Marshal(result.PatchOps) if err != nil { message.WarnErr(err, lang.AgentErrMarshallJSONPatch) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 7b07b1445d..2c56428fd7 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -30,7 +30,7 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) - fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook() + fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) argocdApplicationMutation := hooks.NewApplicationMutationHook() argocdRepositoryMutation := hooks.NewRepositoryMutationHook() From 6c9d5dad2a201941e239f538f39a97a133cdbf5a Mon Sep 17 00:00:00 2001 From: schristoff <28318173+schristoff@users.noreply.github.com> Date: Thu, 23 May 2024 09:11:27 -0600 Subject: [PATCH 019/132] chore: fix codecov (#2538) Signed-off-by: schristoff <28318173+schristoff@users.noreply.github.com> Co-authored-by: razzle Co-authored-by: schristoff <167717759+schristoff-du@users.noreply.github.com> Co-authored-by: Lucas Rodriguez --- .github/workflows/test-unit.yml | 15 ++++++--------- .gitignore | 1 + Makefile | 4 +--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index f3425c1098..90429169f1 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -25,17 +25,14 @@ jobs: steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Run coverage - run: go test -race -coverprofile=coverage.out -covermode=atomic - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - name: Setup golang uses: ./.github/actions/golang - name: Run unit tests - run: make test-unit \ No newline at end of file + run: make test-unit + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 + with: + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 493e7381c2..2a68c38107 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ zarf-sbom/ test-*.txt __debug_bin .netlify +coverage.out diff --git a/Makefile b/Makefile index 5683962afc..1924780d4c 100644 --- a/Makefile +++ b/Makefile @@ -208,9 +208,7 @@ test-upgrade: ## Run the Zarf CLI E2E tests for an external registry and cluster .PHONY: test-unit test-unit: ## Run unit tests - cd src/pkg && go test ./... -failfast -v -timeout 30m - cd src/internal && go test ./... -failfast -v timeout 30m - cd src/extensions/bigbang && go test ./. -failfast -v timeout 30m + go test -failfast -v -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v '^github.com/defenseunicorns/zarf/src/test' | grep -v 'github.com/defenseunicorns/zarf/src/extensions/bigbang/test') # INTERNAL: used to test that a dev has ran `make docs-and-schema` in their PR test-docs-and-schema: From 31d56e4fb4706dcf6c50a15fa030b7f934a4c372 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 23 May 2024 10:41:12 -0500 Subject: [PATCH 020/132] test: creator.ComposeComponents (#2537) Relates to #2512 --- src/pkg/packager/creator/compose_test.go | 175 +++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 src/pkg/packager/creator/compose_test.go diff --git a/src/pkg/packager/creator/compose_test.go b/src/pkg/packager/creator/compose_test.go new file mode 100644 index 0000000000..7d1310bf3e --- /dev/null +++ b/src/pkg/packager/creator/compose_test.go @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package creator contains functions for creating Zarf packages. +package creator + +import ( + "testing" + + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestComposeComponents(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + pkg types.ZarfPackage + flavor string + expectedPkg types.ZarfPackage + expectedErr string + }{ + { + name: "filter by architecture match", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + }, + }, + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + {Name: "component2"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by architecture mismatch", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "amd64", + }, + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Cluster: types.ZarfComponentOnlyCluster{ + Architecture: "arm64", + }, + }, + }, + }, + }, + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by flavor match", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + {Name: "component2"}, + }, + }, + expectedErr: "", + }, + { + name: "filter by flavor mismatch", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{Architecture: "amd64"}, + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + { + Name: "component2", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "special", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + {Name: "component1"}, + }, + }, + expectedErr: "", + }, + { + name: "no architecture set error", + pkg: types.ZarfPackage{ + Components: []types.ZarfComponent{ + { + Name: "component1", + Only: types.ZarfComponentOnlyTarget{ + Flavor: "default", + }, + }, + }, + }, + flavor: "default", + expectedPkg: types.ZarfPackage{}, + expectedErr: "cannot build import chain: architecture must be provided", + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + pkg, _, err := ComposeComponents(tt.pkg, tt.flavor) + + if tt.expectedErr == "" { + require.NoError(t, err) + require.Equal(t, tt.expectedPkg.Components, pkg.Components) + return + } + + require.EqualError(t, err, tt.expectedErr) + require.Empty(t, tt.expectedPkg) + }) + } +} From f445e6d858a6afba3f033bb389932beef410f771 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 18:42:09 +0200 Subject: [PATCH 021/132] refactor: remove use of k8s serivce account (#2544) ## Description This change removes the k8s service account code and replaces it with direct use of the client set instead. ## Related Issue Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: razzle --- src/pkg/cluster/state.go | 24 ++++++++++++++- src/pkg/k8s/sa.go | 64 ---------------------------------------- 2 files changed, 23 insertions(+), 65 deletions(-) delete mode 100644 src/pkg/k8s/sa.go diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 39efa83008..83af027217 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -13,6 +13,7 @@ import ( "github.com/fatih/color" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/pkg/helpers" @@ -115,7 +116,28 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // The default SA is required for pods to start properly. saCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) defer cancel() - if _, err := c.WaitForServiceAccount(saCtx, ZarfNamespaceName, "default"); err != nil { + err = func(ctx context.Context, ns, name string) error { + timer := time.NewTimer(0) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) + case <-timer.C: + _, err := c.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return err + } + if kerrors.IsNotFound(err) { + c.Log("Service account %s/%s not found, retrying...", ns, name) + timer.Reset(1 * time.Second) + continue + } + return nil + } + } + }(saCtx, ZarfNamespaceName, "default") + if err != nil { return fmt.Errorf("unable get default Zarf service account: %w", err) } diff --git a/src/pkg/k8s/sa.go b/src/pkg/k8s/sa.go deleted file mode 100644 index 38b7624130..0000000000 --- a/src/pkg/k8s/sa.go +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "fmt" - "time" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetAllServiceAccounts returns a list of services accounts for all namespaces. -func (k *K8s) GetAllServiceAccounts(ctx context.Context) (*corev1.ServiceAccountList, error) { - return k.GetServiceAccounts(ctx, corev1.NamespaceAll) -} - -// GetServiceAccounts returns a list of service accounts in a given namespace. -func (k *K8s) GetServiceAccounts(ctx context.Context, namespace string) (*corev1.ServiceAccountList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).List(ctx, metaOptions) -} - -// GetServiceAccount returns a single service account by namespace and name. -func (k *K8s) GetServiceAccount(ctx context.Context, namespace, name string) (*corev1.ServiceAccount, error) { - metaOptions := metav1.GetOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metaOptions) -} - -// UpdateServiceAccount updates the given service account in the cluster. -func (k *K8s) UpdateServiceAccount(ctx context.Context, svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { - metaOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(ctx, svcAccount, metaOptions) -} - -// WaitForServiceAccount waits for a service account to be created in the cluster. -func (k *K8s) WaitForServiceAccount(ctx context.Context, ns, name string) (*corev1.ServiceAccount, error) { - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) - case <-timer.C: - sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) - if err == nil { - return sa, nil - } - - if errors.IsNotFound(err) { - k.Log("Service account %s/%s not found, retrying...", ns, name) - } else { - return nil, fmt.Errorf("error getting service account %s/%s: %w", ns, name, err) - } - - timer.Reset(1 * time.Second) - } - } -} From 9d444526e9c2e4f7b60ceba7fbd03d9c129e3e3f Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 19:11:55 +0200 Subject: [PATCH 022/132] refactor: remove use of k8s service (#2543) ## Description This change removes the k8s service code and replaces it with direct use of the client set instead. ## Related Issue Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: razzle --- src/pkg/cluster/injector.go | 50 ++++++--- src/pkg/cluster/tunnel.go | 89 +++++++++++++--- src/pkg/cluster/tunnel_test.go | 127 ++++++++++++++++++++++ src/pkg/k8s/services.go | 181 -------------------------------- src/pkg/k8s/tunnel.go | 6 +- src/pkg/packager/deploy.go | 40 +++++-- src/pkg/packager/deploy_test.go | 47 +++++++++ 7 files changed, 323 insertions(+), 217 deletions(-) create mode 100644 src/pkg/cluster/tunnel_test.go delete mode 100644 src/pkg/k8s/services.go diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index b2a8569318..b30ab34314 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -23,6 +23,7 @@ import ( "github.com/google/go-containerregistry/pkg/crane" "github.com/mholt/archiver/v3" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -156,7 +157,11 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { } // Remove the injector service - return c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") + err := c.Clientset.CoreV1().Services(ZarfNamespaceName).Delete(ctx, "zarf-injector", metav1.DeleteOptions{}) + if err != nil { + return err + } + return nil } func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string, spinner *message.Spinner) ([]transform.Image, error) { @@ -306,20 +311,37 @@ func (c *Cluster) createInjectorConfigMap(ctx context.Context, binaryPath string } func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { - service := c.GenerateService(ZarfNamespaceName, "zarf-injector") - - service.Spec.Type = corev1.ServiceTypeNodePort - service.Spec.Ports = append(service.Spec.Ports, corev1.ServicePort{ - Port: int32(5000), - }) - service.Spec.Selector = map[string]string{ - "app": "zarf-injector", + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "zarf-injector", + Namespace: ZarfNamespaceName, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + Port: int32(5000), + }, + }, + Selector: map[string]string{ + "app": "zarf-injector", + }, + }, } - - // Attempt to purse the service silently - _ = c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") - - return c.CreateService(ctx, service) + // TODO: Replace with create or update + err := c.Clientset.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return nil, err + } + svc, err = c.Clientset.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + return svc, nil } // buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index c960fb1e8e..424881b2fc 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -7,14 +7,18 @@ package cluster import ( "context" "fmt" + "net/url" + "strconv" "strings" - "github.com/defenseunicorns/zarf/src/types" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" - v1 "k8s.io/api/core/v1" + "github.com/defenseunicorns/zarf/src/types" ) // Zarf specific connect strings @@ -56,25 +60,30 @@ func NewTunnelInfo(namespace, resourceType, resourceName, urlSuffix string, loca // PrintConnectTable will print a table of all Zarf connect matches found in the cluster. func (c *Cluster) PrintConnectTable(ctx context.Context) error { - list, err := c.GetServicesByLabelExists(ctx, v1.NamespaceAll, config.ZarfConnectLabelName) + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Operator: metav1.LabelSelectorOpExists, + Key: config.ZarfConnectLabelName, + }}, + }) + if err != nil { + return err + } + serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { return err } connections := make(types.ConnectStrings) - - for _, svc := range list.Items { + for _, svc := range serviceList.Items { name := svc.Labels[config.ZarfConnectLabelName] - // Add the connectString for processing later in the deployment. connections[name] = types.ConnectString{ Description: svc.Annotations[config.ZarfConnectAnnotationDescription], URL: svc.Annotations[config.ZarfConnectAnnotationURL], } } - message.PrintConnectStringTable(connections) - return nil } @@ -151,11 +160,15 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInf return "", tunnel, err } } else { - svcInfo, err := c.ServiceInfoFromNodePortURL(ctx, registryInfo.Address) + serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{}) + if err != nil { + return "", nil, err + } + namespace, name, port, err := serviceInfoFromNodePortURL(serviceList.Items, registryInfo.Address) // If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource if err == nil { - if tunnel, err = c.NewTunnel(svcInfo.Namespace, k8s.SvcResource, svcInfo.Name, "", 0, svcInfo.Port); err != nil { + if tunnel, err = c.NewTunnel(namespace, k8s.SvcResource, name, "", 0, port); err != nil { return "", tunnel, err } } @@ -179,14 +192,23 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu message.Debugf("Looking for a Zarf Connect Label in the cluster") - matches, err := c.GetServicesByLabel(ctx, "", config.ZarfConnectLabelName, name) + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + config.ZarfConnectLabelName: name, + }, + }) if err != nil { - return zt, fmt.Errorf("unable to lookup the service: %w", err) + return TunnelInfo{}, err + } + listOpts := metav1.ListOptions{LabelSelector: selector.String()} + serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, listOpts) + if err != nil { + return TunnelInfo{}, err } - if len(matches.Items) > 0 { + if len(serviceList.Items) > 0 { // If there is a match, use the first one as these are supposed to be unique. - svc := matches.Items[0] + svc := serviceList.Items[0] // Reset based on the matched params. zt.resourceType = k8s.SvcResource @@ -209,3 +231,42 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu return zt, nil } + +// TODO: Refactor to use netip.AddrPort instead of a string for nodePortURL. +func serviceInfoFromNodePortURL(services []corev1.Service, nodePortURL string) (string, string, int, error) { + // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) + parsedURL, err := url.Parse(nodePortURL) + if err != nil { + parsedURL, err = url.Parse("scheme://" + nodePortURL) + if err != nil { + return "", "", 0, err + } + } + + // Match hostname against localhost ip/hostnames + hostname := parsedURL.Hostname() + if hostname != helpers.IPV4Localhost && hostname != "localhost" { + return "", "", 0, fmt.Errorf("node port services should be on localhost") + } + + // Get the node port from the nodeportURL. + nodePort, err := strconv.Atoi(parsedURL.Port()) + if err != nil { + return "", "", 0, err + } + if nodePort < 30000 || nodePort > 32767 { + return "", "", 0, fmt.Errorf("node port services should use the port range 30000-32767") + } + + for _, svc := range services { + if svc.Spec.Type == "NodePort" { + for _, port := range svc.Spec.Ports { + if int(port.NodePort) == nodePort { + return svc.Namespace, svc.Name, int(port.Port), nil + } + } + } + } + + return "", "", 0, fmt.Errorf("no matching node port services found") +} diff --git a/src/pkg/cluster/tunnel_test.go b/src/pkg/cluster/tunnel_test.go new file mode 100644 index 0000000000..41c4aac407 --- /dev/null +++ b/src/pkg/cluster/tunnel_test.go @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cluster + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestServiceInfoFromNodePortURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + services []corev1.Service + nodePortURL string + expectedErr string + expectedNamespace string + expectedName string + expectedPort int + }{ + { + name: "invalid node port", + nodePortURL: "example.com", + expectedErr: "node port services should be on localhost", + }, + { + name: "invalid port range", + nodePortURL: "http://localhost:8080", + expectedErr: "node port services should use the port range 30000-32767", + }, + { + name: "no services", + nodePortURL: "http://localhost:30001", + services: []corev1.Service{}, + expectedErr: "no matching node port services found", + }, + { + name: "found serivce", + nodePortURL: "http://localhost:30001", + services: []corev1.Service{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "wrong-type", + Namespace: "wrong-type", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeClusterIP, + Ports: []corev1.ServicePort{ + { + Port: 1111, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "wrong-node-port", + Namespace: "wrong-node-port", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: 30002, + Port: 2222, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "good-service", + Namespace: "good-namespace", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: 30001, + Port: 3333, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "too-late", + Namespace: "too-late", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: 30001, + Port: 4444, + }, + }, + }, + }, + }, + expectedNamespace: "good-namespace", + expectedName: "good-service", + expectedPort: 3333, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + namespace, name, port, err := serviceInfoFromNodePortURL(tt.services, tt.nodePortURL) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.expectedNamespace, namespace) + require.Equal(t, tt.expectedName, name) + require.Equal(t, tt.expectedPort, port) + }) + } +} diff --git a/src/pkg/k8s/services.go b/src/pkg/k8s/services.go deleted file mode 100644 index 63b847d413..0000000000 --- a/src/pkg/k8s/services.go +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "fmt" - "net/url" - "regexp" - "strconv" - - "github.com/defenseunicorns/pkg/helpers" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// See https://regex101.com/r/OWVfAO/1. -const serviceURLPattern = `^(?P[^\.]+)\.(?P[^\.]+)\.svc\.cluster\.local$` - -// ServiceInfo contains information necessary for connecting to a cluster service. -type ServiceInfo struct { - Namespace string - Name string - Port int -} - -// ReplaceService deletes and re-creates a service. -func (k *K8s) ReplaceService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { - if err := k.DeleteService(ctx, service.Namespace, service.Name); err != nil { - return nil, err - } - - return k.CreateService(ctx, service) -} - -// GenerateService returns a K8s service struct without writing to the cluster. -func (k *K8s) GenerateService(namespace, name string) *corev1.Service { - service := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Annotations: make(Labels), - Labels: make(Labels), - }, - } - - return service -} - -// DeleteService removes a service from the cluster by namespace and name. -func (k *K8s) DeleteService(ctx context.Context, namespace, name string) error { - return k.Clientset.CoreV1().Services(namespace).Delete(ctx, name, metav1.DeleteOptions{}) -} - -// CreateService creates the given service in the cluster. -func (k *K8s) CreateService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { - createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Services(service.Namespace).Create(ctx, service, createOptions) -} - -// GetService returns a Kubernetes service resource in the provided namespace with the given name. -func (k *K8s) GetService(ctx context.Context, namespace, serviceName string) (*corev1.Service, error) { - return k.Clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{}) -} - -// GetServices returns a list of services in the provided namespace. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServices(ctx context.Context, namespace string) (*corev1.ServiceList, error) { - return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{}) -} - -// GetServicesByLabel returns a list of matched services given a label and value. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabel(ctx context.Context, namespace, label, value string) (*corev1.ServiceList, error) { - // Create the selector and add the requirement - labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchLabels: Labels{ - label: value, - }, - }) - - // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) -} - -// GetServicesByLabelExists returns a list of matched services given a label. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabelExists(ctx context.Context, namespace, label string) (*corev1.ServiceList, error) { - // Create the selector and add the requirement - labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{{ - Key: label, - Operator: metav1.LabelSelectorOpExists, - }}, - }) - - // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) -} - -// ServiceInfoFromNodePortURL takes a nodePortURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: -// Example nodePortURL: 127.0.0.1:{PORT}. -func (k *K8s) ServiceInfoFromNodePortURL(ctx context.Context, nodePortURL string) (*ServiceInfo, error) { - // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) - parsedURL, err := url.Parse(nodePortURL) - if err != nil { - parsedURL, err = url.Parse("scheme://" + nodePortURL) - if err != nil { - return nil, err - } - } - - // Match hostname against localhost ip/hostnames - hostname := parsedURL.Hostname() - if hostname != helpers.IPV4Localhost && hostname != "localhost" { - return nil, fmt.Errorf("node port services should be on localhost") - } - - // Get the node port from the nodeportURL. - nodePort, err := strconv.Atoi(parsedURL.Port()) - if err != nil { - return nil, err - } - if nodePort < 30000 || nodePort > 32767 { - return nil, fmt.Errorf("node port services should use the port range 30000-32767") - } - - services, err := k.GetServices(ctx, "") - if err != nil { - return nil, err - } - - for _, svc := range services.Items { - if svc.Spec.Type == "NodePort" { - for _, port := range svc.Spec.Ports { - if int(port.NodePort) == nodePort { - return &ServiceInfo{ - Namespace: svc.Namespace, - Name: svc.Name, - Port: int(port.Port), - }, nil - } - } - } - } - - return nil, fmt.Errorf("no matching node port services found") -} - -// ServiceInfoFromServiceURL takes a serviceURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: -// Example serviceURL: http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}. -func ServiceInfoFromServiceURL(serviceURL string) (*ServiceInfo, error) { - parsedURL, err := url.Parse(serviceURL) - if err != nil { - return nil, err - } - - // Get the remote port from the serviceURL. - remotePort, err := strconv.Atoi(parsedURL.Port()) - if err != nil { - return nil, err - } - - // Match hostname against local cluster service format. - pattern := regexp.MustCompile(serviceURLPattern) - get, err := helpers.MatchRegex(pattern, parsedURL.Hostname()) - - // If incomplete match, return an error. - if err != nil { - return nil, err - } - - return &ServiceInfo{ - Namespace: get("namespace"), - Name: get("name"), - Port: remotePort, - }, nil -} diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go index 6c4f46e0cf..df49ac667a 100644 --- a/src/pkg/k8s/tunnel.go +++ b/src/pkg/k8s/tunnel.go @@ -14,9 +14,11 @@ import ( "sync" "time" - "github.com/defenseunicorns/pkg/helpers" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" + + "github.com/defenseunicorns/pkg/helpers" ) // Global lock to synchronize port selections. @@ -248,7 +250,7 @@ func (tunnel *Tunnel) getAttachablePodForResource(ctx context.Context) (string, // getAttachablePodForService will find an active pod associated with the Service and return the pod name. func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, error) { - service, err := tunnel.kube.GetService(ctx, tunnel.namespace, tunnel.resourceName) + service, err := tunnel.kube.Clientset.CoreV1().Services(tunnel.namespace).Get(ctx, tunnel.resourceName, metav1.GetOptions{}) if err != nil { return "", fmt.Errorf("unable to find the service: %w", err) } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index a7c516fb43..2805c96620 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -7,8 +7,10 @@ package packager import ( "context" "fmt" + "net/url" "os" "path/filepath" + "regexp" "runtime" "strconv" "strings" @@ -511,13 +513,11 @@ func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, // Create an anonymous function to push the repo to the Zarf git server tryPush := func() error { gitClient := git.New(p.state.GitServer) - svcInfo, _ := k8s.ServiceInfoFromServiceURL(gitClient.Server.Address) - - var err error - var tunnel *k8s.Tunnel + namespace, name, port, err := serviceInfoFromServiceURL(gitClient.Server.Address) // If this is a service (svcInfo is not nil), create a port-forward tunnel to that resource - if svcInfo != nil { + // TODO: Find a better way as ignoring the error is not a good solution to decide to port forward. + if err == nil { if !p.isConnectedToCluster() { connectCtx, cancel := context.WithTimeout(ctx, 5*time.Second) defer cancel() @@ -527,7 +527,7 @@ func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, } } - tunnel, err = p.cluster.NewTunnel(svcInfo.Namespace, k8s.SvcResource, svcInfo.Name, "", 0, svcInfo.Port) + tunnel, err := p.cluster.NewTunnel(namespace, k8s.SvcResource, name, "", 0, port) if err != nil { return err } @@ -704,3 +704,31 @@ func (p *Packager) printTablesForDeployment(ctx context.Context, componentsToDep } } } + +// ServiceInfoFromServiceURL takes a serviceURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: +// Example serviceURL: http://{SERVICE_NAME}.{NAMESPACE}.svc.cluster.local:{PORT}. +func serviceInfoFromServiceURL(serviceURL string) (string, string, int, error) { + parsedURL, err := url.Parse(serviceURL) + if err != nil { + return "", "", 0, err + } + + // Get the remote port from the serviceURL. + remotePort, err := strconv.Atoi(parsedURL.Port()) + if err != nil { + return "", "", 0, err + } + + // Match hostname against local cluster service format. + pattern, err := regexp.Compile(`^(?P[^\.]+)\.(?P[^\.]+)\.svc\.cluster\.local$`) + if err != nil { + return "", "", 0, err + } + get, err := helpers.MatchRegex(pattern, parsedURL.Hostname()) + + // If incomplete match, return an error. + if err != nil { + return "", "", 0, err + } + return get("namespace"), get("name"), remotePort, nil +} diff --git a/src/pkg/packager/deploy_test.go b/src/pkg/packager/deploy_test.go index d79f19f1a5..b4b7cb9df2 100644 --- a/src/pkg/packager/deploy_test.go +++ b/src/pkg/packager/deploy_test.go @@ -10,6 +10,7 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/variables" "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" ) func TestGenerateValuesOverrides(t *testing.T) { @@ -227,3 +228,49 @@ func TestGenerateValuesOverrides(t *testing.T) { }) } } + +func TestServiceInfoFromServiceURL(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + serviceURL string + expectedErr string + expectedNamespace string + expectedName string + expectedPort int + }{ + { + name: "no port", + serviceURL: "http://example.com", + expectedErr: `strconv.Atoi: parsing "": invalid syntax`, + }, + { + name: "normal domain", + serviceURL: "http://example.com:8080", + expectedErr: "unable to match against example.com", + }, + { + name: "valid url", + serviceURL: "http://foo.bar.svc.cluster.local:9090", + expectedNamespace: "bar", + expectedName: "foo", + expectedPort: 9090, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + namespace, name, port, err := serviceInfoFromServiceURL(tt.serviceURL) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } + require.NoError(t, err) + require.Equal(t, tt.expectedNamespace, namespace) + require.Equal(t, tt.expectedName, name) + require.Equal(t, tt.expectedPort, port) + }) + } +} From 047cb35bd66744b2ab22aa76dad190e6cef5c1bf Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 19:46:22 +0200 Subject: [PATCH 023/132] refactor: remove use of k8s configmap (#2541) --- src/pkg/cluster/injector.go | 73 ++++++++++++++++++++++++++----------- src/pkg/k8s/configmap.go | 62 ------------------------------- 2 files changed, 51 insertions(+), 84 deletions(-) delete mode 100644 src/pkg/k8s/configmap.go diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index b30ab34314..008df3915a 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -151,13 +151,24 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { } // Remove the configmaps - labelMatch := map[string]string{"zarf-injector": "payload"} - if err := c.DeleteConfigMapsByLabel(ctx, ZarfNamespaceName, labelMatch); err != nil { + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + "zarf-injector": "payload", + }, + }) + if err != nil { + return err + } + listOpts := metav1.ListOptions{ + LabelSelector: selector.String(), + } + err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts) + if err != nil { return err } // Remove the injector service - err := c.Clientset.CoreV1().Services(ZarfNamespaceName).Delete(ctx, "zarf-injector", metav1.DeleteOptions{}) + err = c.Clientset.CoreV1().Services(ZarfNamespaceName).Delete(ctx, "zarf-injector", metav1.DeleteOptions{}) if err != nil { return err } @@ -231,16 +242,26 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta // Create a cat-friendly filename fileName := fmt.Sprintf("zarf-payload-%03d", idx) - // Store the binary data - configData := map[string][]byte{ - fileName: data, - } - spinner.Updatef("Adding archive binary configmap %d of %d to the cluster", idx+1, chunkCount) // Attempt to create the configmap in the cluster - if _, err = c.ReplaceConfigmap(ctx, ZarfNamespaceName, fileName, configData); err != nil { - return configMaps, "", err + // TODO: Replace with create or update. + err := c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, fileName, metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return nil, "", err + } + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: fileName, + Namespace: ZarfNamespaceName, + }, + BinaryData: map[string][]byte{ + fileName: data, + }, + } + _, err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Create(ctx, cm, metav1.CreateOptions{}) + if err != nil { + return nil, "", err } // Add the configmap to the configmaps slice for later usage in the pod @@ -291,22 +312,30 @@ func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Im } func (c *Cluster) createInjectorConfigMap(ctx context.Context, binaryPath string) error { - var err error - configData := make(map[string][]byte) - - // Add the injector binary data to the configmap - if configData["zarf-injector"], err = os.ReadFile(binaryPath); err != nil { + name := "rust-binary" + // TODO: Replace with a create or update. + err := c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, name, metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { return err } - - // Try to delete configmap silently - _ = c.DeleteConfigmap(ctx, ZarfNamespaceName, "rust-binary") - - // Attempt to create the configmap in the cluster - if _, err = c.CreateConfigmap(ctx, ZarfNamespaceName, "rust-binary", configData); err != nil { + b, err := os.ReadFile(binaryPath) + if err != nil { + return err + } + configData := map[string][]byte{ + "zarf-injector": b, + } + configMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ZarfNamespaceName, + }, + BinaryData: configData, + } + _, err = c.Clientset.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, configMap, metav1.CreateOptions{}) + if err != nil { return err } - return nil } diff --git a/src/pkg/k8s/configmap.go b/src/pkg/k8s/configmap.go deleted file mode 100644 index 57a72c65ae..0000000000 --- a/src/pkg/k8s/configmap.go +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// ReplaceConfigmap deletes and recreates a configmap. -func (k *K8s) ReplaceConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { - if err := k.DeleteConfigmap(ctx, namespace, name); err != nil { - return nil, err - } - return k.CreateConfigmap(ctx, namespace, name, data) -} - -// CreateConfigmap applies a configmap to the cluster. -func (k *K8s) CreateConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: make(Labels), - }, - BinaryData: data, - } - - createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, createOptions) -} - -// DeleteConfigmap deletes a configmap by name. -func (k *K8s) DeleteConfigmap(ctx context.Context, namespace, name string) error { - namespaceConfigmap := k.Clientset.CoreV1().ConfigMaps(namespace) - - err := namespaceConfigmap.Delete(ctx, name, metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("error deleting the configmap: %w", err) - } - - return nil -} - -// DeleteConfigMapsByLabel deletes a configmap by label(s). -func (k *K8s) DeleteConfigMapsByLabel(ctx context.Context, namespace string, labels Labels) error { - labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchLabels: labels, - }) - metaOptions := metav1.DeleteOptions{} - listOptions := metav1.ListOptions{ - LabelSelector: labelSelector.String(), - } - - return k.Clientset.CoreV1().ConfigMaps(namespace).DeleteCollection(ctx, metaOptions, listOptions) -} From 249e91a14cd7db542f1f0e9d91454a8ac4fe1510 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 20:59:55 +0200 Subject: [PATCH 024/132] refactor: remove use of k8s hpa (#2542) ## Description This change removes the k8s hpa code and replaces it with direct use of the client set instead. ## Related Issue Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: schristoff <167717759+schristoff-du@users.noreply.github.com> Co-authored-by: razzle --- src/pkg/cluster/zarf.go | 20 +++++------------ src/pkg/cluster/zarf_test.go | 43 +++++++++++++++++++++++++++++++++--- src/pkg/k8s/hpa.go | 36 ------------------------------ 3 files changed, 46 insertions(+), 53 deletions(-) delete mode 100644 src/pkg/k8s/hpa.go diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 4d01891f9d..e37810269c 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -222,39 +222,31 @@ func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg types.ZarfPac // EnableRegHPAScaleDown enables the HPA scale down for the Zarf Registry. func (c *Cluster) EnableRegHPAScaleDown(ctx context.Context) error { - hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") + hpa, err := c.Clientset.AutoscalingV2().HorizontalPodAutoscalers(ZarfNamespaceName).Get(ctx, "zarf-docker-registry", metav1.GetOptions{}) if err != nil { return err } - - // Enable HPA scale down. policy := autoscalingV2.MinChangePolicySelect hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy - - // Save the HPA changes. - if _, err = c.UpdateHPA(ctx, hpa); err != nil { + _, err = c.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(ctx, hpa, metav1.UpdateOptions{}) + if err != nil { return err } - return nil } // DisableRegHPAScaleDown disables the HPA scale down for the Zarf Registry. func (c *Cluster) DisableRegHPAScaleDown(ctx context.Context) error { - hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") + hpa, err := c.Clientset.AutoscalingV2().HorizontalPodAutoscalers(ZarfNamespaceName).Get(ctx, "zarf-docker-registry", metav1.GetOptions{}) if err != nil { return err } - - // Disable HPA scale down. policy := autoscalingV2.DisabledPolicySelect hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy - - // Save the HPA changes. - if _, err = c.UpdateHPA(ctx, hpa); err != nil { + _, err = c.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(ctx, hpa, metav1.UpdateOptions{}) + if err != nil { return err } - return nil } diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index 0d40f58bfa..c83e680628 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -10,13 +10,15 @@ import ( "strings" "testing" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" + autoscalingv2 "k8s.io/api/autoscaling/v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/k8s" + "github.com/defenseunicorns/zarf/src/types" ) // TestPackageSecretNeedsWait verifies that Zarf waits for webhooks to complete correctly. @@ -246,3 +248,38 @@ func TestGetDeployedPackage(t *testing.T) { require.NoError(t, err) require.ElementsMatch(t, packages, actualList) } + +func TestRegistryHPA(t *testing.T) { + ctx := context.Background() + cs := fake.NewSimpleClientset() + hpa := autoscalingv2.HorizontalPodAutoscaler{ + ObjectMeta: metav1.ObjectMeta{ + Name: "zarf-docker-registry", + Namespace: ZarfNamespaceName, + }, + Spec: autoscalingv2.HorizontalPodAutoscalerSpec{ + Behavior: &autoscalingv2.HorizontalPodAutoscalerBehavior{ + ScaleDown: &autoscalingv2.HPAScalingRules{}, + }, + }, + } + _, err := cs.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, &hpa, metav1.CreateOptions{}) + require.NoError(t, err) + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + }, + } + + err = c.EnableRegHPAScaleDown(ctx) + require.NoError(t, err) + enableHpa, err := cs.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Get(ctx, hpa.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, autoscalingv2.MinChangePolicySelect, *enableHpa.Spec.Behavior.ScaleDown.SelectPolicy) + + err = c.DisableRegHPAScaleDown(ctx) + require.NoError(t, err) + disableHpa, err := cs.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Get(ctx, hpa.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, autoscalingv2.DisabledPolicySelect, *disableHpa.Spec.Behavior.ScaleDown.SelectPolicy) +} diff --git a/src/pkg/k8s/hpa.go b/src/pkg/k8s/hpa.go deleted file mode 100644 index 902f5b1d29..0000000000 --- a/src/pkg/k8s/hpa.go +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - - autoscalingV2 "k8s.io/api/autoscaling/v2" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetAllHPAs returns a list of horizontal pod autoscalers for all namespaces. -func (k *K8s) GetAllHPAs(ctx context.Context) (*autoscalingV2.HorizontalPodAutoscalerList, error) { - return k.GetHPAs(ctx, corev1.NamespaceAll) -} - -// GetHPAs returns a list of horizontal pod autoscalers in a given namespace. -func (k *K8s) GetHPAs(ctx context.Context, namespace string) (*autoscalingV2.HorizontalPodAutoscalerList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(ctx, metaOptions) -} - -// GetHPA returns a single horizontal pod autoscaler by namespace and name. -func (k *K8s) GetHPA(ctx context.Context, namespace, name string) (*autoscalingV2.HorizontalPodAutoscaler, error) { - metaOptions := metav1.GetOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(ctx, name, metaOptions) -} - -// UpdateHPA updates the given horizontal pod autoscaler in the cluster. -func (k *K8s) UpdateHPA(ctx context.Context, hpa *autoscalingV2.HorizontalPodAutoscaler) (*autoscalingV2.HorizontalPodAutoscaler, error) { - metaOptions := metav1.UpdateOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(ctx, hpa, metaOptions) -} From 2a8bfe2c009408201928c3030aaebbb81479ab07 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 21:30:51 +0200 Subject: [PATCH 025/132] test: add secrets tests (#2540) ## Description Adds tests to clusters secrets. ## Related Issue Fixes #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: razzle Co-authored-by: schristoff <167717759+schristoff-du@users.noreply.github.com> --- src/pkg/cluster/secrets.go | 2 + src/pkg/cluster/secrets_test.go | 76 +++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 src/pkg/cluster/secrets_test.go diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 4b58d841d0..8bd604250c 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -75,6 +75,7 @@ func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo typ } // UpdateZarfManagedImageSecrets updates all Zarf-managed image secrets in all namespaces based on state +// TODO: Refactor to return errors properly. func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() @@ -109,6 +110,7 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type } // UpdateZarfManagedGitSecrets updates all Zarf-managed git secrets in all namespaces based on state +// TODO: Refactor to return errors properly. func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() diff --git a/src/pkg/cluster/secrets_test.go b/src/pkg/cluster/secrets_test.go new file mode 100644 index 0000000000..a3c4a3a35a --- /dev/null +++ b/src/pkg/cluster/secrets_test.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cluster + +import ( + "testing" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/defenseunicorns/zarf/src/pkg/k8s" + "github.com/defenseunicorns/zarf/src/types" +) + +func TestGenerateRegistryPullCreds(t *testing.T) { + c := &Cluster{} + ri := types.RegistryInfo{ + PushUsername: "push-user", + PushPassword: "push-password", + PullUsername: "pull-user", + PullPassword: "pull-password", + Address: "example.com", + } + secret := c.GenerateRegistryPullCreds("foo", "bar", ri) + expectedSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + Labels: map[string]string{ + k8s.ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": []byte(`{"auths":{"example.com":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="}}}`), + }, + } + require.Equal(t, expectedSecret, *secret) +} + +func TestGenerateGitPullCreds(t *testing.T) { + c := &Cluster{} + gi := types.GitServerInfo{ + PushUsername: "push-user", + PushPassword: "push-password", + PullUsername: "pull-user", + PullPassword: "pull-password", + } + secret := c.GenerateGitPullCreds("foo", "bar", gi) + expectedSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + Labels: map[string]string{ + k8s.ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{}, + StringData: map[string]string{ + "username": "pull-user", + "password": "pull-password", + }, + } + require.Equal(t, expectedSecret, *secret) +} From f003c949568980d78cd54936390621121ed6fa79 Mon Sep 17 00:00:00 2001 From: razzle Date: Thu, 23 May 2024 16:14:32 -0400 Subject: [PATCH 026/132] refactor: allow callers to directly set logfile location (#2545) Changes `message.UseLogFile` to accept an `f *os.File` and wrap it in a `*message.PausableWriter`. Adds unit tests for `PausableWriter`. ## Related Issue Fixes #2524 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Signed-off-by: razzle --- src/cmd/common/setup.go | 14 +++++++++--- src/pkg/message/credentials.go | 8 +++---- src/pkg/message/message.go | 29 ++++-------------------- src/pkg/message/pausable.go | 31 +++++++++++++------------ src/pkg/message/pausable_test.go | 39 ++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 45 deletions(-) create mode 100644 src/pkg/message/pausable_test.go diff --git a/src/cmd/common/setup.go b/src/cmd/common/setup.go index ae2052b882..72fd0d6722 100644 --- a/src/cmd/common/setup.go +++ b/src/cmd/common/setup.go @@ -5,8 +5,10 @@ package common import ( + "fmt" "io" "os" + "time" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" @@ -51,14 +53,20 @@ func SetupCLI() { } if !config.SkipLogFile { - logFile, err := message.UseLogFile("") + ts := time.Now().Format("2006-01-02-15-04-05") + + f, err := os.CreateTemp("", fmt.Sprintf("zarf-%s-*.log", ts)) + if err != nil { + message.WarnErr(err, "Error creating a log file in a temporary directory") + return + } + logFile, err := message.UseLogFile(f) if err != nil { message.WarnErr(err, "Error saving a log file to a temporary directory") return } pterm.SetDefaultOutput(io.MultiWriter(os.Stderr, logFile)) - location := message.LogFileLocation() - message.Notef("Saving log file to %s", location) + message.Notef("Saving log file to %s", f.Name()) } } diff --git a/src/pkg/message/credentials.go b/src/pkg/message/credentials.go index 60ca971119..07b85525bc 100644 --- a/src/pkg/message/credentials.go +++ b/src/pkg/message/credentials.go @@ -32,8 +32,8 @@ func PrintCredentialTable(state *types.ZarfState, componentsToDeploy []types.Dep // Pause the logfile's output to avoid credentials being printed to the log file if logFile != nil { - logFile.pause() - defer logFile.resume() + logFile.Pause() + defer logFile.Resume() } loginData := [][]string{} @@ -95,8 +95,8 @@ func PrintComponentCredential(state *types.ZarfState, componentName string) { func PrintCredentialUpdates(oldState *types.ZarfState, newState *types.ZarfState, services []string) { // Pause the logfile's output to avoid credentials being printed to the log file if logFile != nil { - logFile.pause() - defer logFile.resume() + logFile.Pause() + defer logFile.Resume() } for _, service := range services { diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 1801c7b9f5..713bb90cbe 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -7,7 +7,6 @@ package message import ( "encoding/json" "fmt" - "io" "net/http" "os" "runtime/debug" @@ -49,7 +48,7 @@ var RuleLine = strings.Repeat("━", TermWidth) var logLevel = InfoLevel // logFile acts as a buffer for logFile generation -var logFile *pausableLogFile +var logFile *PausableWriter // DebugWriter represents a writer interface that writes to message.Debug type DebugWriter struct{} @@ -77,32 +76,14 @@ func init() { pterm.SetDefaultOutput(os.Stderr) } -// UseLogFile writes output to stderr and a logFile. -func UseLogFile(dir string) (io.Writer, error) { - // Prepend the log filename with a timestamp. - ts := time.Now().Format("2006-01-02-15-04-05") - - f, err := os.CreateTemp(dir, fmt.Sprintf("zarf-%s-*.log", ts)) - if err != nil { - return nil, err - } - - logFile = &pausableLogFile{ - wr: f, - f: f, - } +// UseLogFile wraps a given file in a PausableWriter +// and sets it as the log file used by the message package. +func UseLogFile(f *os.File) (*PausableWriter, error) { + logFile = NewPausableWriter(f) return logFile, nil } -// LogFileLocation returns the location of the log file. -func LogFileLocation() string { - if logFile == nil { - return "" - } - return logFile.f.Name() -} - // SetLogLevel sets the log level. func SetLogLevel(lvl LogLevel) { logLevel = lvl diff --git a/src/pkg/message/pausable.go b/src/pkg/message/pausable.go index ee8d7b6a67..b9e8fae1c7 100644 --- a/src/pkg/message/pausable.go +++ b/src/pkg/message/pausable.go @@ -6,26 +6,29 @@ package message import ( "io" - "os" ) -// pausableLogFile is a pausable log file -type pausableLogFile struct { - wr io.Writer - f *os.File +// PausableWriter is a pausable writer +type PausableWriter struct { + out, wr io.Writer } -// pause the log file -func (l *pausableLogFile) pause() { - l.wr = io.Discard +// NewPausableWriter creates a new pausable writer +func NewPausableWriter(wr io.Writer) *PausableWriter { + return &PausableWriter{out: wr, wr: wr} } -// resume the log file -func (l *pausableLogFile) resume() { - l.wr = l.f +// Pause sets the output writer to io.Discard +func (pw *PausableWriter) Pause() { + pw.out = io.Discard } -// Write writes the data to the log file -func (l *pausableLogFile) Write(p []byte) (n int, err error) { - return l.wr.Write(p) +// Resume sets the output writer back to the original writer +func (pw *PausableWriter) Resume() { + pw.out = pw.wr +} + +// Write writes the data to the underlying output writer +func (pw *PausableWriter) Write(p []byte) (n int, err error) { + return pw.out.Write(p) } diff --git a/src/pkg/message/pausable_test.go b/src/pkg/message/pausable_test.go new file mode 100644 index 0000000000..2cfeb2c827 --- /dev/null +++ b/src/pkg/message/pausable_test.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package message + +import ( + "bytes" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPausableWriter(t *testing.T) { + var buf bytes.Buffer + + pw := NewPausableWriter(&buf) + + n, err := pw.Write([]byte("foo")) + require.NoError(t, err) + require.Equal(t, 3, n) + + require.Equal(t, "foo", buf.String()) + + pw.Pause() + + n, err = pw.Write([]byte("bar")) + require.NoError(t, err) + require.Equal(t, 3, n) + + require.Equal(t, "foo", buf.String()) + + pw.Resume() + + n, err = pw.Write([]byte("baz")) + require.NoError(t, err) + require.Equal(t, 3, n) + + require.Equal(t, "foobaz", buf.String()) +} From a69c44f25c44bc044e68e9c9e1d194d7aa682ad5 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 23 May 2024 23:21:53 +0200 Subject: [PATCH 027/132] test: add test for packager source (#2525) ## Description This change adds tests for packager source which tests all implementations. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Lucas Rodriguez --- .gitignore | 1 + src/pkg/packager/sources/new_test.go | 196 +++++++++++++++--- src/pkg/packager/sources/tarball.go | 31 ++- .../sources/testdata/expected-pkg.json | 23 ++ ...arf-package-wordpress-amd64-16.0.4.tar.zst | Bin 0 -> 645 bytes src/pkg/packager/sources/testdata/zarf.yaml | 6 + 6 files changed, 224 insertions(+), 33 deletions(-) create mode 100644 src/pkg/packager/sources/testdata/expected-pkg.json create mode 100644 src/pkg/packager/sources/testdata/zarf-package-wordpress-amd64-16.0.4.tar.zst create mode 100644 src/pkg/packager/sources/testdata/zarf.yaml diff --git a/.gitignore b/.gitignore index 2a68c38107..9c90e0b138 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ zarf-sbom/ test-*.txt __debug_bin .netlify +!**/testdata/** coverage.out diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index b1cdea51bf..2e20bc1c21 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -5,49 +5,181 @@ package sources import ( + "encoding/json" "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" "testing" - "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" + + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/pkg/packager/filters" + "github.com/defenseunicorns/zarf/src/types" ) -var ociS *OCISource -var urlS *URLSource -var tarballS *TarballSource -var splitS *SplitTarballSource -var packageS *PackageSource +func TestNewPackageSource(t *testing.T) { + t.Parallel() -type source struct { - pkgSrc string - srcType string - source PackageSource -} + tests := []struct { + name string + src string + expectedIdentify string + expectedType PackageSource + }{ + { + name: "oci", + src: "oci://ghcr.io/defenseunicorns/packages/init:1.0.0", + expectedIdentify: "oci", + expectedType: &OCISource{}, + }, + { + name: "sget with sub path", + src: "sget://github.com/defenseunicorns/zarf-hello-world:x86", + expectedIdentify: "sget", + expectedType: &URLSource{}, + }, + { + name: "sget without host", + src: "sget://defenseunicorns/zarf-hello-world:x86_64", + expectedIdentify: "sget", + expectedType: &URLSource{}, + }, + { + name: "https", + src: "https://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "https", + expectedType: &URLSource{}, + }, + { + name: "http", + src: "http://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "http", + expectedType: &URLSource{}, + }, + { + name: "local tar init zst", + src: "zarf-init-amd64-v1.0.0.tar.zst", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar", + src: "zarf-package-manifests-amd64-v1.0.0.tar", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar manifest zst", + src: "zarf-package-manifests-amd64-v1.0.0.tar.zst", + expectedIdentify: "tarball", + expectedType: &TarballSource{}, + }, + { + name: "local tar split", + src: "testdata/.part000", + expectedIdentify: "split", + expectedType: &SplitTarballSource{}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() -var sources = []source{ - {pkgSrc: "oci://ghcr.io/defenseunicorns/packages/init:1.0.0", srcType: "oci", source: ociS}, - {pkgSrc: "sget://github.com/defenseunicorns/zarf-hello-world:x86", srcType: "sget", source: urlS}, - {pkgSrc: "sget://defenseunicorns/zarf-hello-world:x86_64", srcType: "sget", source: urlS}, - {pkgSrc: "https://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", srcType: "https", source: urlS}, - {pkgSrc: "http://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst", srcType: "http", source: urlS}, - {pkgSrc: "zarf-init-amd64-v1.0.0.tar.zst", srcType: "tarball", source: tarballS}, - {pkgSrc: "zarf-package-manifests-amd64-v1.0.0.tar", srcType: "tarball", source: tarballS}, - {pkgSrc: "zarf-package-manifests-amd64-v1.0.0.tar.zst", srcType: "tarball", source: tarballS}, - {pkgSrc: "some-dir/.part000", srcType: "split", source: splitS}, + require.Equal(t, tt.expectedIdentify, Identify(tt.src)) + ps, err := New(&types.ZarfPackageOptions{PackageSource: tt.src}) + require.NoError(t, err) + require.IsType(t, tt.expectedType, ps) + }) + } } -func Test_identifySourceType(t *testing.T) { - for _, source := range sources { - actual := Identify(source.pkgSrc) - require.Equalf(t, source.srcType, actual, fmt.Sprintf("source: %s", source)) +func TestPackageSource(t *testing.T) { + t.Parallel() + + // Copy tar to a temp directory, otherwise Collect will delete it. + tarName := "zarf-package-wordpress-amd64-16.0.4.tar.zst" + testDir := t.TempDir() + src, err := os.Open(filepath.Join("testdata", tarName)) + require.NoError(t, err) + tarPath := filepath.Join(testDir, tarName) + dst, err := os.Create(tarPath) + require.NoError(t, err) + _, err = io.Copy(dst, src) + require.NoError(t, err) + src.Close() + dst.Close() + + b, err := os.ReadFile("./testdata/expected-pkg.json") + require.NoError(t, err) + expectedPkg := types.ZarfPackage{} + err = json.Unmarshal(b, &expectedPkg) + require.NoError(t, err) + + ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, fp := filepath.Split(req.URL.Path) + f, err := os.Open(filepath.Join("testdata", fp)) + if err != nil { + rw.WriteHeader(http.StatusNotFound) + return + } + defer f.Close() + io.Copy(rw, f) + })) + + tests := []struct { + name string + src string + shasum string + }{ + { + name: "local", + src: tarPath, + }, + { + name: "http", + src: fmt.Sprintf("%s/zarf-package-wordpress-amd64-16.0.4.tar.zst", ts.URL), + shasum: "835b06fc509e639497fb45f45d432e5c4cbd5d84212db5357b16bc69724b0e26", + }, } -} + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + opts := &types.ZarfPackageOptions{ + PackageSource: tt.src, + Shasum: tt.shasum, + } + + ps, err := New(opts) + require.NoError(t, err) + packageDir := t.TempDir() + pkgLayout := layout.New(packageDir) + pkg, warnings, err := ps.LoadPackage(pkgLayout, filters.Empty(), false) + require.NoError(t, err) + require.Empty(t, warnings) + require.Equal(t, expectedPkg, pkg) + + ps, err = New(opts) + require.NoError(t, err) + metadataDir := t.TempDir() + metadataLayout := layout.New(metadataDir) + metadata, warnings, err := ps.LoadPackageMetadata(metadataLayout, true, false) + require.NoError(t, err) + require.Empty(t, warnings) + require.Equal(t, expectedPkg, metadata) -func TestNew(t *testing.T) { - for _, source := range sources { - actual, err := New(&types.ZarfPackageOptions{PackageSource: source.pkgSrc}) - require.NoError(t, err) - require.IsType(t, source.source, actual) - require.Implements(t, packageS, actual) + ps, err = New(opts) + require.NoError(t, err) + collectDir := t.TempDir() + fp, err := ps.Collect(collectDir) + require.NoError(t, err) + require.Equal(t, filepath.Join(collectDir, filepath.Base(tt.src)), fp) + }) } } diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go index 55ba0c6b2e..2172d7ef13 100644 --- a/src/pkg/packager/sources/tarball.go +++ b/src/pkg/packager/sources/tarball.go @@ -204,5 +204,34 @@ func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM b // Collect for the TarballSource is essentially an `mv` func (s *TarballSource) Collect(dir string) (string, error) { dst := filepath.Join(dir, filepath.Base(s.PackageSource)) - return dst, os.Rename(s.PackageSource, dst) + err := os.Rename(s.PackageSource, dst) + linkErr := &os.LinkError{} + isLinkErr := errors.As(err, &linkErr) + if err != nil && !isLinkErr { + return "", err + } + if err == nil { + return dst, nil + } + + // Copy file if rename is not possible due to existing on different partitions. + srcFile, err := os.Open(linkErr.Old) + if err != nil { + return "", err + } + defer srcFile.Close() + dstFile, err := os.Create(linkErr.New) + if err != nil { + return "", err + } + defer dstFile.Close() + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return "", err + } + err = os.Remove(linkErr.Old) + if err != nil { + return "", err + } + return dst, nil } diff --git a/src/pkg/packager/sources/testdata/expected-pkg.json b/src/pkg/packager/sources/testdata/expected-pkg.json new file mode 100644 index 0000000000..6f1e946899 --- /dev/null +++ b/src/pkg/packager/sources/testdata/expected-pkg.json @@ -0,0 +1,23 @@ +{ + "kind": "ZarfPackageConfig", + "metadata": { + "name": "wordpress", + "description": "\"A Zarf Package that deploys the WordPress blogging and content management platform\"\n", + "version": "16.0.4", + "architecture": "amd64", + "aggregateChecksum": "cd6cc0c238c45982d80b55d3dd3d27497d4f3b264e0f037a37c464be34a3640e" + }, + "build": { + "terminal": "control-center", + "user": "philip", + "architecture": "amd64", + "timestamp": "Wed, 22 May 2024 10:16:55 +0200", + "version": "v0.33.1", + "migrations": [ + "scripts-to-actions", + "pluralize-set-variable" + ], + "lastNonBreakingVersion": "v0.27.0" + }, + "components": [] +} \ No newline at end of file diff --git a/src/pkg/packager/sources/testdata/zarf-package-wordpress-amd64-16.0.4.tar.zst b/src/pkg/packager/sources/testdata/zarf-package-wordpress-amd64-16.0.4.tar.zst new file mode 100644 index 0000000000000000000000000000000000000000..b5800b3d54950f2597e97ec633db6b59cd68c9a1 GIT binary patch literal 645 zcmV;00($)@wJ-f-02jRz0Fq~LA|P$_UzN%s#Ys_wv^{#1kSPEuga-6ocQcOwL^VK& z)p!gJ4BXf|f6_^!5b5Ntq#qKXDINZmVA6wYJz_>aa$Ht_VBEKipOBH)M>Iia(95vQ zU`84{sk~kxDKR4|VJzu~?$rS7T%)L_6}Q!kCES45;(+)H+)R>R`;xre)VB|2r2d@` z84;~b zofLAM&d`()L?ef-{fZDPRp4)2wh}a&KJxX(Wgb{GEV!9e-bq$yKQbfMew|{a5Zt`v zlNo9Jdi47e0?B96f`{Az9pS0Iz)hUe@l;!_BFQC^r}~6)>vhtrvDT34H?b0REH4$| zbK=54=Qu7djj7lDZY>Lh{m&nTJ<(xpS=uv=QQSYzX^Zl!ANDZK3fHVLrIhaO8*vjW zJ*)$M1KRrbG08|arfx;`YafmkOesvYqTg$av86PnVD72zR&>AaZiN8Dx;e&mV~nTz zL_q*FfFGD;E~u6(`bwwRoofW7VW|F`g3MZh;Ko)pb0f83o^#rOQWRlevZKIl90Mu_ zGYtSX04d1R6##zV017Jv_RjI|5Z3jhv~g#a=rAP`;+Oj)2{zyt(Pm@Hy~1uO;(s2g-+fP@;6%=8gJg;5Ds1ek^Z fkN`}gfCz^26g=P^26gViinvxX6`0%-i3{;zt1v2f literal 0 HcmV?d00001 diff --git a/src/pkg/packager/sources/testdata/zarf.yaml b/src/pkg/packager/sources/testdata/zarf.yaml new file mode 100644 index 0000000000..0e81803fa9 --- /dev/null +++ b/src/pkg/packager/sources/testdata/zarf.yaml @@ -0,0 +1,6 @@ +kind: ZarfPackageConfig +metadata: + name: wordpress + version: 16.0.4 + description: | + "A Zarf Package that deploys the WordPress blogging and content management platform" From f0362d1f021b67d9bca8139d258666364a711d80 Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 23 May 2024 15:52:23 -0600 Subject: [PATCH 028/132] chore: add unit tests to variables pkg (#2519) ## Description This moves the unit tests from pkg (incorporating feedback from over there) ## Related Issue Fixes #N/A ## Checklist before merging - [X] Test, docs, adr added or updated as needed - [X] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Co-authored-by: Lucas Rodriguez --- Makefile | 2 +- src/pkg/variables/templates_test.go | 150 ++++++++++++++++++++++++++ src/pkg/variables/testdata/file.txt | 1 + src/pkg/variables/variables.go | 15 ++- src/pkg/variables/variables_test.go | 159 ++++++++++++++++++++++++++++ 5 files changed, 323 insertions(+), 4 deletions(-) create mode 100644 src/pkg/variables/templates_test.go create mode 100644 src/pkg/variables/testdata/file.txt create mode 100644 src/pkg/variables/variables_test.go diff --git a/Makefile b/Makefile index 1924780d4c..62bbf3b859 100644 --- a/Makefile +++ b/Makefile @@ -223,5 +223,5 @@ cve-report: ## Create a CVE report for the current project (must `brew install g @test -d ./build || mkdir ./build go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype -o template -t hack/grype.tmpl > build/zarf-known-cves.csv -lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golang-ci-lint` first) +lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golangci-lint` first) golangci-lint run diff --git a/src/pkg/variables/templates_test.go b/src/pkg/variables/templates_test.go new file mode 100644 index 0000000000..7dfb540029 --- /dev/null +++ b/src/pkg/variables/templates_test.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package variables + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +var start = ` +This is a test file for templating. + + ###PREFIX_VAR_REPLACE_ME### + ###PREFIX_CONST_REPLACE_ME### + ###PREFIX_APP_REPLACE_ME### + ###PREFIX_NON_EXIST### +` +var simple = ` +This is a test file for templating. + + VAR_REPLACED + CONST_REPLACED + APP_REPLACED + ###PREFIX_NON_EXIST### +` +var multiline = ` +This is a test file for templating. + + VAR_REPLACED +VAR_SECOND + CONST_REPLACED +CONST_SECOND + APP_REPLACED +APP_SECOND + ###PREFIX_NON_EXIST### +` +var autoIndent = ` +This is a test file for templating. + + VAR_REPLACED + VAR_SECOND + CONST_REPLACED + CONST_SECOND + APP_REPLACED + APP_SECOND + ###PREFIX_NON_EXIST### +` +var file = ` +This is a test file for templating. + + The contents of this file become the template value + CONSTs Don't Support File + The contents of this file become the template value + ###PREFIX_NON_EXIST### +` + +func TestReplaceTextTemplate(t *testing.T) { + type test struct { + vc VariableConfig + path string + wantErr bool + wantContents string + } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, applicationTemplates: map[string]*TextTemplate{}}, + path: "non-existent.test", + wantErr: true, + wantContents: start, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED"}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED"}, + }, + }, + wantContents: simple, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED\nVAR_SECOND"}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED\nCONST_SECOND"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED\nAPP_SECOND"}, + }, + }, + wantContents: multiline, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "VAR_REPLACED\nVAR_SECOND", Variable: Variable{AutoIndent: true}}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONST_REPLACED\nCONST_SECOND", AutoIndent: true}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "APP_REPLACED\nAPP_SECOND", AutoIndent: true}, + }, + }, + wantContents: autoIndent, + }, + { + vc: VariableConfig{ + templatePrefix: "PREFIX", + setVariableMap: SetVariableMap{ + "REPLACE_ME": {Value: "testdata/file.txt", Variable: Variable{Type: FileVariableType}}, + }, + constants: []Constant{{Name: "REPLACE_ME", Value: "CONSTs Don't Support File"}}, + applicationTemplates: map[string]*TextTemplate{ + "###PREFIX_APP_REPLACE_ME###": {Value: "testdata/file.txt", Type: FileVariableType}, + }, + }, + wantContents: file, + }, + } + + for _, tc := range tests { + if tc.path == "" { + tmpDir := t.TempDir() + tc.path = filepath.Join(tmpDir, "templates.test") + + f, _ := os.Create(tc.path) + defer f.Close() + + f.WriteString(start) + } + + gotErr := tc.vc.ReplaceTextTemplate(tc.path) + if tc.wantErr { + require.Error(t, gotErr) + } else { + require.NoError(t, gotErr) + gotContents, _ := os.ReadFile(tc.path) + require.Equal(t, tc.wantContents, string(gotContents)) + } + } +} diff --git a/src/pkg/variables/testdata/file.txt b/src/pkg/variables/testdata/file.txt new file mode 100644 index 0000000000..c5710b7a29 --- /dev/null +++ b/src/pkg/variables/testdata/file.txt @@ -0,0 +1 @@ +The contents of this file become the template value \ No newline at end of file diff --git a/src/pkg/variables/variables.go b/src/pkg/variables/variables.go index cdd3bc4c94..1f2e6a8480 100644 --- a/src/pkg/variables/variables.go +++ b/src/pkg/variables/variables.go @@ -76,9 +76,18 @@ func (vc *VariableConfig) SetVariable(name, value string, sensitive bool, autoIn // CheckVariablePattern checks to see if a current variable is set to a value that matches its pattern func (vc *VariableConfig) CheckVariablePattern(name, pattern string) error { - if regexp.MustCompile(pattern).MatchString(vc.setVariableMap[name].Value) { - return nil + if variable, ok := vc.setVariableMap[name]; ok { + r, err := regexp.Compile(pattern) + if err != nil { + return err + } + + if r.MatchString(variable.Value) { + return nil + } + + return fmt.Errorf("provided value for variable %q does not match pattern %q", name, pattern) } - return fmt.Errorf("provided value for variable %q does not match pattern %q", name, pattern) + return fmt.Errorf("variable %q was not found in the current variable map", name) } diff --git a/src/pkg/variables/variables_test.go b/src/pkg/variables/variables_test.go new file mode 100644 index 0000000000..901fdf4ba9 --- /dev/null +++ b/src/pkg/variables/variables_test.go @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package variables + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPopulateVariables(t *testing.T) { + type test struct { + vc VariableConfig + vars []InteractiveVariable + presets map[string]string + wantErr bool + wantVars SetVariableMap + } + + prompt := func(_ InteractiveVariable) (value string, err error) { return "Prompt", nil } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{{Variable: Variable{Name: "NAME"}}}, + presets: map[string]string{}, + wantVars: SetVariableMap{"NAME": {Variable: Variable{Name: "NAME"}}}, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default"}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Default"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default"}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Set"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME", Sensitive: true, AutoIndent: true, Type: FileVariableType}, Value: "Set"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Prompt: true}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Prompt"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Default: "Default", Prompt: true}, + }, + presets: map[string]string{}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Prompt"}, + }, + }, + { + vc: VariableConfig{setVariableMap: SetVariableMap{}, prompt: prompt}, + vars: []InteractiveVariable{ + {Variable: Variable{Name: "NAME"}, Prompt: true}, + }, + presets: map[string]string{"NAME": "Set"}, + wantVars: SetVariableMap{ + "NAME": {Variable: Variable{Name: "NAME"}, Value: "Set"}, + }, + }, + } + + for _, tc := range tests { + gotErr := tc.vc.PopulateVariables(tc.vars, tc.presets) + if tc.wantErr { + require.Error(t, gotErr) + } else { + require.NoError(t, gotErr) + } + + gotVars := tc.vc.setVariableMap + + require.Equal(t, len(gotVars), len(tc.wantVars)) + + for key := range gotVars { + require.Equal(t, gotVars[key], tc.wantVars[key]) + } + } +} + +func TestCheckVariablePattern(t *testing.T) { + type test struct { + vc VariableConfig + name string + pattern string + wantErrMsg string + } + + tests := []test{ + { + vc: VariableConfig{setVariableMap: SetVariableMap{}}, name: "NAME", pattern: "n[a-z]me", + wantErrMsg: "variable \"NAME\" was not found in the current variable map", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[^a]me", + wantErrMsg: "provided value for variable \"NAME\" does not match pattern \"n[^a]me\"", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[a-z]me", wantErrMsg: "", + }, + { + vc: VariableConfig{ + setVariableMap: SetVariableMap{"NAME": &SetVariable{Value: "name"}}, + }, name: "NAME", pattern: "n[a-z-bad-pattern", wantErrMsg: "error parsing regexp: missing closing ]: `[a-z-bad-pattern`", + }, + } + + for _, tc := range tests { + got := tc.vc.CheckVariablePattern(tc.name, tc.pattern) + if tc.wantErrMsg != "" { + require.EqualError(t, got, tc.wantErrMsg) + } else { + require.NoError(t, got) + } + } +} From c8c52d918a6dd0cd1c89fc7c2267d2ef3425d154 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 24 May 2024 00:22:15 +0200 Subject: [PATCH 029/132] test: clean up tests for composer (#2532) ## Description Cleans up the composer tests to the newer testing style. Also marks test helper functions as helpers. ## Related Issue Relates to #2512 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: schristoff <167717759+schristoff-du@users.noreply.github.com> Co-authored-by: razzle Co-authored-by: Lucas Rodriguez --- src/pkg/packager/composer/extensions.go | 14 --- src/pkg/packager/composer/list.go | 3 +- src/pkg/packager/composer/list_test.go | 125 ++++++++++-------------- 3 files changed, 56 insertions(+), 86 deletions(-) delete mode 100644 src/pkg/packager/composer/extensions.go diff --git a/src/pkg/packager/composer/extensions.go b/src/pkg/packager/composer/extensions.go deleted file mode 100644 index 77ac73c038..0000000000 --- a/src/pkg/packager/composer/extensions.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package composer contains functions for composing components within Zarf packages. -package composer - -import ( - "github.com/defenseunicorns/zarf/src/extensions/bigbang" - "github.com/defenseunicorns/zarf/src/types" -) - -func composeExtensions(c *types.ZarfComponent, override types.ZarfComponent, relativeTo string) { - bigbang.Compose(c, override, relativeTo) -} diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go index 3642ec562f..4f1a484ca3 100644 --- a/src/pkg/packager/composer/list.go +++ b/src/pkg/packager/composer/list.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/extensions/bigbang" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated" "github.com/defenseunicorns/zarf/src/pkg/utils" @@ -303,7 +304,7 @@ func (ic *ImportChain) Compose() (composed *types.ZarfComponent, err error) { overrideResources(composed, node.ZarfComponent) overrideActions(composed, node.ZarfComponent) - composeExtensions(composed, node.ZarfComponent, node.relativeToHead) + bigbang.Compose(composed, node.ZarfComponent, node.relativeToHead) node = node.prev } diff --git a/src/pkg/packager/composer/list_test.go b/src/pkg/packager/composer/list_test.go index 73e84a85fa..622647357c 100644 --- a/src/pkg/packager/composer/list_test.go +++ b/src/pkg/packager/composer/list_test.go @@ -19,19 +19,17 @@ import ( func TestNewImportChain(t *testing.T) { t.Parallel() - type testCase struct { - name string - head types.ZarfComponent - arch string - flavor string - expectedErrorMessage string - } - - testCases := []testCase{ + tests := []struct { + name string + head types.ZarfComponent + arch string + flavor string + expectedErr string + }{ { - name: "No Architecture", - head: types.ZarfComponent{}, - expectedErrorMessage: "architecture must be provided", + name: "No Architecture", + head: types.ZarfComponent{}, + expectedErr: "architecture must be provided", }, { name: "Circular Import", @@ -40,19 +38,18 @@ func TestNewImportChain(t *testing.T) { Path: ".", }, }, - arch: "amd64", - expectedErrorMessage: "detected circular import chain", + arch: "amd64", + expectedErr: "detected circular import chain", }, } testPackageName := "test-package" - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, err := NewImportChain(testCase.head, 0, testPackageName, testCase.arch, testCase.flavor) - require.Contains(t, err.Error(), testCase.expectedErrorMessage) + _, err := NewImportChain(tt.head, 0, testPackageName, tt.arch, tt.flavor) + require.ErrorContains(t, err, tt.expectedErr) }) } } @@ -60,14 +57,6 @@ func TestNewImportChain(t *testing.T) { func TestCompose(t *testing.T) { t.Parallel() - type testCase struct { - name string - ic *ImportChain - returnError bool - expectedComposed types.ZarfComponent - expectedErrorMessage string - } - firstDirectory := "hello" secondDirectory := "world" finalDirectory := filepath.Join(firstDirectory, secondDirectory) @@ -76,27 +65,29 @@ func TestCompose(t *testing.T) { secondDirectoryActionDefault := filepath.Join(firstDirectory, "world-dc") firstDirectoryActionDefault := "hello-dc" - testCases := []testCase{ + tests := []struct { + name string + ic *ImportChain + expectedComposed types.ZarfComponent + }{ { name: "Single Component", - ic: createChainFromSlice([]types.ZarfComponent{ + ic: createChainFromSlice(t, []types.ZarfComponent{ { Name: "no-import", }, }), - returnError: false, expectedComposed: types.ZarfComponent{ Name: "no-import", }, }, { name: "Multiple Components", - ic: createChainFromSlice([]types.ZarfComponent{ - createDummyComponent("hello", firstDirectory, "hello"), - createDummyComponent("world", secondDirectory, "world"), - createDummyComponent("today", "", "hello"), + ic: createChainFromSlice(t, []types.ZarfComponent{ + createDummyComponent(t, "hello", firstDirectory, "hello"), + createDummyComponent(t, "world", secondDirectory, "world"), + createDummyComponent(t, "today", "", "hello"), }), - returnError: false, expectedComposed: types.ZarfComponent{ Name: "import-hello", // Files should always be appended with corrected directories @@ -243,19 +234,14 @@ func TestCompose(t *testing.T) { }, }, } - - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { t.Parallel() - composed, err := testCase.ic.Compose() - if testCase.returnError { - require.Contains(t, err.Error(), testCase.expectedErrorMessage) - } else { - require.EqualValues(t, &testCase.expectedComposed, composed) - } + composed, err := tt.ic.Compose() + require.NoError(t, err) + require.EqualValues(t, &tt.expectedComposed, composed) }) } } @@ -263,15 +249,6 @@ func TestCompose(t *testing.T) { func TestMerging(t *testing.T) { t.Parallel() - type testCase struct { - name string - ic *ImportChain - existingVars []variables.InteractiveVariable - existingConsts []variables.Constant - expectedVars []variables.InteractiveVariable - expectedConsts []variables.Constant - } - head := Node{ vars: []variables.InteractiveVariable{ { @@ -316,7 +293,14 @@ func TestMerging(t *testing.T) { tail.prev = &head testIC := &ImportChain{head: &head, tail: &tail} - testCases := []testCase{ + tests := []struct { + name string + ic *ImportChain + existingVars []variables.InteractiveVariable + existingConsts []variables.Constant + expectedVars []variables.InteractiveVariable + expectedConsts []variables.Constant + }{ { name: "empty-ic", ic: &ImportChain{}, @@ -425,41 +409,40 @@ func TestMerging(t *testing.T) { }, } - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { t.Parallel() - mergedVars := testCase.ic.MergeVariables(testCase.existingVars) - require.EqualValues(t, testCase.expectedVars, mergedVars) + mergedVars := tt.ic.MergeVariables(tt.existingVars) + require.EqualValues(t, tt.expectedVars, mergedVars) - mergedConsts := testCase.ic.MergeConstants(testCase.existingConsts) - require.EqualValues(t, testCase.expectedConsts, mergedConsts) + mergedConsts := tt.ic.MergeConstants(tt.existingConsts) + require.EqualValues(t, tt.expectedConsts, mergedConsts) }) } } -func createChainFromSlice(components []types.ZarfComponent) (ic *ImportChain) { +func createChainFromSlice(t *testing.T, components []types.ZarfComponent) (ic *ImportChain) { + t.Helper() + ic = &ImportChain{} testPackageName := "test-package" - if len(components) == 0 { return ic } - ic.append(components[0], 0, testPackageName, ".", nil, nil) history := []string{} - for idx := 1; idx < len(components); idx++ { history = append(history, components[idx-1].Import.Path) ic.append(components[idx], idx, testPackageName, filepath.Join(history...), nil, nil) } - return ic } -func createDummyComponent(name, importDir, subName string) types.ZarfComponent { +func createDummyComponent(t *testing.T, name, importDir, subName string) types.ZarfComponent { + t.Helper() + return types.ZarfComponent{ Name: fmt.Sprintf("import-%s", name), Import: types.ZarfComponentImport{ From 1dcc14011e8729075436d718f69dfc857659100f Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Thu, 23 May 2024 18:53:16 -0400 Subject: [PATCH 030/132] test: argo agent unit tests (#2536) ## Description Relates to #2512 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Lucas Rodriguez Co-authored-by: razzle Co-authored-by: schristoff <167717759+schristoff-du@users.noreply.github.com> --- packages/zarf-agent/manifests/deployment.yaml | 6 - .../agent/hooks/argocd-application.go | 102 ++++++++------- .../agent/hooks/argocd-application_test.go | 95 ++++++++++++++ src/internal/agent/hooks/argocd-repository.go | 92 +++++++------- .../agent/hooks/argocd-repository_test.go | 119 ++++++++++++++++++ src/internal/agent/http/proxy.go | 28 +++-- src/internal/agent/http/server.go | 4 +- src/internal/agent/state/state.go | 26 ---- 8 files changed, 338 insertions(+), 134 deletions(-) create mode 100644 src/internal/agent/hooks/argocd-application_test.go create mode 100644 src/internal/agent/hooks/argocd-repository_test.go delete mode 100644 src/internal/agent/state/state.go diff --git a/packages/zarf-agent/manifests/deployment.yaml b/packages/zarf-agent/manifests/deployment.yaml index a4113812f9..a8e481845f 100644 --- a/packages/zarf-agent/manifests/deployment.yaml +++ b/packages/zarf-agent/manifests/deployment.yaml @@ -43,9 +43,6 @@ spec: - name: tls-certs mountPath: /etc/certs readOnly: true - - name: zarf-state - mountPath: /etc/zarf-state - readOnly: true # Required for OpenShift to mount k9s vendored directories - name: config mountPath: /.config @@ -55,9 +52,6 @@ spec: - name: tls-certs secret: secretName: agent-hook-tls - - name: zarf-state - secret: - secretName: zarf-state # Required for OpenShift to mount k9s vendored directories - name: config emptyDir: {} diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 260b1d53a3..90a5b98744 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -5,81 +5,90 @@ package hooks import ( + "context" "encoding/json" "fmt" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" ) -// Source represents a subset of the Argo Source object needed for Zarf Git URL mutations -type Source struct { - RepoURL string `json:"repoURL"` +// Application is a definition of an ArgoCD Application resource. +// The ArgoCD Application structs in this file have been partially copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type Application struct { + Spec ApplicationSpec `json:"spec"` } -// ArgoApplication represents a subset of the Argo Application object needed for Zarf Git URL mutations -type ArgoApplication struct { - Spec struct { - Source Source `json:"source"` - Sources []Source `json:"sources"` - } `json:"spec"` +// ApplicationSpec represents desired application state. Contains link to repository with application definition. +type ApplicationSpec struct { + // Source is a reference to the location of the application's manifests or chart. + Source *ApplicationSource `json:"source,omitempty"` + Sources []ApplicationSource `json:"sources,omitempty"` } -var ( - zarfState *types.ZarfState - patches []operations.PatchOperation - isPatched bool - isCreate bool - isUpdate bool -) +// ApplicationSource contains all required information about the source of an application. +type ApplicationSource struct { + // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. + RepoURL string `json:"repoURL"` +} // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. -func NewApplicationMutationHook() operations.Hook { +func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewApplicationMutationHook()") return operations.Hook{ - Create: mutateApplication, - Update: mutateApplication, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateApplication(ctx, r, cluster) + }, } } // mutateApplication mutates the git repository url to point to the repository URL defined in the ZarfState. -func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err error) { - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - - patches = []operations.PatchOperation{} - - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { +func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", zarfState.GitServer.Address) + message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", state.GitServer.Address) - // parse to simple struct to read the git url - src := &ArgoApplication{} - - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + app := Application{} + if err = json.Unmarshal(r.Object.Raw, &app); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } message.Debugf("Data %v", string(r.Object.Raw)) - if src.Spec.Source != (Source{}) { - patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL) + patches := []operations.PatchOperation{} + + if app.Spec.Source != nil { + patchedURL, err := getPatchedRepoURL(app.Spec.Source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches) } - if len(src.Spec.Sources) > 0 { - for idx, source := range src.Spec.Sources { - patchedURL, _ := getPatchedRepoURL(source.RepoURL) + if len(app.Spec.Sources) > 0 { + for idx, source := range app.Spec.Sources { + patchedURL, err := getPatchedRepoURL(source.RepoURL, state.GitServer, r) + if err != nil { + return nil, err + } patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches) } } @@ -90,15 +99,18 @@ func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err e }, nil } -func getPatchedRepoURL(repoURL string) (string, error) { - var err error +func getPatchedRepoURL(repoURL string, gs types.GitServerInfo, r *v1.AdmissionRequest) (string, error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update patchedURL := repoURL + var isPatched bool + var err error // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, repoURL) + isPatched, err = helpers.DoHostnamesMatch(gs.Address, repoURL) if err != nil { return "", fmt.Errorf(lang.AgentErrHostnameMatch, err) } @@ -107,15 +119,15 @@ func getPatchedRepoURL(repoURL string) (string, error) { // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(gs.Address, patchedURL, gs.PushUsername) if err != nil { - message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL) + return "", fmt.Errorf("%s: %w", AgentErrTransformGitURL, err) } patchedURL = transformedURL.String() message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL) } - return patchedURL, err + return patchedURL, nil } // Patch updates of the Argo source spec. diff --git a/src/internal/agent/hooks/argocd-application_test.go b/src/internal/agent/hooks/argocd-application_test.go new file mode 100644 index 0000000000..5d241dd571 --- /dev/null +++ b/src/internal/agent/hooks/argocd-application_test.go @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createArgoAppAdmissionRequest(t *testing.T, op v1.Operation, argoApp *Application) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(argoApp) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestArgoAppWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewApplicationMutationHook(ctx, c)) + + tests := []admissionTest{ + { + name: "should be mutated", + admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ + Spec: ApplicationSpec{ + Source: &ApplicationSource{RepoURL: "https://diff-git-server.com/peanuts"}, + Sources: []ApplicationSource{ + { + RepoURL: "https://diff-git-server.com/cashews", + }, + { + RepoURL: "https://diff-git-server.com/almonds", + }, + }, + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/source/repoURL", + "https://git-server.com/a-push-user/peanuts-3883081014", + ), + operations.ReplacePatchOperation( + "/spec/sources/0/repoURL", + "https://git-server.com/a-push-user/cashews-580170494", + ), + operations.ReplacePatchOperation( + "/spec/sources/1/repoURL", + "https://git-server.com/a-push-user/almonds-640159520", + ), + }, + code: http.StatusOK, + }, + { + name: "should return internal server error on bad git URL", + admissionReq: createArgoAppAdmissionRequest(t, v1.Create, &Application{ + Spec: ApplicationSpec{ + Source: &ApplicationSource{RepoURL: "https://bad-url"}, + }, + }), + code: http.StatusInternalServerError, + errContains: AgentErrTransformGitURL, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index a9b74cd72f..9e643414d7 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -5,6 +5,7 @@ package hooks import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,97 +13,100 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" ) -// ArgoRepository represents a subset of the Argo Repository object needed for Zarf Git URL mutations -type ArgoRepository struct { - Data struct { - URL string `json:"url"` - } +// RepoCreds holds the definition for repository credentials. +// This has been partially copied from upstream. +// +// https://github.com/argoproj/argo-cd/blob/v2.11.0/pkg/apis/application/v1alpha1/repository_types.go +// +// There were errors encountered when trying to import argocd as a Go package. +// +// For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ +type RepoCreds struct { + // URL is the URL that this credential matches to. + URL string `json:"url"` } -// NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook. -func NewRepositoryMutationHook() operations.Hook { +// NewRepositorySecretMutationHook creates a new instance of the ArgoCD repository secret mutation hook. +func NewRepositorySecretMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ - Create: mutateRepository, - Update: mutateRepository, + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepositorySecret(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateRepositorySecret(ctx, r, cluster) + }, } } -// mutateRepository mutates the git repository URL to point to the repository URL defined in the ZarfState. -func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err error) { - - var ( - zarfState *types.ZarfState - patches []operations.PatchOperation - isPatched bool - - isCreate = r.Operation == v1.Create - isUpdate = r.Operation == v1.Update - ) +// mutateRepositorySecret mutates the git URL in the ArgoCD repository secret to point to the repository URL defined in the ZarfState. +func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (result *operations.Result, err error) { + isCreate := r.Operation == v1.Create + isUpdate := r.Operation == v1.Update + var isPatched bool - // Form the zarfState.GitServer.Address from the zarfState - if zarfState, err = state.GetZarfStateFromAgentPod(); err != nil { + state, err := cluster.LoadZarfState(ctx) + if err != nil { return nil, fmt.Errorf(lang.AgentErrGetState, err) } - message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", zarfState.GitServer.Address) - - // parse to simple struct to read the git url - src := &ArgoRepository{} + message.Infof("Using the url of (%s) to mutate the ArgoCD Repository Secret", state.GitServer.Address) - if err = json.Unmarshal(r.Object.Raw, &src); err != nil { + secret := corev1.Secret{} + if err = json.Unmarshal(r.Object.Raw, &secret); err != nil { return nil, fmt.Errorf(lang.ErrUnmarshal, err) } - decodedURL, err := base64.StdEncoding.DecodeString(src.Data.URL) - if err != nil { - message.Fatalf("Error decoding URL from Repository Secret %s", src.Data.URL) + + url, exists := secret.Data["url"] + if !exists { + return nil, fmt.Errorf("url field not found in argocd repository secret data") } - src.Data.URL = string(decodedURL) - patchedURL := src.Data.URL + + var repoCreds RepoCreds + repoCreds.URL = string(url) // Check if this is an update operation and the hostname is different from what we have in the zarfState // NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the zarfState // NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated. if isUpdate { - isPatched, err = helpers.DoHostnamesMatch(zarfState.GitServer.Address, src.Data.URL) + isPatched, err = helpers.DoHostnamesMatch(state.GitServer.Address, repoCreds.URL) if err != nil { return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err) } } + patchedURL := repoCreds.URL // Mutate the repoURL if necessary if isCreate || (isUpdate && !isPatched) { // Mutate the git URL so that the hostname matches the hostname in the Zarf state - transformedURL, err := transform.GitURL(zarfState.GitServer.Address, patchedURL, zarfState.GitServer.PushUsername) + transformedURL, err := transform.GitURL(state.GitServer.Address, repoCreds.URL, state.GitServer.PushUsername) if err != nil { - message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL) + return nil, fmt.Errorf("unable the git url: %w", err) } patchedURL = transformedURL.String() - message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL) + message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) } - // Patch updates of the repo spec - patches = populateArgoRepositoryPatchOperations(patchedURL, zarfState.GitServer.PullPassword) - return &operations.Result{ Allowed: true, - PatchOps: patches, + PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer), }, nil } // Patch updates of the Argo Repository Secret. -func populateArgoRepositoryPatchOperations(repoURL string, zarfGitPullPassword string) []operations.PatchOperation { +func populateArgoRepositoryPatchOperations(repoURL string, gitServer types.GitServerInfo) []operations.PatchOperation { var patches []operations.PatchOperation patches = append(patches, operations.ReplacePatchOperation("/data/url", base64.StdEncoding.EncodeToString([]byte(repoURL)))) - patches = append(patches, operations.ReplacePatchOperation("/data/username", base64.StdEncoding.EncodeToString([]byte(types.ZarfGitReadUser)))) - patches = append(patches, operations.ReplacePatchOperation("/data/password", base64.StdEncoding.EncodeToString([]byte(zarfGitPullPassword)))) + patches = append(patches, operations.ReplacePatchOperation("/data/username", base64.StdEncoding.EncodeToString([]byte(gitServer.PullUsername)))) + patches = append(patches, operations.ReplacePatchOperation("/data/password", base64.StdEncoding.EncodeToString([]byte(gitServer.PullPassword)))) return patches } diff --git a/src/internal/agent/hooks/argocd-repository_test.go b/src/internal/agent/hooks/argocd-repository_test.go new file mode 100644 index 0000000000..b84a3e8d07 --- /dev/null +++ b/src/internal/agent/hooks/argocd-repository_test.go @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + b64 "encoding/base64" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createArgoRepoAdmissionRequest(t *testing.T, op v1.Operation, argoRepo *corev1.Secret) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(argoRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestArgoRepoWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{GitServer: types.GitServerInfo{ + Address: "https://git-server.com", + PushUsername: "a-push-user", + PullPassword: "a-pull-password", + PullUsername: "a-pull-user", + }} + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewRepositorySecretMutationHook(ctx, c)) + + tests := []admissionTest{ + { + name: "should be mutated", + admissionReq: createArgoRepoAdmissionRequest(t, v1.Create, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + }, + Name: "argo-repo-secret", + Namespace: "argo", + }, + Data: map[string][]byte{ + "url": []byte("https://diff-git-server.com/podinfo"), + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/data/url", + b64.StdEncoding.EncodeToString([]byte("https://git-server.com/a-push-user/podinfo-1868163476")), + ), + operations.ReplacePatchOperation( + "/data/username", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullUsername)), + ), + operations.ReplacePatchOperation( + "/data/password", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), + ), + }, + code: http.StatusOK, + }, + { + name: "matching hostname on update should stay the same, but secret should be added", + admissionReq: createArgoRepoAdmissionRequest(t, v1.Update, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + }, + Name: "argo-repo-secret", + Namespace: "argo", + }, + Data: map[string][]byte{ + "url": []byte("https://git-server.com/podinfo"), + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/data/url", + b64.StdEncoding.EncodeToString([]byte("https://git-server.com/podinfo")), + ), + operations.ReplacePatchOperation( + "/data/username", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullUsername)), + ), + operations.ReplacePatchOperation( + "/data/password", + b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), + ), + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go index ea0a54f024..860d147811 100644 --- a/src/internal/agent/http/proxy.go +++ b/src/internal/agent/http/proxy.go @@ -5,6 +5,7 @@ package http import ( + "context" "crypto/tls" "fmt" "io" @@ -14,7 +15,7 @@ import ( "strings" "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/internal/agent/state" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" ) @@ -45,7 +46,12 @@ func proxyRequestTransform(r *http.Request) error { // We remove this so that go will encode and decode on our behalf (see https://pkg.go.dev/net/http#Transport DisableCompression) r.Header.Del("Accept-Encoding") - zarfState, err := state.GetZarfStateFromAgentPod() + c, err := cluster.NewCluster() + if err != nil { + return err + } + ctx := context.Background() + state, err := c.LoadZarfState(ctx) if err != nil { return err } @@ -55,31 +61,31 @@ func proxyRequestTransform(r *http.Request) error { // Setup authentication for each type of service based on User Agent switch { case isGitUserAgent(r.UserAgent()): - r.SetBasicAuth(zarfState.GitServer.PushUsername, zarfState.GitServer.PushPassword) + r.SetBasicAuth(state.GitServer.PushUsername, state.GitServer.PushPassword) case isNpmUserAgent(r.UserAgent()): - r.Header.Set("Authorization", "Bearer "+zarfState.ArtifactServer.PushToken) + r.Header.Set("Authorization", "Bearer "+state.ArtifactServer.PushToken) default: - r.SetBasicAuth(zarfState.ArtifactServer.PushUsername, zarfState.ArtifactServer.PushToken) + r.SetBasicAuth(state.ArtifactServer.PushUsername, state.ArtifactServer.PushToken) } // Transform the URL; if we see the NoTransform prefix, strip it; otherwise, transform the URL based on User Agent if strings.HasPrefix(r.URL.Path, transform.NoTransform) { switch { case isGitUserAgent(r.UserAgent()): - targetURL, err = transform.NoTransformTarget(zarfState.GitServer.Address, r.URL.Path) + targetURL, err = transform.NoTransformTarget(state.GitServer.Address, r.URL.Path) default: - targetURL, err = transform.NoTransformTarget(zarfState.ArtifactServer.Address, r.URL.Path) + targetURL, err = transform.NoTransformTarget(state.ArtifactServer.Address, r.URL.Path) } } else { switch { case isGitUserAgent(r.UserAgent()): - targetURL, err = transform.GitURL(zarfState.GitServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String(), zarfState.GitServer.PushUsername) + targetURL, err = transform.GitURL(state.GitServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String(), state.GitServer.PushUsername) case isPipUserAgent(r.UserAgent()): - targetURL, err = transform.PipTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.PipTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) case isNpmUserAgent(r.UserAgent()): - targetURL, err = transform.NpmTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.NpmTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) default: - targetURL, err = transform.GenTransformURL(zarfState.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) + targetURL, err = transform.GenTransformURL(state.ArtifactServer.Address, getTLSScheme(r.TLS)+r.Host+r.URL.String()) } } diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 2c56428fd7..49fa5a6e7e 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -31,8 +31,8 @@ func NewAdmissionServer(port string) *http.Server { // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) - argocdApplicationMutation := hooks.NewApplicationMutationHook() - argocdRepositoryMutation := hooks.NewRepositoryMutationHook() + argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) + argocdRepositoryMutation := hooks.NewRepositorySecretMutationHook(ctx, c) // Routers ah := admission.NewHandler() diff --git a/src/internal/agent/state/state.go b/src/internal/agent/state/state.go deleted file mode 100644 index 7c99acb4d2..0000000000 --- a/src/internal/agent/state/state.go +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package state provides helpers for interacting with the Zarf agent state. -package state - -import ( - "encoding/json" - "os" - - "github.com/defenseunicorns/zarf/src/types" -) - -const zarfStatePath = "/etc/zarf-state/state" - -// GetZarfStateFromAgentPod reads the state json file that was mounted into the agent pods. -func GetZarfStateFromAgentPod() (state *types.ZarfState, err error) { - // Read the state file - stateFile, err := os.ReadFile(zarfStatePath) - if err != nil { - return nil, err - } - - // Unmarshal the json file into a Go struct - return state, json.Unmarshal(stateFile, &state) -} From ab83d3720d38f9329a30768b109c8ae0cea41a42 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 23 May 2024 19:49:46 -0500 Subject: [PATCH 031/132] fix(release): do not delete testdata in release workflow (#2547) ## Description Fixes an issue where `make delete-packages` in the `cleanup-files` action of the release workflow deletes `src/pkg/packager/sources/testdata/zarf-package-wordpress-amd64-16.0.4.tar.zst`, causing `git` to be in a dirty state and the workflow to fail. image https://github.com/defenseunicorns/zarf/actions/runs/9216112744/job/25356360304#step:11:92 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 62bbf3b859..2f397ae821 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ destroy: ## Run `zarf destroy` on the current cluster rm -fr build delete-packages: ## Delete all Zarf package tarballs in the project recursively - find . -type f -name 'zarf-package-*' -delete + find . -type f -name 'zarf-package-*' -not -path '*/testdata/*' -print -delete # Note: the path to the main.go file is not used due to https://github.com/golang/go/issues/51831#issuecomment-1074188363 .PHONY: build From c09cac5a073c9aea582331550ba29f57e8d69532 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 28 May 2024 20:40:29 +0200 Subject: [PATCH 032/132] refactor: remove use of k8s info and nodes (#2551) ## Description Removes use of k8s info and nodes functions. ## Related Issue Relates to #2507 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/helm/chart.go | 4 +- src/pkg/cluster/common.go | 2 +- src/pkg/cluster/injector.go | 2 +- src/pkg/cluster/tunnel.go | 5 ++- src/pkg/k8s/common.go | 21 ++++------- src/pkg/k8s/info.go | 57 ----------------------------- src/pkg/k8s/nodes.go | 23 ------------ src/pkg/k8s/pods.go | 13 ++++--- src/pkg/k8s/tunnel.go | 7 +++- src/pkg/packager/common.go | 30 ++++++++++----- 10 files changed, 50 insertions(+), 114 deletions(-) delete mode 100644 src/pkg/k8s/info.go delete mode 100644 src/pkg/k8s/nodes.go diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go index 9252bf40d4..b145953173 100644 --- a/src/internal/packager/helm/chart.go +++ b/src/internal/packager/helm/chart.go @@ -370,12 +370,12 @@ func (h *Helm) loadChartData() (*chart.Chart, chartutil.Values, error) { func (h *Helm) migrateDeprecatedAPIs(latestRelease *release.Release) error { // Get the Kubernetes version from the current cluster - kubeVersion, err := h.cluster.GetServerVersion() + kubeVersion, err := h.cluster.Clientset.Discovery().ServerVersion() if err != nil { return err } - kubeGitVersion, err := semver.NewVersion(kubeVersion) + kubeGitVersion, err := semver.NewVersion(kubeVersion.String()) if err != nil { return err } diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go index e0a295cd3c..02b6f1da78 100644 --- a/src/pkg/cluster/common.go +++ b/src/pkg/cluster/common.go @@ -56,7 +56,7 @@ func NewCluster() (*Cluster, error) { } // Dogsled the version output. We just want to ensure no errors were returned to validate cluster connection. - _, err = c.GetServerVersion() + _, err = c.Clientset.Discovery().ServerVersion() if err != nil { return nil, err } diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 008df3915a..3412553d29 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -514,7 +514,7 @@ func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeM for _, pod := range pods.Items { nodeName := pod.Spec.NodeName - nodeDetails, err := c.GetNode(ctx, nodeName) + nodeDetails, err := c.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("unable to get the node %q: %w", nodeName, err) } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 424881b2fc..122a57abf4 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -218,7 +218,10 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu zt.remotePort = svc.Spec.Ports[0].TargetPort.IntValue() // if targetPort == 0, look for Port (which is required) if zt.remotePort == 0 { - zt.remotePort = c.FindPodContainerPort(ctx, svc) + zt.remotePort, err = c.FindPodContainerPort(ctx, svc) + if err != nil { + return TunnelInfo{}, err + } } // Add the url suffix too. diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index 66434f3caa..9041775825 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -9,11 +9,11 @@ import ( "fmt" "time" - v1 "k8s.io/api/core/v1" - "k8s.io/klog/v2" - "github.com/go-logr/logr/funcr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" // Include the cloud auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" @@ -48,12 +48,6 @@ func New(logger Log) (*K8s, error) { // WaitForHealthyCluster checks for an available K8s cluster every second until timeout. func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { - var ( - err error - nodes *v1.NodeList - pods *v1.PodList - ) - const waitDuration = 1 * time.Second timer := time.NewTimer(0) @@ -77,15 +71,16 @@ func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { } // Make sure there is at least one running Node - nodes, err = k.GetNodes(ctx) - if err != nil || len(nodes.Items) < 1 { + nodeList, err := k.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil || len(nodeList.Items) < 1 { k.Log("No nodes reporting healthy yet: %v\n", err) timer.Reset(waitDuration) continue } // Get the cluster pod list - if pods, err = k.GetAllPods(ctx); err != nil { + pods, err := k.GetAllPods(ctx) + if err != nil { k.Log("Could not get the pod list: %w", err) timer.Reset(waitDuration) continue @@ -93,7 +88,7 @@ func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { // Check that at least one pod is in the 'succeeded' or 'running' state for _, pod := range pods.Items { - if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodRunning { + if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodRunning { return nil } } diff --git a/src/pkg/k8s/info.go b/src/pkg/k8s/info.go deleted file mode 100644 index 1a19a837e1..0000000000 --- a/src/pkg/k8s/info.go +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "errors" - "fmt" - "strings" -) - -// GetArchitectures returns the cluster system architectures if found. -func (k *K8s) GetArchitectures(ctx context.Context) ([]string, error) { - nodes, err := k.GetNodes(ctx) - if err != nil { - return nil, err - } - - if len(nodes.Items) == 0 { - return nil, errors.New("could not identify node architecture") - } - - archMap := map[string]bool{} - - for _, node := range nodes.Items { - archMap[node.Status.NodeInfo.Architecture] = true - } - - architectures := []string{} - - for arch := range archMap { - architectures = append(architectures, arch) - } - - return architectures, nil -} - -// GetServerVersion retrieves and returns the k8s revision. -func (k *K8s) GetServerVersion() (version string, err error) { - versionInfo, err := k.Clientset.Discovery().ServerVersion() - if err != nil { - return "", fmt.Errorf("unable to get Kubernetes version from the cluster : %w", err) - } - - return versionInfo.String(), nil -} - -// MakeLabels is a helper to format a map of label key and value pairs into a single string for use as a selector. -func MakeLabels(labels map[string]string) string { - var out []string - for key, value := range labels { - out = append(out, fmt.Sprintf("%s=%s", key, value)) - } - return strings.Join(out, ",") -} diff --git a/src/pkg/k8s/nodes.go b/src/pkg/k8s/nodes.go deleted file mode 100644 index 134c00b140..0000000000 --- a/src/pkg/k8s/nodes.go +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetNodes returns a list of nodes from the k8s cluster. -func (k *K8s) GetNodes(ctx context.Context) (*corev1.NodeList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Nodes().List(ctx, metaOptions) -} - -// GetNode returns a node from the k8s cluster. -func (k *K8s) GetNode(ctx context.Context, nodeName string) (*corev1.Node, error) { - return k.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) -} diff --git a/src/pkg/k8s/pods.go b/src/pkg/k8s/pods.go index be9c72bec4..cbdc77161b 100644 --- a/src/pkg/k8s/pods.go +++ b/src/pkg/k8s/pods.go @@ -178,13 +178,16 @@ func (k *K8s) WaitForPodsAndContainers(ctx context.Context, target PodLookup, in // FindPodContainerPort will find a pod's container port from a service and return it. // // Returns 0 if no port is found. -func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) int { - selectorLabelsOfPods := MakeLabels(svc.Spec.Selector) +func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) (int, error) { + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: svc.Spec.Selector}) + if err != nil { + return 0, err + } pods := k.WaitForPodsAndContainers( ctx, PodLookup{ Namespace: svc.Namespace, - Selector: selectorLabelsOfPods, + Selector: selector.String(), }, nil, ) @@ -194,11 +197,11 @@ func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) int for _, container := range pod.Spec.Containers { for _, port := range container.Ports { if port.Name == svc.Spec.Ports[0].TargetPort.String() { - return int(port.ContainerPort) + return int(port.ContainerPort), nil } } } } - return 0 + return 0, nil } diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go index df49ac667a..7b890d2e20 100644 --- a/src/pkg/k8s/tunnel.go +++ b/src/pkg/k8s/tunnel.go @@ -254,13 +254,16 @@ func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, e if err != nil { return "", fmt.Errorf("unable to find the service: %w", err) } - selectorLabelsOfPods := MakeLabels(service.Spec.Selector) + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: service.Spec.Selector}) + if err != nil { + return "", err + } servicePods := tunnel.kube.WaitForPodsAndContainers( ctx, PodLookup{ Namespace: tunnel.namespace, - Selector: selectorLabelsOfPods, + Selector: selector.String(), }, nil, ) diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index f432b8d423..938167e52c 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -9,23 +9,23 @@ import ( "errors" "fmt" "os" - "strings" - "slices" + "strings" "github.com/Masterminds/semver/v3" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/template" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/variables" - "github.com/defenseunicorns/zarf/src/types" - - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/utils" + "github.com/defenseunicorns/zarf/src/pkg/variables" + "github.com/defenseunicorns/zarf/src/types" ) // Packager is the main struct for managing packages. @@ -227,14 +227,26 @@ func (p *Packager) validatePackageArchitecture(ctx context.Context) error { return nil } - clusterArchitectures, err := p.cluster.GetArchitectures(ctx) + // Get node architectures + nodeList, err := p.cluster.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) if err != nil { return lang.ErrUnableToCheckArch } + if len(nodeList.Items) == 0 { + return lang.ErrUnableToCheckArch + } + archMap := map[string]bool{} + for _, node := range nodeList.Items { + archMap[node.Status.NodeInfo.Architecture] = true + } + architectures := []string{} + for arch := range archMap { + architectures = append(architectures, arch) + } // Check if the package architecture and the cluster architecture are the same. - if !slices.Contains(clusterArchitectures, p.cfg.Pkg.Metadata.Architecture) { - return fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, p.cfg.Pkg.Metadata.Architecture, strings.Join(clusterArchitectures, ", ")) + if !slices.Contains(architectures, p.cfg.Pkg.Metadata.Architecture) { + return fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, p.cfg.Pkg.Metadata.Architecture, strings.Join(architectures, ", ")) } return nil From 9da740636f8e90dfb3edfd0d90717b11fe45d980 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 30 May 2024 06:18:58 -0500 Subject: [PATCH 033/132] test: shutdown http test servers (#2559) ## Description Shutdown HTTP test servers with `Close()` ## Related Issue Noticed we aren't shutting down HTTP test servers in a couple of our unit tests while working on #2558 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/packager/sources/new_test.go | 1 + src/pkg/utils/network_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index 2e20bc1c21..91ea3e2c2f 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -130,6 +130,7 @@ func TestPackageSource(t *testing.T) { defer f.Close() io.Copy(rw, f) })) + t.Cleanup(func() { ts.Close() }) tests := []struct { name string diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go index 4722993312..1728fe874b 100644 --- a/src/pkg/utils/network_test.go +++ b/src/pkg/utils/network_test.go @@ -85,6 +85,7 @@ func TestDownloadToFile(t *testing.T) { } rw.Write([]byte(content)) })) + t.Cleanup(func() { srv.Close() }) tests := []struct { name string From f69e42d7baa4e987f3dcf7a6c0d7b7347bf67e1a Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Thu, 30 May 2024 13:31:25 -0400 Subject: [PATCH 034/132] feat: adding labels to all resources mutated by the agent (#2557) ## Description Adds labels to any resource mutated by the agent ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- .../agent/hooks/argocd-application.go | 4 ++++ .../agent/hooks/argocd-application_test.go | 6 +++++ src/internal/agent/hooks/argocd-repository.go | 5 +++- .../agent/hooks/argocd-repository_test.go | 14 +++++++++++ src/internal/agent/hooks/common.go | 15 ++++++++++++ src/internal/agent/hooks/flux.go | 1 + src/internal/agent/hooks/flux_test.go | 12 ++++++++++ src/internal/agent/hooks/pods.go | 23 ++++++------------- src/internal/agent/hooks/pods_test.go | 12 ++++++---- 9 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 src/internal/agent/hooks/common.go diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 90a5b98744..0e037fbb00 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -17,6 +17,7 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" v1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Application is a definition of an ArgoCD Application resource. @@ -29,6 +30,7 @@ import ( // For more information: https://argo-cd.readthedocs.io/en/stable/user-guide/import/ type Application struct { Spec ApplicationSpec `json:"spec"` + metav1.ObjectMeta } // ApplicationSpec represents desired application state. Contains link to repository with application definition. @@ -93,6 +95,8 @@ func mutateApplication(ctx context.Context, r *v1.AdmissionRequest, cluster *clu } } + patches = append(patches, getLabelPatch(app.Labels)) + return &operations.Result{ Allowed: true, PatchOps: patches, diff --git a/src/internal/agent/hooks/argocd-application_test.go b/src/internal/agent/hooks/argocd-application_test.go index 5d241dd571..6ef39b2730 100644 --- a/src/internal/agent/hooks/argocd-application_test.go +++ b/src/internal/agent/hooks/argocd-application_test.go @@ -69,6 +69,12 @@ func TestArgoAppWebhook(t *testing.T) { "/spec/sources/1/repoURL", "https://git-server.com/a-push-user/almonds-640159520", ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), }, code: http.StatusOK, }, diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 9e643414d7..53d96af44b 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -95,9 +95,12 @@ func mutateRepositorySecret(ctx context.Context, r *v1.AdmissionRequest, cluster message.Debugf("original url of (%s) got mutated to (%s)", repoCreds.URL, patchedURL) } + patches := populateArgoRepositoryPatchOperations(patchedURL, state.GitServer) + patches = append(patches, getLabelPatch(secret.Labels)) + return &operations.Result{ Allowed: true, - PatchOps: populateArgoRepositoryPatchOperations(patchedURL, state.GitServer), + PatchOps: patches, }, nil } diff --git a/src/internal/agent/hooks/argocd-repository_test.go b/src/internal/agent/hooks/argocd-repository_test.go index b84a3e8d07..4506b682e1 100644 --- a/src/internal/agent/hooks/argocd-repository_test.go +++ b/src/internal/agent/hooks/argocd-repository_test.go @@ -73,6 +73,13 @@ func TestArgoRepoWebhook(t *testing.T) { "/data/password", b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + "zarf-agent": "patched", + }, + ), }, code: http.StatusOK, }, @@ -103,6 +110,13 @@ func TestArgoRepoWebhook(t *testing.T) { "/data/password", b64.StdEncoding.EncodeToString([]byte(state.GitServer.PullPassword)), ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "argocd.argoproj.io/secret-type": "repository", + "zarf-agent": "patched", + }, + ), }, code: http.StatusOK, }, diff --git a/src/internal/agent/hooks/common.go b/src/internal/agent/hooks/common.go new file mode 100644 index 0000000000..ed1de69797 --- /dev/null +++ b/src/internal/agent/hooks/common.go @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package hooks contains the mutation hooks for the Zarf agent. +package hooks + +import "github.com/defenseunicorns/zarf/src/internal/agent/operations" + +func getLabelPatch(currLabels map[string]string) operations.PatchOperation { + if currLabels == nil { + currLabels = make(map[string]string) + } + currLabels["zarf-agent"] = "patched" + return operations.ReplacePatchOperation("/metadata/labels", currLabels) +} diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 617b91901c..77382aecf3 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -85,6 +85,7 @@ func mutateGitRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster // Patch updates of the repo spec patches = populatePatchOperations(patchedURL) + patches = append(patches, getLabelPatch(repo.Labels)) return &operations.Result{ Allowed: true, diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux_test.go index cf56ac844a..a68cbfe591 100644 --- a/src/internal/agent/hooks/flux_test.go +++ b/src/internal/agent/hooks/flux_test.go @@ -64,6 +64,12 @@ func TestFluxMutationWebhook(t *testing.T) { "/spec/secretRef", fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), }, code: http.StatusOK, }, @@ -100,6 +106,12 @@ func TestFluxMutationWebhook(t *testing.T) { "/spec/secretRef", fluxmeta.LocalObjectReference{Name: config.ZarfGitServerSecretName}, ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), }, code: http.StatusOK, }, diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index 658c585b12..299b841f31 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -64,11 +64,11 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu } registryURL := state.RegistryInfo.Address - var patchOperations []operations.PatchOperation + var patches []operations.PatchOperation // Add the zarf secret to the podspec zarfSecret := []corev1.LocalObjectReference{{Name: config.ZarfImagePullSecretName}} - patchOperations = append(patchOperations, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) + patches = append(patches, operations.ReplacePatchOperation("/spec/imagePullSecrets", zarfSecret)) // update the image host for each init container for idx, container := range pod.Spec.InitContainers { @@ -78,7 +78,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod } - patchOperations = append(patchOperations, operations.ReplacePatchOperation(path, replacement)) + patches = append(patches, operations.ReplacePatchOperation(path, replacement)) } // update the image host for each ephemeral container @@ -89,7 +89,7 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod } - patchOperations = append(patchOperations, operations.ReplacePatchOperation(path, replacement)) + patches = append(patches, operations.ReplacePatchOperation(path, replacement)) } // update the image host for each normal container @@ -100,22 +100,13 @@ func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Clu message.Warnf(lang.AgentErrImageSwap, container.Image) continue // Continue, because we might as well attempt to mutate the other containers for this pod } - patchOperations = append(patchOperations, operations.ReplacePatchOperation(path, replacement)) + patches = append(patches, operations.ReplacePatchOperation(path, replacement)) } - // Add a label noting the zarf mutation - if pod.Labels == nil { - // If the labels path does not exist - create with map[string]string value - patchOperations = append(patchOperations, operations.AddPatchOperation("/metadata/labels", - map[string]string{ - "zarf-agent": "patched", - })) - } else { - patchOperations = append(patchOperations, operations.ReplacePatchOperation("/metadata/labels/zarf-agent", "patched")) - } + patches = append(patches, getLabelPatch(pod.Labels)) return &operations.Result{ Allowed: true, - PatchOps: patchOperations, + PatchOps: patches, }, nil } diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go index 8eca704f59..3a41f9227c 100644 --- a/src/internal/agent/hooks/pods_test.go +++ b/src/internal/agent/hooks/pods_test.go @@ -46,7 +46,8 @@ func TestPodMutationWebhook(t *testing.T) { name: "pod with label should be mutated", admissionReq: createPodAdmissionRequest(t, v1.Create, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{"should-be": "mutated"}, + Labels: map[string]string{"should-be": "mutated"}, + Annotations: map[string]string{"should-be": "mutated"}, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{{Image: "nginx"}}, @@ -78,8 +79,11 @@ func TestPodMutationWebhook(t *testing.T) { "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", ), operations.ReplacePatchOperation( - "/metadata/labels/zarf-agent", - "patched", + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + "should-be": "mutated", + }, ), }, code: http.StatusOK, @@ -116,7 +120,7 @@ func TestPodMutationWebhook(t *testing.T) { "/spec/containers/0/image", "127.0.0.1:31999/library/nginx:latest-zarf-3793515731", ), - operations.AddPatchOperation( + operations.ReplacePatchOperation( "/metadata/labels", map[string]string{"zarf-agent": "patched"}, ), From bc896974fce117c5304ea5240aba35e517632f70 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 30 May 2024 19:59:40 +0200 Subject: [PATCH 035/132] test: zarf init state (#2556) ## Description This change adds tests to Zarf init state. It also fixes bugs that were found during testing. * Existing Zarf namespace would receive agent ignore label * Existing Zarf namespace would not receive managed by label * Clusters with zero nodes would cause panic ## Related Issue Relates to #2550 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/cluster/state.go | 25 +++++- src/pkg/cluster/state_test.go | 165 +++++++++++++++++++++++++++++++++- 2 files changed, 187 insertions(+), 3 deletions(-) diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 83af027217..fbcccf7989 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -64,6 +64,9 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO if err != nil { return err } + if len(nodeList.Items) == 0 { + return fmt.Errorf("cannot init Zarf state in empty cluster") + } namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return err @@ -90,6 +93,10 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO } // Mark existing namespaces as ignored for the zarf agent to prevent mutating resources we don't own. for _, namespace := range namespaces.Items { + // Skip Zarf namespace if it already exists. + if namespace.Name == ZarfNamespaceName { + continue + } spinner.Updatef("Marking existing namespace %s as ignored by Zarf Agent", namespace.Name) if namespace.Labels == nil { // Ensure label map exists to avoid nil panic @@ -107,8 +114,22 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") zarfNamespace := NewZarfManagedNamespace(ZarfNamespaceName) - if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { - return fmt.Errorf("unable to create the zarf namespace: %w", err) + err = func() error { + _, err := c.Clientset.CoreV1().Namespaces().Create(ctx, zarfNamespace, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create the Zarf namespace: %w", err) + } + if err == nil { + return nil + } + _, err = c.Clientset.CoreV1().Namespaces().Update(ctx, zarfNamespace, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("unable to update the Zarf namespace: %w", err) + } + return nil + }() + if err != nil { + return err } // Wait up to 2 minutes for the default service account to be created. diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index 72f10cd873..e056aab5f4 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -5,16 +5,179 @@ package cluster import ( + "context" "fmt" "testing" + "time" + + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" "github.com/defenseunicorns/pkg/helpers" + + "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/types" - "github.com/stretchr/testify/require" ) +func TestInitZarfState(t *testing.T) { + tests := []struct { + name string + initOpts types.ZarfInitOptions + nodes []corev1.Node + namespaces []corev1.Namespace + secrets []corev1.Secret + expectedErr string + }{ + { + name: "no nodes in cluster", + expectedErr: "cannot init Zarf state in empty cluster", + }, + { + name: "no namespaces exist", + initOpts: types.ZarfInitOptions{}, + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + }, + { + name: "namespaces exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "kube-system", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "default", + }, + }, + }, + }, + { + name: "Zarf namespace exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ZarfNamespaceName, + }, + }, + }, + }, + { + name: "Zarf state exists", + nodes: []corev1.Node{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "node", + }, + }, + }, + namespaces: []corev1.Namespace{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: ZarfNamespaceName, + }, + }, + }, + secrets: []corev1.Secret{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: ZarfNamespaceName, + Name: ZarfStateSecretName, + }, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + cs := fake.NewSimpleClientset() + for _, node := range tt.nodes { + _, err := cs.CoreV1().Nodes().Create(ctx, &node, metav1.CreateOptions{}) + require.NoError(t, err) + } + for _, namespace := range tt.namespaces { + _, err := cs.CoreV1().Namespaces().Create(ctx, &namespace, metav1.CreateOptions{}) + require.NoError(t, err) + } + for _, secret := range tt.secrets { + _, err := cs.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(ctx, &secret, metav1.CreateOptions{}) + require.NoError(t, err) + } + c := &Cluster{ + &k8s.K8s{ + Clientset: cs, + Log: func(string, ...any) {}, + }, + } + + // Create default service account in Zarf namespace + go func() { + for { + time.Sleep(1 * time.Second) + ns, err := cs.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + if err != nil { + continue + } + sa := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: "default", + }, + } + cs.CoreV1().ServiceAccounts(ns.Name).Create(ctx, sa, metav1.CreateOptions{}) + break + } + }() + + err := c.InitZarfState(ctx, tt.initOpts) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } + require.NoError(t, err) + zarfNs, err := cs.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, map[string]string{"app.kubernetes.io/managed-by": "zarf"}, zarfNs.Labels) + _, err = cs.CoreV1().Secrets(zarfNs.Name).Get(ctx, ZarfStateSecretName, metav1.GetOptions{}) + require.NoError(t, err) + for _, ns := range tt.namespaces { + if ns.Name == zarfNs.Name { + continue + } + ns, err := cs.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, map[string]string{k8s.AgentLabel: "ignore"}, ns.Labels) + } + }) + } +} + // TODO: Change password gen method to make testing possible. func TestMergeZarfStateRegistry(t *testing.T) { t.Parallel() From 61e10a733693a81b18e51df07ec1e0df2c18fbf1 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 30 May 2024 20:38:39 +0200 Subject: [PATCH 036/132] refactor: remove use of k8s deprecations (#2560) ## Description Removes use of k8s deprecations functions. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/helm/chart.go | 53 ++++++++++++++++++++++------- src/pkg/k8s/deprecations.go | 44 ------------------------ 2 files changed, 41 insertions(+), 56 deletions(-) delete mode 100644 src/pkg/k8s/deprecations.go diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go index b145953173..3f0fc1ac8d 100644 --- a/src/internal/packager/helm/chart.go +++ b/src/internal/packager/helm/chart.go @@ -9,22 +9,24 @@ import ( "fmt" "time" - "github.com/defenseunicorns/pkg/helpers" - "github.com/Masterminds/semver/v3" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/types" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "sigs.k8s.io/yaml" - - "github.com/defenseunicorns/zarf/src/pkg/message" + plutoversionsfile "github.com/fairwindsops/pluto/v5" + plutoapi "github.com/fairwindsops/pluto/v5/pkg/api" + goyaml "github.com/goccy/go-yaml" "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chartutil" - "helm.sh/helm/v3/pkg/releaseutil" - "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/storage/driver" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/yaml" + + "github.com/defenseunicorns/pkg/helpers" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/types" ) // InstallOrUpgradeChart performs a helm install of the given chart. @@ -398,7 +400,7 @@ func (h *Helm) migrateDeprecatedAPIs(latestRelease *release.Release) error { return fmt.Errorf("failed to unmarshal manifest: %#v", err) } - rawData, manifestModified, _ := h.cluster.HandleDeprecations(rawData, *kubeGitVersion) + rawData, manifestModified, _ := handleDeprecations(rawData, *kubeGitVersion) manifestContent, err := yaml.Marshal(rawData) if err != nil { return fmt.Errorf("failed to marshal raw manifest after deprecation check: %#v", err) @@ -437,3 +439,30 @@ func (h *Helm) migrateDeprecatedAPIs(latestRelease *release.Release) error { return nil } + +// handleDeprecations takes in an unstructured object and the k8s version and returns the latest version of the object and if it was modified. +func handleDeprecations(rawData *unstructured.Unstructured, kubernetesVersion semver.Version) (*unstructured.Unstructured, bool, error) { + deprecatedVersionContent := &plutoapi.VersionFile{} + err := goyaml.Unmarshal(plutoversionsfile.Content(), deprecatedVersionContent) + if err != nil { + return rawData, false, err + } + for _, deprecation := range deprecatedVersionContent.DeprecatedVersions { + if deprecation.Component == "k8s" && deprecation.Kind == rawData.GetKind() && deprecation.Name == rawData.GetAPIVersion() { + removedVersion, err := semver.NewVersion(deprecation.RemovedIn) + if err != nil { + return rawData, false, err + } + + if removedVersion.LessThan(&kubernetesVersion) { + if deprecation.ReplacementAPI != "" { + rawData.SetAPIVersion(deprecation.ReplacementAPI) + return rawData, true, nil + } + + return nil, true, nil + } + } + } + return rawData, false, nil +} diff --git a/src/pkg/k8s/deprecations.go b/src/pkg/k8s/deprecations.go deleted file mode 100644 index ea43aa3c03..0000000000 --- a/src/pkg/k8s/deprecations.go +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "github.com/Masterminds/semver/v3" - plutoversionsfile "github.com/fairwindsops/pluto/v5" - "github.com/fairwindsops/pluto/v5/pkg/api" - goyaml "github.com/goccy/go-yaml" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -const k8sDeprecationComponent = "k8s" - -// HandleDeprecations takes in an unstructured object and the version of kubernetes in a cluster and returns a converted version of that object and whether it was modified (if applicable) -func (k *K8s) HandleDeprecations(rawData *unstructured.Unstructured, kubernetesVersion semver.Version) (*unstructured.Unstructured, bool, error) { - deprecatedVersionContent := &api.VersionFile{} - err := goyaml.Unmarshal(plutoversionsfile.Content(), deprecatedVersionContent) - if err != nil { - return rawData, false, err - } - - for _, deprecation := range deprecatedVersionContent.DeprecatedVersions { - if deprecation.Component == k8sDeprecationComponent && deprecation.Kind == rawData.GetKind() && deprecation.Name == rawData.GetAPIVersion() { - removedVersion, err := semver.NewVersion(deprecation.RemovedIn) - if err != nil { - return rawData, false, err - } - - if removedVersion.LessThan(&kubernetesVersion) { - if deprecation.ReplacementAPI != "" { - rawData.SetAPIVersion(deprecation.ReplacementAPI) - return rawData, true, nil - } - - return nil, true, nil - } - } - } - - return rawData, false, nil -} From ff83e191cc6170eb863833a34d16290fa1d0d43f Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 30 May 2024 15:38:37 -0500 Subject: [PATCH 037/132] test: remove validate pkg arch e2e test (#2563) ## Description Removes `TestMismatchedArchitectures` e2e test and refactors `TestValidatePackageArchitecture` unit test ## Related Issue Relates to #2562 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/packager/common_test.go | 134 +++++++++------------- src/test/e2e/29_mismatched_checks_test.go | 23 ---- 2 files changed, 57 insertions(+), 100 deletions(-) diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go index 3ead7f9b4b..e60b030ffc 100644 --- a/src/pkg/packager/common_test.go +++ b/src/pkg/packager/common_test.go @@ -5,7 +5,6 @@ package packager import ( "context" - "errors" "fmt" "testing" @@ -16,126 +15,107 @@ import ( "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - k8sTesting "k8s.io/client-go/testing" ) -// TestValidatePackageArchitecture verifies that Zarf validates package architecture against cluster architecture correctly. func TestValidatePackageArchitecture(t *testing.T) { t.Parallel() - type testCase struct { - name string - pkgArch string - clusterArchs []string - images []string - expectedError error - getArchError error - } - - testCases := []testCase{ + tests := []struct { + name string + pkgArch string + clusterArchs []string + images []string + wantErr error + }{ { - name: "architecture match", - pkgArch: "amd64", - clusterArchs: []string{"amd64"}, - images: []string{"nginx"}, - expectedError: nil, + name: "architecture match", + pkgArch: "amd64", + clusterArchs: []string{"amd64"}, + images: []string{"nginx"}, + wantErr: nil, }, { - name: "architecture mismatch", - pkgArch: "arm64", - clusterArchs: []string{"amd64"}, - images: []string{"nginx"}, - expectedError: fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, "arm64", "amd64"), + name: "architecture mismatch", + pkgArch: "arm64", + clusterArchs: []string{"amd64"}, + images: []string{"nginx"}, + wantErr: fmt.Errorf(lang.CmdPackageDeployValidateArchitectureErr, "arm64", "amd64"), }, { - name: "multiple cluster architectures", - pkgArch: "arm64", - clusterArchs: []string{"amd64", "arm64"}, - images: []string{"nginx"}, - expectedError: nil, + name: "multiple cluster architectures", + pkgArch: "arm64", + clusterArchs: []string{"amd64", "arm64"}, + images: []string{"nginx"}, + wantErr: nil, }, { - name: "ignore validation when package arch equals 'multi'", - pkgArch: "multi", - clusterArchs: []string{"not evaluated"}, - expectedError: nil, + name: "ignore validation when package arch equals 'multi'", + pkgArch: "multi", + clusterArchs: []string{"not evaluated"}, + wantErr: nil, }, { - name: "ignore validation when a package doesn't contain images", - pkgArch: "amd64", - images: []string{}, - clusterArchs: []string{"not evaluated"}, - expectedError: nil, + name: "ignore validation when a package doesn't contain images", + pkgArch: "amd64", + images: []string{}, + clusterArchs: []string{"not evaluated"}, + wantErr: nil, }, { - name: "test the error path when fetching cluster architecture fails", - pkgArch: "amd64", - images: []string{"nginx"}, - getArchError: errors.New("error fetching cluster architecture"), - expectedError: lang.ErrUnableToCheckArch, + name: "test the error path when fetching cluster architecture fails", + pkgArch: "amd64", + images: []string{"nginx"}, + wantErr: lang.ErrUnableToCheckArch, }, } - for _, testCase := range testCases { - testCase := testCase + for _, tt := range tests { + tt := tt - t.Run(testCase.name, func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { t.Parallel() - mockClient := fake.NewSimpleClientset() + cs := fake.NewSimpleClientset() logger := func(string, ...interface{}) {} - // Create a Packager instance with package architecture set and a mock Kubernetes client. p := &Packager{ cluster: &cluster.Cluster{ K8s: &k8s.K8s{ - Clientset: mockClient, + Clientset: cs, Log: logger, }, }, cfg: &types.PackagerConfig{ Pkg: types.ZarfPackage{ - Metadata: types.ZarfMetadata{Architecture: testCase.pkgArch}, + Metadata: types.ZarfMetadata{Architecture: tt.pkgArch}, Components: []types.ZarfComponent{ { - Images: testCase.images, + Images: tt.images, }, }, }, }, } - // Set up test data for fetching cluster architecture. - mockClient.Fake.PrependReactor("list", "nodes", func(_ k8sTesting.Action) (bool, runtime.Object, error) { - // Return an error for cases that test this error path. - if testCase.getArchError != nil { - return true, nil, testCase.getArchError - } - - nodeItems := []v1.Node{} - - for _, arch := range testCase.clusterArchs { - nodeItems = append(nodeItems, v1.Node{ - Status: v1.NodeStatus{ - NodeInfo: v1.NodeSystemInfo{ - Architecture: arch, - }, + for i, arch := range tt.clusterArchs { + node := &v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("node-%d-%s", i, tt.name), + }, + Status: v1.NodeStatus{ + NodeInfo: v1.NodeSystemInfo{ + Architecture: arch, }, - }) - } - - // Create a NodeList object to fetch cluster architecture with the mock client. - nodeList := &v1.NodeList{ - Items: nodeItems, + }, } - return true, nodeList, nil - }) - - err := p.validatePackageArchitecture(context.TODO()) + _, err := cs.CoreV1().Nodes().Create(context.Background(), node, metav1.CreateOptions{}) + require.NoError(t, err) + } - require.Equal(t, testCase.expectedError, err) + err := p.validatePackageArchitecture(context.Background()) + require.Equal(t, tt.wantErr, err) }) } } diff --git a/src/test/e2e/29_mismatched_checks_test.go b/src/test/e2e/29_mismatched_checks_test.go index 0522f5c04f..fef9a413dc 100644 --- a/src/test/e2e/29_mismatched_checks_test.go +++ b/src/test/e2e/29_mismatched_checks_test.go @@ -15,29 +15,6 @@ import ( "github.com/stretchr/testify/require" ) -// TestMismatchedArchitectures ensures that zarf produces an error -// when the package architecture doesn't match the target cluster architecture. -func TestMismatchedArchitectures(t *testing.T) { - t.Log("E2E: Mismatched architectures") - e2e.SetupWithCluster(t) - - var ( - mismatchedArch = e2e.GetMismatchedArch() - mismatchedGamesPackage = fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", mismatchedArch) - expectedErrorMessage = fmt.Sprintf("this package architecture is %s", mismatchedArch) - ) - - // Build dos-games package with different arch than the cluster arch. - stdOut, stdErr, err := e2e.Zarf("package", "create", "examples/dos-games/", "--architecture", mismatchedArch, "--confirm") - require.NoError(t, err, stdOut, stdErr) - defer e2e.CleanFiles(mismatchedGamesPackage) - - // Ensure zarf package deploy returns an error because of the mismatched architectures. - _, stdErr, err = e2e.Zarf("package", "deploy", mismatchedGamesPackage, "--confirm") - require.Error(t, err, stdErr) - require.Contains(t, e2e.StripMessageFormatting(stdErr), expectedErrorMessage) -} - // TestMismatchedVersions ensures that zarf produces a warning // when the initialized version of Zarf doesn't match the current CLI func TestMismatchedVersions(t *testing.T) { From 69a8507eea1125d86707b4899dfd6e2a2ec76252 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Fri, 31 May 2024 10:38:34 -0500 Subject: [PATCH 038/132] test: remove TestMismatchedVersions e2e test (#2564) ## Description Removes `TestMismatchedVersions` and adds `TestPrintBreakingChanges` unit test ## Related Issue Relates to #2562 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/message/message.go | 8 +- src/pkg/packager/common.go | 2 +- src/pkg/packager/deprecated/common.go | 77 ++++++++++--------- src/pkg/packager/deprecated/common_test.go | 58 ++++++++++++++ ...ig_file_test.go => 29_config_file_test.go} | 0 src/test/e2e/29_mismatched_checks_test.go | 67 ---------------- ...go => 30_component_action_cluster_test.go} | 0 ...t.go => 31_checksum_and_signature_test.go} | 0 ..._test.go => 32_component_webhooks_test.go} | 0 ...st.go => 33_manifest_with_symlink_test.go} | 0 ...test.go => 34_custom_init_package_test.go} | 0 ...ries_test.go => 35_custom_retries_test.go} | 0 ..._test.go => 36_pod_without_labels_test.go} | 0 13 files changed, 106 insertions(+), 106 deletions(-) create mode 100644 src/pkg/packager/deprecated/common_test.go rename src/test/e2e/{30_config_file_test.go => 29_config_file_test.go} (100%) delete mode 100644 src/test/e2e/29_mismatched_checks_test.go rename src/test/e2e/{31_component_action_cluster_test.go => 30_component_action_cluster_test.go} (100%) rename src/test/e2e/{32_checksum_and_signature_test.go => 31_checksum_and_signature_test.go} (100%) rename src/test/e2e/{33_component_webhooks_test.go => 32_component_webhooks_test.go} (100%) rename src/test/e2e/{34_manifest_with_symlink_test.go => 33_manifest_with_symlink_test.go} (100%) rename src/test/e2e/{35_custom_init_package_test.go => 34_custom_init_package_test.go} (100%) rename src/test/e2e/{36_custom_retries_test.go => 35_custom_retries_test.go} (100%) rename src/test/e2e/{37_pod_without_labels_test.go => 36_pod_without_labels_test.go} (100%) diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 713bb90cbe..c3eab711bf 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -7,6 +7,7 @@ package message import ( "encoding/json" "fmt" + "io" "net/http" "os" "runtime/debug" @@ -59,6 +60,11 @@ func (d *DebugWriter) Write(raw []byte) (int, error) { } func init() { + InitializePTerm(os.Stderr) +} + +// InitializePTerm sets the default styles and output for pterm. +func InitializePTerm(w io.Writer) { pterm.ThemeDefault.SuccessMessageStyle = *pterm.NewStyle(pterm.FgLightGreen) // Customize default error. pterm.Success.Prefix = pterm.Prefix{ @@ -73,7 +79,7 @@ func init() { Text: " •", } - pterm.SetDefaultOutput(os.Stderr) + pterm.SetDefaultOutput(w) } // UseLogFile wraps a given file in a PausableWriter diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index 938167e52c..eddce07a53 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -212,7 +212,7 @@ func (p *Packager) attemptClusterChecks(ctx context.Context) (err error) { // Check for any breaking changes between the initialized Zarf version and this CLI if existingInitPackage, _ := p.cluster.GetDeployedPackage(ctx, "init"); existingInitPackage != nil { // Use the build version instead of the metadata since this will support older Zarf versions - deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version) + deprecated.PrintBreakingChanges(os.Stderr, existingInitPackage.Data.Build.Version, config.CLIVersion) } spinner.Success() diff --git a/src/pkg/packager/deprecated/common.go b/src/pkg/packager/deprecated/common.go index 3583eac9a4..4867ac52b2 100644 --- a/src/pkg/packager/deprecated/common.go +++ b/src/pkg/packager/deprecated/common.go @@ -6,24 +6,33 @@ package deprecated import ( "fmt" + "io" "strings" "slices" "github.com/Masterminds/semver/v3" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" "github.com/pterm/pterm" ) -// BreakingChange represents a breaking change that happened on a specified Zarf version +// BreakingChange represents a breaking change that happened on a specified Zarf version. type BreakingChange struct { version *semver.Version title string mitigation string } +// String returns the string representation of the BreakingChange. +func (bc BreakingChange) String() string { + return fmt.Sprintf("%s\n\n - %s\n %s\n", + pterm.Bold.Sprintf(bc.title), + pterm.Bold.Sprint("Mitigation:"), + strings.ReplaceAll(message.Paragraphn(96, "%s", pterm.FgLightCyan.Sprint(bc.mitigation)), "\n", "\n "), + ) +} + // List of migrations tracked in the zarf.yaml build data. const ( // This should be updated when a breaking change is introduced to the Zarf package structure. See: https://github.com/defenseunicorns/zarf/releases/tag/v0.27.0 @@ -32,15 +41,6 @@ const ( PluralizeSetVariable = "pluralize-set-variable" ) -// List of breaking changes to warn the user of. -var breakingChanges = []BreakingChange{ - { - version: semver.New(0, 26, 0, "", ""), - title: "Zarf container images are now mutated based on tag instead of repository name.", - mitigation: "Reinitialize the cluster using v0.26.0 or later and redeploy existing packages to update the image references (you can view existing packages with 'zarf package list' and view cluster images with 'zarf tools registry catalog').", - }, -} - // MigrateComponent runs all migrations on a component. // Build should be empty on package create, but include just in case someone copied a zarf.yaml from a zarf package. func MigrateComponent(build types.ZarfBuildData, component types.ZarfComponent) (migratedComponent types.ZarfComponent, warnings []string) { @@ -77,47 +77,50 @@ func MigrateComponent(build types.ZarfBuildData, component types.ZarfComponent) return migratedComponent, warnings } -// PrintBreakingChanges prints the breaking changes between the provided version and the current CLIVersion -func PrintBreakingChanges(deployedZarfVersion string) { +// PrintBreakingChanges prints the breaking changes between the provided version and the current CLIVersion. +func PrintBreakingChanges(w io.Writer, deployedZarfVersion, cliVersion string) { deployedSemver, err := semver.NewVersion(deployedZarfVersion) if err != nil { message.Debugf("Unable to check for breaking changes between Zarf versions") return } - applicableBreakingChanges := []BreakingChange{} + // List of breaking changes to warn the user of. + var breakingChanges = []BreakingChange{ + { + version: semver.MustParse("0.26.0"), + title: "Zarf container images are now mutated based on tag instead of repository name.", + mitigation: "Reinitialize the cluster using v0.26.0 or later and redeploy existing packages to update the image references (you can view existing packages with 'zarf package list' and view cluster images with 'zarf tools registry catalog').", + }, + } - // Calculate the applicable breaking changes + // Calculate the applicable breaking changes. + var applicableBreakingChanges []BreakingChange for _, breakingChange := range breakingChanges { if deployedSemver.LessThan(breakingChange.version) { applicableBreakingChanges = append(applicableBreakingChanges, breakingChange) } } - if len(applicableBreakingChanges) > 0 { - // Print header information - message.HorizontalRule() - message.Title("Potential Breaking Changes", "breaking changes that may cause issues with this package") - - // Print information about the versions - format := pterm.FgYellow.Sprint("CLI version ") + "%s" + pterm.FgYellow.Sprint(" is being used to deploy to a cluster that was initialized with ") + - "%s" + pterm.FgYellow.Sprint(". Between these versions there are the following breaking changes to consider:") - cliVersion := pterm.Bold.Sprintf(config.CLIVersion) - deployedVersion := pterm.Bold.Sprintf(deployedZarfVersion) - message.Warnf(format, cliVersion, deployedVersion) - - // Print each applicable breaking change - for idx, applicableBreakingChange := range applicableBreakingChanges { - titleFormat := pterm.Bold.Sprintf("\n %d. ", idx+1) + "%s" - - pterm.Printfln(titleFormat, applicableBreakingChange.title) + if len(applicableBreakingChanges) == 0 { + return + } - mitigationText := message.Paragraphn(96, "%s", pterm.FgLightCyan.Sprint(applicableBreakingChange.mitigation)) + // Print header information + message.HorizontalRule() + message.Title("Potential Breaking Changes", "breaking changes that may cause issues with this package") - pterm.Printfln("\n - %s", pterm.Bold.Sprint("Mitigation:")) - pterm.Printfln(" %s", strings.ReplaceAll(mitigationText, "\n", "\n ")) - } + // Print information about the versions + format := pterm.FgYellow.Sprint("CLI version ") + "%s" + pterm.FgYellow.Sprint(" is being used to deploy to a cluster that was initialized with ") + + "%s" + pterm.FgYellow.Sprint(". Between these versions there are the following breaking changes to consider:") + cliVersion = pterm.Bold.Sprintf(cliVersion) + deployedZarfVersion = pterm.Bold.Sprintf(deployedZarfVersion) + message.Warnf(format, cliVersion, deployedZarfVersion) - message.HorizontalRule() + // Print each applicable breaking change + for i, applicableBreakingChange := range applicableBreakingChanges { + fmt.Fprintf(w, "\n %d. %s", i+1, applicableBreakingChange.String()) } + + message.HorizontalRule() } diff --git a/src/pkg/packager/deprecated/common_test.go b/src/pkg/packager/deprecated/common_test.go new file mode 100644 index 0000000000..6dec381554 --- /dev/null +++ b/src/pkg/packager/deprecated/common_test.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package deprecated handles package deprecations and migrations +package deprecated + +import ( + "bytes" + "testing" + + "github.com/Masterminds/semver/v3" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/stretchr/testify/require" +) + +func TestPrintBreakingChanges(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + deployedVersion string + cliVersion string + breakingChanges []BreakingChange + }{ + { + name: "No breaking changes", + deployedVersion: "0.26.0", + cliVersion: "0.26.0", + breakingChanges: []BreakingChange{}, + }, + { + name: "agent breaking change", + deployedVersion: "0.25.0", + cliVersion: "0.26.0", + breakingChanges: []BreakingChange{ + { + version: semver.MustParse("0.26.0"), + title: "Zarf container images are now mutated based on tag instead of repository name.", + mitigation: "Reinitialize the cluster using v0.26.0 or later and redeploy existing packages to update the image references (you can view existing packages with 'zarf package list' and view cluster images with 'zarf tools registry catalog').", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var output bytes.Buffer + message.InitializePTerm(&output) + PrintBreakingChanges(&output, tt.deployedVersion, tt.cliVersion) + for _, bc := range tt.breakingChanges { + require.Contains(t, output.String(), bc.String()) + } + t.Log(output.String()) + }) + } +} diff --git a/src/test/e2e/30_config_file_test.go b/src/test/e2e/29_config_file_test.go similarity index 100% rename from src/test/e2e/30_config_file_test.go rename to src/test/e2e/29_config_file_test.go diff --git a/src/test/e2e/29_mismatched_checks_test.go b/src/test/e2e/29_mismatched_checks_test.go deleted file mode 100644 index fef9a413dc..0000000000 --- a/src/test/e2e/29_mismatched_checks_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package test provides e2e tests for Zarf. -package test - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "path/filepath" - "testing" - - "github.com/defenseunicorns/zarf/src/types" - "github.com/stretchr/testify/require" -) - -// TestMismatchedVersions ensures that zarf produces a warning -// when the initialized version of Zarf doesn't match the current CLI -func TestMismatchedVersions(t *testing.T) { - t.Log("E2E: Mismatched versions") - e2e.SetupWithCluster(t) - - var ( - expectedWarningMessage = "Potential Breaking Changes" - ) - - // Get the current init package secret - initPkg := types.DeployedPackage{} - base64Pkg, _, err := e2e.Kubectl("get", "secret", "zarf-package-init", "-n", "zarf", "-o", "jsonpath={.data.data}") - require.NoError(t, err) - jsonPkg, err := base64.StdEncoding.DecodeString(base64Pkg) - require.NoError(t, err) - fmt.Println(string(jsonPkg)) - err = json.Unmarshal(jsonPkg, &initPkg) - require.NoError(t, err) - - // Edit the build data to trigger the breaking change check - initPkg.Data.Build.Version = "v0.25.0" - - // Delete the package secret - _, _, err = e2e.Kubectl("delete", "secret", "zarf-package-init", "-n", "zarf") - require.NoError(t, err) - - // Create a new secret with the modified data - jsonPkgModified, err := json.Marshal(initPkg) - require.NoError(t, err) - _, _, err = e2e.Kubectl("create", "secret", "generic", "zarf-package-init", "-n", "zarf", fmt.Sprintf("--from-literal=data=%s", string(jsonPkgModified))) - require.NoError(t, err) - - path := filepath.Join("build", fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch)) - - // Deploy the games package - stdOut, stdErr, err := e2e.Zarf("package", "deploy", path, "--confirm") - require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, expectedWarningMessage) - - // Remove the games package - stdOut, stdErr, err = e2e.Zarf("package", "remove", "dos-games", "--confirm") - require.NoError(t, err, stdOut, stdErr) - - // Reset the package secret - _, _, err = e2e.Kubectl("delete", "secret", "zarf-package-init", "-n", "zarf") - require.NoError(t, err) - _, _, err = e2e.Kubectl("create", "secret", "generic", "zarf-package-init", "-n", "zarf", fmt.Sprintf("--from-literal=data=%s", string(jsonPkg))) - require.NoError(t, err) -} diff --git a/src/test/e2e/31_component_action_cluster_test.go b/src/test/e2e/30_component_action_cluster_test.go similarity index 100% rename from src/test/e2e/31_component_action_cluster_test.go rename to src/test/e2e/30_component_action_cluster_test.go diff --git a/src/test/e2e/32_checksum_and_signature_test.go b/src/test/e2e/31_checksum_and_signature_test.go similarity index 100% rename from src/test/e2e/32_checksum_and_signature_test.go rename to src/test/e2e/31_checksum_and_signature_test.go diff --git a/src/test/e2e/33_component_webhooks_test.go b/src/test/e2e/32_component_webhooks_test.go similarity index 100% rename from src/test/e2e/33_component_webhooks_test.go rename to src/test/e2e/32_component_webhooks_test.go diff --git a/src/test/e2e/34_manifest_with_symlink_test.go b/src/test/e2e/33_manifest_with_symlink_test.go similarity index 100% rename from src/test/e2e/34_manifest_with_symlink_test.go rename to src/test/e2e/33_manifest_with_symlink_test.go diff --git a/src/test/e2e/35_custom_init_package_test.go b/src/test/e2e/34_custom_init_package_test.go similarity index 100% rename from src/test/e2e/35_custom_init_package_test.go rename to src/test/e2e/34_custom_init_package_test.go diff --git a/src/test/e2e/36_custom_retries_test.go b/src/test/e2e/35_custom_retries_test.go similarity index 100% rename from src/test/e2e/36_custom_retries_test.go rename to src/test/e2e/35_custom_retries_test.go diff --git a/src/test/e2e/37_pod_without_labels_test.go b/src/test/e2e/36_pod_without_labels_test.go similarity index 100% rename from src/test/e2e/37_pod_without_labels_test.go rename to src/test/e2e/36_pod_without_labels_test.go From 0c02643b9f5631a7bdd98650cc87c74901190771 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 31 May 2024 12:33:44 -0400 Subject: [PATCH 039/132] test: delete agent e2e label test (#2568) ## Description I believe this test is no longer necessary since we've added unit tests to the agent Relates to #2562 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/test/e2e/36_pod_without_labels_test.go | 35 ------------------- .../packages/37-pod-without-labels/pod.yaml | 13 ------- 2 files changed, 48 deletions(-) delete mode 100644 src/test/e2e/36_pod_without_labels_test.go delete mode 100644 src/test/packages/37-pod-without-labels/pod.yaml diff --git a/src/test/e2e/36_pod_without_labels_test.go b/src/test/e2e/36_pod_without_labels_test.go deleted file mode 100644 index 4d249dd3ee..0000000000 --- a/src/test/e2e/36_pod_without_labels_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package test provides e2e tests for Zarf. -package test - -import ( - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestPodWithoutLabels(t *testing.T) { - t.Log("E2E: Pod Without Labels") - e2e.SetupWithCluster(t) - - // Path to pod manifest containing 0 lavbels - buildPath := filepath.Join("src", "test", "packages", "37-pod-without-labels", "pod.yaml") - - // Create the testing namespace - _, _, err := e2e.Kubectl("create", "ns", "pod-label") - require.NoError(t, err) - - // Create the pod without labels - // This is not an image zarf will have in the registry - but the agent was failing to admit on an internal server error before completing admission - _, _, err = e2e.Kubectl("create", "-f", buildPath, "-n", "pod-label") - require.NoError(t, err) - - // Cleanup - _, _, err = e2e.Kubectl("delete", "-f", buildPath, "-n", "pod-label") - require.NoError(t, err) - _, _, err = e2e.Kubectl("delete", "ns", "pod-label") - require.NoError(t, err) -} diff --git a/src/test/packages/37-pod-without-labels/pod.yaml b/src/test/packages/37-pod-without-labels/pod.yaml deleted file mode 100644 index a1ea2ac32b..0000000000 --- a/src/test/packages/37-pod-without-labels/pod.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - creationTimestamp: null - name: test -spec: - containers: - - image: nginx - name: test - resources: {} - dnsPolicy: ClusterFirst - restartPolicy: Always -status: {} \ No newline at end of file From 90f038ea100b6221f021a1b0abf0c9fd7942c23f Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 3 Jun 2024 17:58:49 +0200 Subject: [PATCH 040/132] fix: add custom error printing for Zarf commands (#2575) ## Description This change adds some custom logic for printing returned errors from Cobra commands. This will allow embedded tools like Helm to behave the same as the original tools. While the Zarf commands will print errors in the same expected error format. ## Related Issue Relates to #2567 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/cmd/root.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/cmd/root.go b/src/cmd/root.go index 83fb28e61d..52e10044c1 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -17,6 +17,7 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" + "github.com/pterm/pterm" "github.com/spf13/cobra" ) @@ -44,9 +45,11 @@ var rootCmd = &cobra.Command{ common.SetupCLI() }, - Short: lang.RootCmdShort, - Long: lang.RootCmdLong, - Args: cobra.MaximumNArgs(1), + Short: lang.RootCmdShort, + Long: lang.RootCmdLong, + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, + SilenceErrors: true, Run: func(cmd *cobra.Command, args []string) { zarfLogo := message.GetLogo() _, _ = fmt.Fprintln(os.Stderr, zarfLogo) @@ -65,7 +68,17 @@ var rootCmd = &cobra.Command{ // Execute is the entrypoint for the CLI. func Execute() { - cobra.CheckErr(rootCmd.Execute()) + cmd, err := rootCmd.ExecuteC() + if err == nil { + return + } + comps := strings.Split(cmd.CommandPath(), " ") + if len(comps) > 1 && comps[1] == "tools" { + cmd.PrintErrln(cmd.ErrPrefix(), err.Error()) + } else { + pterm.Error.Println(err.Error()) + } + os.Exit(1) } func init() { From 1749dd50c6a447810313e2d5645e269e114db659 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 3 Jun 2024 18:39:21 +0200 Subject: [PATCH 041/132] refactor: remove use of k8s dynamic (#2561) ## Description Removes use of k8s dynamic functions. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Lucas Rodriguez --- src/internal/packager/git/gitea.go | 40 +++++++---- src/internal/packager/helm/post-render.go | 56 ++++++++++++--- src/pkg/k8s/dynamic.go | 83 ----------------------- 3 files changed, 74 insertions(+), 105 deletions(-) delete mode 100644 src/pkg/k8s/dynamic.go diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go index d243337d3c..c6644c6b1a 100644 --- a/src/internal/packager/git/gitea.go +++ b/src/internal/packager/git/gitea.go @@ -10,17 +10,17 @@ import ( "encoding/json" "fmt" "io" + netHttp "net/http" "os" "time" - netHttp "net/http" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" - "k8s.io/apimachinery/pkg/runtime/schema" ) // CreateTokenResponse is the response given from creating a token in Gitea @@ -253,24 +253,38 @@ func UpdateGiteaPVC(ctx context.Context, shouldRollBack bool) (string, error) { } pvcName := os.Getenv("ZARF_VAR_GIT_SERVER_EXISTING_PVC") - groupKind := schema.GroupKind{ - Group: "", - Kind: "PersistentVolumeClaim", - } - labels := map[string]string{"app.kubernetes.io/managed-by": "Helm"} - annotations := map[string]string{"meta.helm.sh/release-name": "zarf-gitea", "meta.helm.sh/release-namespace": "zarf"} if shouldRollBack { - err = c.K8s.RemoveLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) - return "false", err + pvc, err := c.Clientset.CoreV1().PersistentVolumeClaims(cluster.ZarfNamespaceName).Get(ctx, pvcName, metav1.GetOptions{}) + if err != nil { + return "false", err + } + delete(pvc.Labels, "app.kubernetes.io/managed-by") + delete(pvc.Annotations, "meta.helm.sh/release-name") + delete(pvc.Annotations, "meta.helm.sh/release-namespace") + _, err = c.Clientset.CoreV1().PersistentVolumeClaims(cluster.ZarfNamespaceName).Update(ctx, pvc, metav1.UpdateOptions{}) + if err != nil { + return "false", err + } + return "false", nil } if pvcName == "data-zarf-gitea-0" { - err = c.K8s.AddLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) - return "true", err + pvc, err := c.Clientset.CoreV1().PersistentVolumeClaims(cluster.ZarfNamespaceName).Get(ctx, pvcName, metav1.GetOptions{}) + if err != nil { + return "true", err + } + pvc.Labels["app.kubernetes.io/managed-by"] = "Helm" + pvc.Annotations["meta.helm.sh/release-name"] = "zarf-gitea" + pvc.Annotations["meta.helm.sh/release-namespace"] = "zarf" + _, err = c.Clientset.CoreV1().PersistentVolumeClaims(cluster.ZarfNamespaceName).Update(ctx, pvc, metav1.UpdateOptions{}) + if err != nil { + return "true", err + } + return "true", nil } - return "false", err + return "false", nil } // DoHTTPThings adds http request boilerplate and perform the request, checking for a successful response. diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 4be00a1b11..da71335201 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -20,6 +20,8 @@ import ( "github.com/defenseunicorns/zarf/src/types" "helm.sh/helm/v3/pkg/releaseutil" corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/restmapper" "sigs.k8s.io/yaml" kerrors "k8s.io/apimachinery/pkg/api/errors" @@ -174,6 +176,16 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { } func (r *renderer) editHelmResources(ctx context.Context, resources []releaseutil.Manifest, finalManifestsOutput *bytes.Buffer) error { + dc, err := dynamic.NewForConfig(r.cluster.RestConfig) + if err != nil { + return err + } + groupResources, err := restmapper.GetAPIGroupResources(r.cluster.Clientset.Discovery()) + if err != nil { + return err + } + mapper := restmapper.NewDiscoveryRESTMapper(groupResources) + for _, resource := range resources { // parse to unstructured to have access to more data than just the name rawData := &unstructured.Unstructured{} @@ -199,8 +211,13 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti case "Service": // Check service resources for the zarf-connect label labels := rawData.GetLabels() + if labels == nil { + labels = map[string]string{} + } annotations := rawData.GetAnnotations() - + if annotations == nil { + annotations = map[string]string{} + } if key, keyExists := labels[config.ZarfConnectLabelName]; keyExists { // If there is a zarf-connect label message.Debugf("Match helm service %s for zarf connection %s", rawData.GetName(), key) @@ -226,14 +243,35 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti deployedNamespace = r.chart.Namespace } - helmLabels := map[string]string{"app.kubernetes.io/managed-by": "Helm"} - helmAnnotations := map[string]string{ - "meta.helm.sh/release-name": r.chart.ReleaseName, - "meta.helm.sh/release-namespace": r.chart.Namespace, - } - - if err := r.cluster.AddLabelsAndAnnotations(ctx, deployedNamespace, rawData.GetName(), rawData.GroupVersionKind().GroupKind(), helmLabels, helmAnnotations); err != nil { - // Print a debug message since this could just be because the resource doesn't exist + err := func() error { + mapping, err := mapper.RESTMapping(rawData.GroupVersionKind().GroupKind()) + if err != nil { + return err + } + resource, err := dc.Resource(mapping.Resource).Namespace(deployedNamespace).Get(ctx, rawData.GetName(), metav1.GetOptions{}) + if err != nil { + return err + } + labels := resource.GetLabels() + if labels == nil { + labels = map[string]string{} + } + labels["app.kubernetes.io/managed-by"] = "Helm" + resource.SetLabels(labels) + annotations := resource.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + annotations["meta.helm.sh/release-name"] = r.chart.ReleaseName + annotations["meta.helm.sh/release-namespace"] = r.chart.Namespace + resource.SetAnnotations(annotations) + _, err = dc.Resource(mapping.Resource).Namespace(deployedNamespace).Update(ctx, resource, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }() + if err != nil { message.Debugf("Unable to adopt resource %s: %s", rawData.GetName(), err.Error()) } } diff --git a/src/pkg/k8s/dynamic.go b/src/pkg/k8s/dynamic.go deleted file mode 100644 index 59f295f26b..0000000000 --- a/src/pkg/k8s/dynamic.go +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/discovery" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/restmapper" -) - -// AddLabelsAndAnnotations adds the provided labels and annotations to the specified K8s resource -func (k *K8s) AddLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, false) -} - -// RemoveLabelsAndAnnotations removes the provided labels and annotations to the specified K8s resource -func (k *K8s) RemoveLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, true) -} - -// updateLabelsAndAnnotations updates the provided labels and annotations to the specified K8s resource -func (k *K8s) updateLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string, isRemove bool) error { - dynamicClient := dynamic.NewForConfigOrDie(k.RestConfig) - - discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(k.RestConfig) - - groupResources, err := restmapper.GetAPIGroupResources(discoveryClient) - if err != nil { - return err - } - mapper := restmapper.NewDiscoveryRESTMapper(groupResources) - - mapping, err := mapper.RESTMapping(groupKind) - if err != nil { - return err - } - - deployedResource, err := dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Get(ctx, resourceName, metav1.GetOptions{}) - if err != nil { - return err - } - - // Pull the existing labels from the rendered resource - deployedLabels := deployedResource.GetLabels() - if deployedLabels == nil { - // Ensure label map exists to avoid nil panic - deployedLabels = make(map[string]string) - } - for key, value := range labels { - if isRemove { - delete(deployedLabels, key) - } else { - deployedLabels[key] = value - } - } - - deployedResource.SetLabels(deployedLabels) - - // Pull the existing annotations from the rendered resource - deployedAnnotations := deployedResource.GetAnnotations() - if deployedAnnotations == nil { - // Ensure label map exists to avoid nil panic - deployedAnnotations = make(map[string]string) - } - for key, value := range annotations { - if isRemove { - delete(deployedAnnotations, key) - } else { - deployedAnnotations[key] = value - } - } - - deployedResource.SetAnnotations(deployedAnnotations) - - _, err = dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Update(ctx, deployedResource, metav1.UpdateOptions{}) - return err -} From 0527bead547a9ba287848f690f1bf42b8bdb9b14 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 3 Jun 2024 20:03:02 +0200 Subject: [PATCH 042/132] refactor: remove use of k8s namespace (#2550) ## Description This change removes the k8s namespace code and replaces it with direct use of the client set instead. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- --- go.mod | 3 +- src/internal/packager/helm/post-render.go | 19 ++++-- src/pkg/cluster/namespace.go | 27 +++++++- src/pkg/cluster/secrets.go | 11 ++-- src/pkg/cluster/state.go | 11 ++-- src/pkg/cluster/zarf.go | 8 ++- src/pkg/k8s/namespace.go | 79 ----------------------- src/pkg/packager/deploy.go | 22 ++++++- 8 files changed, 77 insertions(+), 103 deletions(-) delete mode 100644 src/pkg/k8s/namespace.go diff --git a/go.mod b/go.mod index 1275140145..137284437c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,6 @@ go 1.21.8 replace github.com/xeipuuv/gojsonschema => github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 require ( - cuelang.org/go v0.7.0 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Masterminds/semver/v3 v3.2.1 github.com/agnivade/levenshtein v1.1.1 @@ -62,6 +61,8 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require cuelang.org/go v0.7.0 // indirect + require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index da71335201..f4789fdcca 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -11,6 +11,7 @@ import ( "os" "path/filepath" "reflect" + "slices" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" @@ -119,12 +120,15 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { c := r.cluster - existingNamespaces, _ := c.GetNamespaces(ctx) + namespaceList, err := r.cluster.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } for name, namespace := range r.namespaces { // Check to see if this namespace already exists var existingNamespace bool - for _, serverNamespace := range existingNamespaces.Items { + for _, serverNamespace := range namespaceList.Items { if serverNamespace.Name == name { existingNamespace = true } @@ -132,16 +136,19 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { if !existingNamespace { // This is a new namespace, add it - if _, err := c.CreateNamespace(ctx, namespace); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{}) + if err != nil { return fmt.Errorf("unable to create the missing namespace %s", name) } } else if r.cfg.DeployOpts.AdoptExistingResources { - if r.cluster.IsInitialNamespace(name) { - // If this is a K8s initial namespace, refuse to adopt it + // Refuse to adopt namespace if it is one of four initial Kubernetes namespaces. + // https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces + if slices.Contains([]string{"default", "kube-node-lease", "kube-public", "kube-system"}, name) { message.Warnf("Refusing to adopt the initial namespace: %s", name) } else { // This is an existing namespace to adopt - if _, err := c.UpdateNamespace(ctx, namespace); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, namespace, metav1.UpdateOptions{}) + if err != nil { return fmt.Errorf("unable to adopt the existing namespace %s", name) } } diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 56a8566834..f7e776146d 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -6,10 +6,12 @@ package cluster import ( "context" + "time" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -18,7 +20,30 @@ func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { spinner := message.NewProgressSpinner("Deleting the zarf namespace from this cluster") defer spinner.Stop() - return c.DeleteNamespace(ctx, ZarfNamespaceName) + err := c.Clientset.CoreV1().Namespaces().Delete(ctx, ZarfNamespaceName, metav1.DeleteOptions{}) + if kerrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + timer := time.NewTimer(0) + defer timer.Stop() + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := c.Clientset.CoreV1().Namespaces().Get(ctx, ZarfNamespaceName, metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + timer.Reset(1 * time.Second) + } + } } // NewZarfManagedNamespace returns a corev1.Namespace with Zarf-managed labels diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 8bd604250c..a7190c8037 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -11,6 +11,7 @@ import ( "reflect" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" @@ -80,11 +81,12 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(ctx); err != nil { + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all image pull secrets - for _, namespace := range namespaces.Items { + for _, namespace := range namespaceList.Items { currentRegistrySecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfImagePullSecretName) if err != nil { continue @@ -115,11 +117,12 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(ctx); err != nil { + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all git pull secrets - for _, namespace := range namespaces.Items { + for _, namespace := range namespaceList.Items { currentGitSecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfGitServerSecretName) if err != nil { continue diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index fbcccf7989..03ac17e32c 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -87,16 +87,12 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Setup zarf agent PKI state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) - namespaces, err := c.GetNamespaces(ctx) + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { return fmt.Errorf("unable to get the Kubernetes namespaces: %w", err) } // Mark existing namespaces as ignored for the zarf agent to prevent mutating resources we don't own. - for _, namespace := range namespaces.Items { - // Skip Zarf namespace if it already exists. - if namespace.Name == ZarfNamespaceName { - continue - } + for _, namespace := range namespaceList.Items { spinner.Updatef("Marking existing namespace %s as ignored by Zarf Agent", namespace.Name) if namespace.Labels == nil { // Ensure label map exists to avoid nil panic @@ -105,7 +101,8 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // This label will tell the Zarf Agent to ignore this namespace. namespace.Labels[k8s.AgentLabel] = "ignore" namespaceCopy := namespace - if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) + if err != nil { // This is not a hard failure, but we should log it. message.WarnErrf(err, "Unable to mark the namespace %s as ignored by Zarf Agent", namespace.Name) } diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index e37810269c..921268e6a6 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -72,15 +72,17 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { LabelSelector: k8s.ZarfManagedByLabel + "=zarf", } - if namespaces, err := c.GetNamespaces(ctx); err != nil { + namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) + if err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { - for _, namespace := range namespaces.Items { + for _, namespace := range namespaceList.Items { if _, ok := namespace.Labels[k8s.AgentLabel]; ok { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) delete(namespace.Labels, k8s.AgentLabel) namespaceCopy := namespace - if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { + _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) + if err != nil { // This is not a hard failure, but we should log it spinner.Errorf(err, "Unable to update the namespace labels for %s", namespace.Name) } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go deleted file mode 100644 index 2f1d43d019..0000000000 --- a/src/pkg/k8s/namespace.go +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "time" - - "cuelang.org/go/pkg/strings" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetNamespaces returns a list of namespaces in the cluster. -func (k *K8s) GetNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { - metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Namespaces().List(ctx, metaOptions) -} - -// UpdateNamespace updates the given namespace in the cluster. -func (k *K8s) UpdateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { - updateOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().Namespaces().Update(ctx, namespace, updateOptions) -} - -// CreateNamespace creates the given namespace or returns it if it already exists in the cluster. -func (k *K8s) CreateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { - metaOptions := metav1.GetOptions{} - createOptions := metav1.CreateOptions{} - - match, err := k.Clientset.CoreV1().Namespaces().Get(ctx, namespace.Name, metaOptions) - - if err != nil || match.Name != namespace.Name { - return k.Clientset.CoreV1().Namespaces().Create(ctx, namespace, createOptions) - } - - return match, err -} - -// DeleteNamespace deletes the given namespace from the cluster. -func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { - // Attempt to delete the namespace immediately - gracePeriod := int64(0) - err := k.Clientset.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) - if err != nil && !errors.IsNotFound(err) { - return err - } - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: - _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil - } - - timer.Reset(1 * time.Second) - } - } -} - -// IsInitialNamespace returns true if the given namespace name is an initial k8s namespace: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces -func (k *K8s) IsInitialNamespace(name string) bool { - if name == "default" { - return true - } else if strings.HasPrefix(name, "kube-") { - return true - } - - return false -} diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 2805c96620..b7268bb8ca 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -17,7 +17,12 @@ import ( "sync" "time" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/git" @@ -32,7 +37,6 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/packager/filters" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" - corev1 "k8s.io/api/core/v1" ) func (p *Packager) resetRegistryHPA(ctx context.Context) { @@ -451,7 +455,21 @@ func (p *Packager) setupState(ctx context.Context) (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") zarfNamespace := cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) - if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { + err := func() error { + _, err := p.cluster.Clientset.CoreV1().Namespaces().Create(ctx, zarfNamespace, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return err + } + if err == nil { + return nil + } + _, err = p.cluster.Clientset.CoreV1().Namespaces().Update(ctx, zarfNamespace, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }() + if err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } } From 310dcc177818bb1cbc413f34b474613a97e7eb3d Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 3 Jun 2024 21:42:58 +0200 Subject: [PATCH 043/132] fix: cancel Cobra parent context on interrupt (#2567) ## Description Right now different functions handle interrupts in different ways. Some will register their own signal listeners while others will use the global signal handler that exits the program. This means that contexts are never cancelled giving the process time to shut down and clean up. This change adds a signal handler to the Cobra parent context and removes the other signal handlers. Now all commands will use the same signal handler. ## Related Issue Fixes #2505 Relates to #2520 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- main.go | 8 +++++- src/cmd/common/setup.go | 2 -- src/cmd/common/utils.go | 19 ------------- src/cmd/connect.go | 11 ++------ src/cmd/internal.go | 8 +++--- src/cmd/root.go | 15 ++++------- src/cmd/tools/crane.go | 4 --- src/internal/agent/start.go | 54 ++++++++++++++++++++----------------- 8 files changed, 48 insertions(+), 73 deletions(-) diff --git a/main.go b/main.go index b9f58f6e44..0449ccd119 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,10 @@ package main import ( + "context" "embed" + "os/signal" + "syscall" "github.com/defenseunicorns/zarf/src/cmd" "github.com/defenseunicorns/zarf/src/config" @@ -19,7 +22,10 @@ var cosignPublicKey string var zarfSchema embed.FS func main() { + ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer cancel() + config.CosignPublicKey = cosignPublicKey lint.ZarfSchema = zarfSchema - cmd.Execute() + cmd.Execute(ctx) } diff --git a/src/cmd/common/setup.go b/src/cmd/common/setup.go index 72fd0d6722..6fab6e134b 100644 --- a/src/cmd/common/setup.go +++ b/src/cmd/common/setup.go @@ -21,8 +21,6 @@ var LogLevelCLI string // SetupCLI sets up the CLI logging, interrupt functions, and more func SetupCLI() { - ExitOnInterrupt() - match := map[string]message.LogLevel{ "warn": message.WarnLevel, "info": message.InfoLevel, diff --git a/src/cmd/common/utils.go b/src/cmd/common/utils.go index 4fe6fa5043..26f2ce6707 100644 --- a/src/cmd/common/utils.go +++ b/src/cmd/common/utils.go @@ -6,18 +6,11 @@ package common import ( "context" - "os" - "os/signal" - "syscall" - "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" ) -// SuppressGlobalInterrupt suppresses the global error on an interrupt -var SuppressGlobalInterrupt = false - // SetBaseDirectory sets the base directory. This is a directory with a zarf.yaml. func SetBaseDirectory(args []string) string { if len(args) > 0 { @@ -26,18 +19,6 @@ func SetBaseDirectory(args []string) string { return "." } -// ExitOnInterrupt catches an interrupt and exits with fatal error -func ExitOnInterrupt() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - if !SuppressGlobalInterrupt { - message.Fatal(lang.ErrInterrupt, lang.ErrInterrupt.Error()) - } - }() -} - // NewClusterOrDie creates a new Cluster instance and waits for the cluster to be ready or throws a fatal error. func NewClusterOrDie(ctx context.Context) *cluster.Cluster { timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 19422df967..ba6eed3d2d 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -7,8 +7,6 @@ package cmd import ( "fmt" "os" - "os/signal" - "syscall" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config/lang" @@ -72,17 +70,12 @@ var ( } } - // Keep this open until an interrupt signal is received. - interruptChan := make(chan os.Signal, 1) - signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM) - common.SuppressGlobalInterrupt = true - // Wait for the interrupt signal or an error. select { + case <-ctx.Done(): + spinner.Successf(lang.CmdConnectTunnelClosed, url) case err = <-tunnel.ErrChan(): spinner.Fatalf(err, lang.CmdConnectErrService, err.Error()) - case <-interruptChan: - spinner.Successf(lang.CmdConnectTunnelClosed, url) } os.Exit(0) }, diff --git a/src/cmd/internal.go b/src/cmd/internal.go index 97263d0b08..59c1981b0a 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -38,8 +38,8 @@ var agentCmd = &cobra.Command{ Use: "agent", Short: lang.CmdInternalAgentShort, Long: lang.CmdInternalAgentLong, - Run: func(_ *cobra.Command, _ []string) { - agent.StartWebhook() + RunE: func(cmd *cobra.Command, _ []string) error { + return agent.StartWebhook(cmd.Context()) }, } @@ -47,8 +47,8 @@ var httpProxyCmd = &cobra.Command{ Use: "http-proxy", Short: lang.CmdInternalProxyShort, Long: lang.CmdInternalProxyLong, - Run: func(_ *cobra.Command, _ []string) { - agent.StartHTTPProxy() + RunE: func(cmd *cobra.Command, _ []string) error { + return agent.StartHTTPProxy(cmd.Context()) }, } diff --git a/src/cmd/root.go b/src/cmd/root.go index 52e10044c1..447a9d76d2 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -10,6 +10,9 @@ import ( "os" "strings" + "github.com/pterm/pterm" + "github.com/spf13/cobra" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/cmd/tools" "github.com/defenseunicorns/zarf/src/config" @@ -17,8 +20,6 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" - "github.com/pterm/pterm" - "github.com/spf13/cobra" ) var ( @@ -33,16 +34,10 @@ var rootCmd = &cobra.Command{ if common.CheckVendorOnlyFromPath(cmd) { return } - // Don't log the help command if cmd.Parent() == nil { config.SkipLogFile = true } - - // Set the global context for the root command and all child commands - ctx := context.Background() - cmd.SetContext(ctx) - common.SetupCLI() }, Short: lang.RootCmdShort, @@ -67,8 +62,8 @@ var rootCmd = &cobra.Command{ } // Execute is the entrypoint for the CLI. -func Execute() { - cmd, err := rootCmd.ExecuteC() +func Execute(ctx context.Context) { + cmd, err := rootCmd.ExecuteContextC(ctx) if err == nil { return } diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index 3b750964d3..a89041bb26 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/AlecAivazis/survey/v2" - "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/images" @@ -39,9 +38,6 @@ func init() { Aliases: []string{"r", "crane"}, Short: lang.CmdToolsRegistryShort, PersistentPreRun: func(cmd *cobra.Command, _ []string) { - - common.ExitOnInterrupt() - // The crane options loading here comes from the rootCmd of crane craneOptions = append(craneOptions, crane.WithContext(cmd.Context())) // TODO(jonjohnsonjr): crane.Verbose option? diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go index 35b57e5fe2..12ee3bcc8b 100644 --- a/src/internal/agent/start.go +++ b/src/internal/agent/start.go @@ -6,10 +6,11 @@ package agent import ( "context" + "errors" "net/http" - "os" - "os/signal" - "syscall" + "time" + + "golang.org/x/sync/errgroup" "github.com/defenseunicorns/zarf/src/config/lang" agentHttp "github.com/defenseunicorns/zarf/src/internal/agent/http" @@ -27,35 +28,40 @@ const ( ) // StartWebhook launches the Zarf agent mutating webhook in the cluster. -func StartWebhook() { +func StartWebhook(ctx context.Context) error { message.Debug("agent.StartWebhook()") - - startServer(agentHttp.NewAdmissionServer(httpPort)) + return startServer(ctx, agentHttp.NewAdmissionServer(httpPort)) } // StartHTTPProxy launches the zarf agent proxy in the cluster. -func StartHTTPProxy() { +func StartHTTPProxy(ctx context.Context) error { message.Debug("agent.StartHttpProxy()") - - startServer(agentHttp.NewProxyServer(httpPort)) + return startServer(ctx, agentHttp.NewProxyServer(httpPort)) } -func startServer(server *http.Server) { - go func() { - if err := server.ListenAndServeTLS(tlsCert, tlsKey); err != nil && err != http.ErrServerClosed { - message.Fatal(err, lang.AgentErrStart) +func startServer(ctx context.Context, srv *http.Server) error { + g, gCtx := errgroup.WithContext(ctx) + g.Go(func() error { + err := srv.ListenAndServeTLS(tlsCert, tlsKey) + if err != nil && !errors.Is(err, http.ErrServerClosed) { + return err } - }() - + return nil + }) + g.Go(func() error { + <-gCtx.Done() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + err := srv.Shutdown(ctx) + if err != nil { + return err + } + return nil + }) message.Infof(lang.AgentInfoPort, httpPort) - - // listen shutdown signal - signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) - <-signalChan - - message.Infof(lang.AgentInfoShutdown) - if err := server.Shutdown(context.Background()); err != nil { - message.Fatal(err, lang.AgentErrShutdown) + err := g.Wait() + if err != nil { + return err } + return nil } From e6ac3d5fc65797d1d2872ece3fde69968553b294 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 3 Jun 2024 16:13:03 -0400 Subject: [PATCH 044/132] refactor: use root ctx in agent (#2578) ## Description Updates the agent to use ctx from the root ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/agent/http/server.go | 4 +--- src/internal/agent/start.go | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 49fa5a6e7e..2bf93f4d7f 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -18,7 +18,7 @@ import ( ) // NewAdmissionServer creates a http.Server for the mutating webhook admission handler. -func NewAdmissionServer(port string) *http.Server { +func NewAdmissionServer(ctx context.Context, port string) *http.Server { message.Debugf("http.NewAdmissionServer(%s)", port) c, err := cluster.NewCluster() @@ -26,8 +26,6 @@ func NewAdmissionServer(port string) *http.Server { message.Fatalf(err, err.Error()) } - ctx := context.Background() - // Instances hooks podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go index 12ee3bcc8b..83172d2e95 100644 --- a/src/internal/agent/start.go +++ b/src/internal/agent/start.go @@ -30,7 +30,7 @@ const ( // StartWebhook launches the Zarf agent mutating webhook in the cluster. func StartWebhook(ctx context.Context) error { message.Debug("agent.StartWebhook()") - return startServer(ctx, agentHttp.NewAdmissionServer(httpPort)) + return startServer(ctx, agentHttp.NewAdmissionServer(ctx, httpPort)) } // StartHTTPProxy launches the zarf agent proxy in the cluster. From d832c60f11013a8e26a511d825770e18802ebc36 Mon Sep 17 00:00:00 2001 From: schristoff <167717759+schristoff-du@users.noreply.github.com> Date: Wed, 5 Jun 2024 05:36:55 -0600 Subject: [PATCH 045/132] chore: deprecate DeprecatedKeys (#2581) Fixes #2267 Signed-off-by: schristoff <28318173+schristoff@users.noreply.github.com> Co-authored-by: schristoff <28318173+schristoff@users.noreply.github.com> --- src/internal/packager/template/template.go | 15 ++------------- src/pkg/variables/common.go | 4 +--- src/pkg/variables/templates.go | 6 ------ 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/src/internal/packager/template/template.go b/src/internal/packager/template/template.go index f823c567f8..20f0d42af4 100644 --- a/src/internal/packager/template/template.go +++ b/src/internal/packager/template/template.go @@ -21,8 +21,7 @@ import ( ) const ( - depMarkerOld = "DATA_INJECTON_MARKER" - depMarkerNew = "DATA_INJECTION_MARKER" + depMarker = "DATA_INJECTION_MARKER" ) // GetZarfVariableConfig gets a variable configuration specific to Zarf @@ -36,7 +35,6 @@ func GetZarfVariableConfig() *variables.VariableConfig { return variables.New( "zarf", - deprecatedKeys(), prompt, slog.New(message.ZarfHandler{})) } @@ -65,9 +63,7 @@ func GetZarfTemplates(componentName string, state *types.ZarfState) (templateMap "GIT_AUTH_PULL": gitInfo.PullPassword, } - // Preserve existing misspelling for backwards compatibility - builtinMap[depMarkerOld] = config.GetDataInjectionMarker() - builtinMap[depMarkerNew] = config.GetDataInjectionMarker() + builtinMap[depMarker] = config.GetDataInjectionMarker() // Don't template component-specific variables for every component switch componentName { @@ -111,13 +107,6 @@ func GetZarfTemplates(componentName string, state *types.ZarfState) (templateMap return templateMap, nil } -// deprecatedKeys returns a map of template keys that are deprecated -func deprecatedKeys() map[string]string { - return map[string]string{ - fmt.Sprintf("###ZARF_%s###", depMarkerOld): fmt.Sprintf("###ZARF_%s###", depMarkerNew), - } -} - // generateHtpasswd returns an htpasswd string for the current state's RegistryInfo. func generateHtpasswd(regInfo *types.RegistryInfo) (string, error) { // Only calculate this for internal registries to allow longer external passwords diff --git a/src/pkg/variables/common.go b/src/pkg/variables/common.go index 6cf73824d1..ae2bb5fac5 100644 --- a/src/pkg/variables/common.go +++ b/src/pkg/variables/common.go @@ -11,7 +11,6 @@ import ( // VariableConfig represents a value to be templated into a text file. type VariableConfig struct { templatePrefix string - deprecatedKeys map[string]string applicationTemplates map[string]*TextTemplate setVariableMap SetVariableMap @@ -22,10 +21,9 @@ type VariableConfig struct { } // New creates a new VariableConfig -func New(templatePrefix string, deprecatedKeys map[string]string, prompt func(variable InteractiveVariable) (value string, err error), logger *slog.Logger) *VariableConfig { +func New(templatePrefix string, prompt func(variable InteractiveVariable) (value string, err error), logger *slog.Logger) *VariableConfig { return &VariableConfig{ templatePrefix: templatePrefix, - deprecatedKeys: deprecatedKeys, applicationTemplates: make(map[string]*TextTemplate), setVariableMap: make(SetVariableMap), prompt: prompt, diff --git a/src/pkg/variables/templates.go b/src/pkg/variables/templates.go index 356345af38..9363357d39 100644 --- a/src/pkg/variables/templates.go +++ b/src/pkg/variables/templates.go @@ -89,12 +89,6 @@ func (vc *VariableConfig) ReplaceTextTemplate(path string) error { preTemplate := matches[regexTemplateLine.SubexpIndex("preTemplate")] templateKey := matches[regexTemplateLine.SubexpIndex("template")] - _, present := vc.deprecatedKeys[templateKey] - if present { - deprecationWarning := fmt.Sprintf("This Zarf Package uses a deprecated variable: '%s' changed to '%s'. Please notify your package creator for an update.", templateKey, vc.deprecatedKeys[templateKey]) - vc.logger.Warn(deprecationWarning) - } - template := templateMap[templateKey] // Check if the template is nil (present), use the original templateKey if not (so that it is not replaced). From c1d1d44793ea07b1b77c3dcf72059d954480fa65 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:21:37 -0400 Subject: [PATCH 046/132] test: validate package (#2569) ## Description This PR adds tests to validate package ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/config/lang/english.go | 12 +- src/pkg/message/message.go | 10 +- src/pkg/packager/composer/list.go | 2 +- src/pkg/packager/sources/new_test.go | 3 +- src/types/component.go | 2 +- src/types/package.go | 2 +- src/types/validate.go | 163 ++++----- src/types/validate_test.go | 513 +++++++++++++++++++++++++++ zarf.schema.json | 4 +- 9 files changed, 611 insertions(+), 100 deletions(-) create mode 100644 src/types/validate_test.go diff --git a/src/config/lang/english.go b/src/config/lang/english.go index adae1f1a41..36112f99a9 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -695,7 +695,7 @@ const ( PkgValidateErrActionClusterNetwork = "a single wait action must contain only one of cluster or network" PkgValidateErrChart = "invalid chart definition: %w" PkgValidateErrChartName = "chart %q exceed the maximum length of %d characters" - PkgValidateErrChartNameMissing = "chart %q must include a name" + PkgValidateErrChartNameMissing = "chart must include a name" PkgValidateErrChartNameNotUnique = "chart name %q is not unique" PkgValidateErrChartNamespaceMissing = "chart %q must include a namespace" PkgValidateErrChartURLOrPath = "chart %q must have either a url or localPath" @@ -715,17 +715,17 @@ const ( PkgValidateErrManifest = "invalid manifest definition: %w" PkgValidateErrManifestFileOrKustomize = "manifest %q must have at least one file or kustomization" PkgValidateErrManifestNameLength = "manifest %q exceed the maximum length of %d characters" - PkgValidateErrManifestNameMissing = "manifest %q must include a name" + PkgValidateErrManifestNameMissing = "manifest must include a name" PkgValidateErrManifestNameNotUnique = "manifest name %q is not unique" PkgValidateErrName = "invalid package name: %w" PkgValidateErrPkgConstantName = "constant name %q must be all uppercase and contain no special characters except _" PkgValidateErrPkgConstantPattern = "provided value for constant %q does not match pattern %q" PkgValidateErrPkgName = "package name %q must be all lowercase and contain no special characters except '-' and cannot start with a '-'" PkgValidateErrVariable = "invalid package variable: %w" - PkgValidateErrYOLONoArch = "cluster architecture not allowed" - PkgValidateErrYOLONoDistro = "cluster distros not allowed" - PkgValidateErrYOLONoGit = "git repos not allowed" - PkgValidateErrYOLONoOCI = "OCI images not allowed" + PkgValidateErrYOLONoArch = "cluster architecture not allowed in YOLO" + PkgValidateErrYOLONoDistro = "cluster distros not allowed in YOLO" + PkgValidateErrYOLONoGit = "git repos not allowed in YOLO" + PkgValidateErrYOLONoOCI = "OCI images not allowed in YOLO" ) // Collection of reusable error messages. diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index c3eab711bf..9cafa0f1c1 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -273,7 +273,15 @@ func Paragraph(format string, a ...any) string { // Paragraphn formats text into an n column paragraph func Paragraphn(n int, format string, a ...any) string { - return pterm.DefaultParagraph.WithMaxWidth(n).Sprintf(format, a...) + // Split the text to keep pterm formatting but add newlines + lines := strings.Split(fmt.Sprintf(format, a...), "\n") + + formattedLines := make([]string, len(lines)) + for i, line := range lines { + formattedLines[i] = pterm.DefaultParagraph.WithMaxWidth(n).Sprintf(line) + } + + return strings.Join(formattedLines, "\n") } // PrintDiff prints the differences between a and b with a as original and b as new diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go index 4f1a484ca3..c88a0bb019 100644 --- a/src/pkg/packager/composer/list.go +++ b/src/pkg/packager/composer/list.go @@ -142,7 +142,7 @@ func NewImportChain(head types.ZarfComponent, index int, originalPackageName, ar } // TODO: stuff like this should also happen in linting - if err := node.ZarfComponent.ValidateImportDefinition(); err != nil { + if err := node.ZarfComponent.Validate(); err != nil { return ic, err } diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index 91ea3e2c2f..ee770f51aa 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -150,8 +150,7 @@ func TestPackageSource(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - t.Parallel() - + // TODO once our messaging is thread safe, re-parallelize this test opts := &types.ZarfPackageOptions{ PackageSource: tt.src, Shasum: tt.shasum, diff --git a/src/types/component.go b/src/types/component.go index 3c4ffa21c4..a9997ac504 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -13,7 +13,7 @@ import ( // ZarfComponent is the primary functional grouping of assets to deploy by Zarf. type ZarfComponent struct { // Name is the unique identifier for this component - Name string `json:"name" jsonschema:"description=The name of the component,pattern=^[a-z0-9\\-]*[a-z0-9]$"` + Name string `json:"name" jsonschema:"description=The name of the component,pattern=^[a-z0-9][a-z0-9\\-]*$"` // Description is a message given to a user when deciding to enable this component or not Description string `json:"description,omitempty" jsonschema:"description=Message to include during package deploy describing the purpose of this component"` diff --git a/src/types/package.go b/src/types/package.go index 4f3f222fb0..99ac9ef008 100644 --- a/src/types/package.go +++ b/src/types/package.go @@ -43,7 +43,7 @@ func (pkg ZarfPackage) IsSBOMAble() bool { // ZarfMetadata lists information about the current ZarfPackage. type ZarfMetadata struct { - Name string `json:"name" jsonschema:"description=Name to identify this Zarf package,pattern=^[a-z0-9\\-]*[a-z0-9]$"` + Name string `json:"name" jsonschema:"description=Name to identify this Zarf package,pattern=^[a-z0-9][a-z0-9\\-]*$"` Description string `json:"description,omitempty" jsonschema:"description=Additional information about this package"` Version string `json:"version,omitempty" jsonschema:"description=Generic string set by a package author to track the package version (Note: ZarfInitConfigs will always be versioned to the CLIVersion they were created with)"` URL string `json:"url,omitempty" jsonschema:"description=Link to package information when online"` diff --git a/src/types/validate.go b/src/types/validate.go index fbef1dab65..fa5cfcbd87 100644 --- a/src/types/validate.go +++ b/src/types/validate.go @@ -40,27 +40,28 @@ func SupportedOS() []string { // Validate runs all validation checks on the package. func (pkg ZarfPackage) Validate() error { + var err error if pkg.Kind == ZarfInitConfig && pkg.Metadata.YOLO { - return fmt.Errorf(lang.PkgValidateErrInitNoYOLO) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrInitNoYOLO)) } if !IsLowercaseNumberHyphenNoStartHyphen(pkg.Metadata.Name) { - return fmt.Errorf(lang.PkgValidateErrPkgName, pkg.Metadata.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrPkgName, pkg.Metadata.Name)) } if len(pkg.Components) == 0 { - return errors.New("package must have at least 1 component") + err = errors.Join(err, fmt.Errorf("package must have at least 1 component")) } for _, variable := range pkg.Variables { - if err := variable.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrVariable, err) + if varErr := variable.Validate(); varErr != nil { + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrVariable, varErr)) } } for _, constant := range pkg.Constants { - if err := constant.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrConstant, err) + if varErr := constant.Validate(); varErr != nil { + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrConstant, varErr)) } } @@ -71,19 +72,19 @@ func (pkg ZarfPackage) Validate() error { if pkg.Metadata.YOLO { for _, component := range pkg.Components { if len(component.Images) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoOCI) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrYOLONoOCI)) } if len(component.Repos) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoGit) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrYOLONoGit)) } if component.Only.Cluster.Architecture != "" { - return fmt.Errorf(lang.PkgValidateErrYOLONoArch) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrYOLONoArch)) } if len(component.Only.Cluster.Distros) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoDistro) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrYOLONoDistro)) } } } @@ -91,24 +92,24 @@ func (pkg ZarfPackage) Validate() error { for _, component := range pkg.Components { // ensure component name is unique if _, ok := uniqueComponentNames[component.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrComponentNameNotUnique, component.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrComponentNameNotUnique, component.Name)) } uniqueComponentNames[component.Name] = true if !IsLowercaseNumberHyphenNoStartHyphen(component.Name) { - return fmt.Errorf(lang.PkgValidateErrComponentName, component.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrComponentName, component.Name)) } if !slices.Contains(supportedOS, component.Only.LocalOS) { - return fmt.Errorf(lang.PkgValidateErrComponentLocalOS, component.Name, component.Only.LocalOS, supportedOS) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrComponentLocalOS, component.Name, component.Only.LocalOS, supportedOS)) } if component.IsRequired() { if component.Default { - return fmt.Errorf(lang.PkgValidateErrComponentReqDefault, component.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrComponentReqDefault, component.Name)) } if component.DeprecatedGroup != "" { - return fmt.Errorf(lang.PkgValidateErrComponentReqGrouped, component.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrComponentReqGrouped, component.Name)) } } @@ -116,12 +117,12 @@ func (pkg ZarfPackage) Validate() error { for _, chart := range component.Charts { // ensure chart name is unique if _, ok := uniqueChartNames[chart.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrChartNameNotUnique, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartNameNotUnique, chart.Name)) } uniqueChartNames[chart.Name] = true - if err := chart.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrChart, err) + if chartErr := chart.Validate(); chartErr != nil { + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChart, chartErr)) } } @@ -129,24 +130,24 @@ func (pkg ZarfPackage) Validate() error { for _, manifest := range component.Manifests { // ensure manifest name is unique if _, ok := uniqueManifestNames[manifest.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrManifestNameNotUnique, manifest.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrManifestNameNotUnique, manifest.Name)) } uniqueManifestNames[manifest.Name] = true - if err := manifest.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrManifest, err) + if manifestErr := manifest.Validate(); manifestErr != nil { + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrManifest, manifestErr)) } } - if err := component.Actions.Validate(); err != nil { - return fmt.Errorf("%q: %w", component.Name, err) + if actionsErr := component.Actions.validate(); actionsErr != nil { + err = errors.Join(err, fmt.Errorf("%q: %w", component.Name, actionsErr)) } // ensure groups don't have multiple defaults or only one component if component.DeprecatedGroup != "" { if component.Default { if _, ok := groupDefault[component.DeprecatedGroup]; ok { - return fmt.Errorf(lang.PkgValidateErrGroupMultipleDefaults, component.DeprecatedGroup, groupDefault[component.DeprecatedGroup], component.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrGroupMultipleDefaults, component.DeprecatedGroup, groupDefault[component.DeprecatedGroup], component.Name)) } groupDefault[component.DeprecatedGroup] = component.Name } @@ -156,54 +157,54 @@ func (pkg ZarfPackage) Validate() error { for groupKey, componentNames := range groupedComponents { if len(componentNames) == 1 { - return fmt.Errorf(lang.PkgValidateErrGroupOneComponent, groupKey, componentNames[0]) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrGroupOneComponent, groupKey, componentNames[0])) } } - return nil + return err } -// Validate runs all validation checks on component actions. -func (a ZarfComponentActions) Validate() error { - if err := a.OnCreate.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrAction, err) - } +func (a ZarfComponentActions) validate() error { + var err error + + err = errors.Join(err, a.OnCreate.Validate()) if a.OnCreate.HasSetVariables() { - return fmt.Errorf("cannot contain setVariables outside of onDeploy in actions") + err = errors.Join(err, fmt.Errorf("cannot contain setVariables outside of onDeploy in actions")) } - if err := a.OnDeploy.Validate(); err != nil { - return fmt.Errorf(lang.PkgValidateErrAction, err) - } + err = errors.Join(err, a.OnDeploy.Validate()) if a.OnRemove.HasSetVariables() { - return fmt.Errorf("cannot contain setVariables outside of onDeploy in actions") + err = errors.Join(err, fmt.Errorf("cannot contain setVariables outside of onDeploy in actions")) } - return nil + err = errors.Join(err, a.OnRemove.Validate()) + + return err } -// ValidateImportDefinition validates the component trying to be imported. -func (c ZarfComponent) ValidateImportDefinition() error { +// Validate validates the component trying to be imported. +func (c ZarfComponent) Validate() error { + var err error path := c.Import.Path url := c.Import.URL // ensure path or url is provided if path == "" && url == "" { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "neither a path nor a URL was provided") + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "neither a path nor a URL was provided")) } // ensure path and url are not both provided if path != "" && url != "" { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "both a path and a URL were provided") + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "both a path and a URL were provided")) } // validation for path if url == "" && path != "" { // ensure path is not an absolute path if filepath.IsAbs(path) { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "path cannot be an absolute path") + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "path cannot be an absolute path")) } } @@ -211,11 +212,11 @@ func (c ZarfComponent) ValidateImportDefinition() error { if url != "" && path == "" { ok := helpers.IsOCIURL(url) if !ok { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "URL is not a valid OCI URL") + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "URL is not a valid OCI URL")) } } - return nil + return err } // HasSetVariables returns true if any of the actions contain setVariables. @@ -234,107 +235,97 @@ func (as ZarfComponentActionSet) HasSetVariables() bool { // Validate runs all validation checks on component action sets. func (as ZarfComponentActionSet) Validate() error { - validate := func(actions []ZarfComponentAction) error { + var err error + validate := func(actions []ZarfComponentAction) { for _, action := range actions { - if err := action.Validate(); err != nil { - return err + if actionErr := action.Validate(); actionErr != nil { + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrAction, actionErr)) } + } - return nil } - if err := validate(as.Before); err != nil { - return err - } - if err := validate(as.After); err != nil { - return err - } - if err := validate(as.OnSuccess); err != nil { - return err - } - return validate(as.OnFailure) + validate(as.Before) + validate(as.After) + validate(as.OnFailure) + validate(as.OnSuccess) + return err } // Validate runs all validation checks on an action. func (action ZarfComponentAction) Validate() error { - // Validate SetVariable + var err error for _, variable := range action.SetVariables { - if err := variable.Validate(); err != nil { - return err - } + err = errors.Join(err, variable.Validate()) } if action.Wait != nil { // Validate only cmd or wait, not both if action.Cmd != "" { - return fmt.Errorf(lang.PkgValidateErrActionCmdWait, action.Cmd) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrActionCmdWait, action.Cmd)) } // Validate only cluster or network, not both if action.Wait.Cluster != nil && action.Wait.Network != nil { - return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrActionClusterNetwork)) } // Validate at least one of cluster or network if action.Wait.Cluster == nil && action.Wait.Network == nil { - return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrActionClusterNetwork)) } } - return nil + return err } // Validate runs all validation checks on a chart. func (chart ZarfChart) Validate() error { - // Don't allow empty names + var err error + if chart.Name == "" { - return fmt.Errorf(lang.PkgValidateErrChartNameMissing, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartNameMissing)) } - // Helm max release name if len(chart.Name) > ZarfMaxChartNameLength { - return fmt.Errorf(lang.PkgValidateErrChartName, chart.Name, ZarfMaxChartNameLength) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartName, chart.Name, ZarfMaxChartNameLength)) } - // Must have a namespace if chart.Namespace == "" { - return fmt.Errorf(lang.PkgValidateErrChartNamespaceMissing, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartNamespaceMissing, chart.Name)) } // Must have a url or localPath (and not both) if chart.URL != "" && chart.LocalPath != "" { - return fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name)) } - // Must have a url or localPath (and not both) if chart.URL == "" && chart.LocalPath == "" { - return fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name)) } - // Must have a version if chart.Version == "" { - return fmt.Errorf(lang.PkgValidateErrChartVersion, chart.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrChartVersion, chart.Name)) } - return nil + return err } // Validate runs all validation checks on a manifest. func (manifest ZarfManifest) Validate() error { - // Don't allow empty names + var err error + if manifest.Name == "" { - return fmt.Errorf(lang.PkgValidateErrManifestNameMissing, manifest.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrManifestNameMissing)) } - // Helm max release name if len(manifest.Name) > ZarfMaxChartNameLength { - return fmt.Errorf(lang.PkgValidateErrManifestNameLength, manifest.Name, ZarfMaxChartNameLength) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrManifestNameLength, manifest.Name, ZarfMaxChartNameLength)) } - // Require files in manifest if len(manifest.Files) < 1 && len(manifest.Kustomizations) < 1 { - return fmt.Errorf(lang.PkgValidateErrManifestFileOrKustomize, manifest.Name) + err = errors.Join(err, fmt.Errorf(lang.PkgValidateErrManifestFileOrKustomize, manifest.Name)) } - return nil + return err } diff --git a/src/types/validate_test.go b/src/types/validate_test.go new file mode 100644 index 0000000000..5fcce6821b --- /dev/null +++ b/src/types/validate_test.go @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package types contains all the types used by Zarf. +package types + +import ( + "fmt" + "path/filepath" + "strings" + "testing" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/variables" + "github.com/stretchr/testify/require" +) + +func TestZarfPackageValidate(t *testing.T) { + t.Parallel() + tests := []struct { + name string + pkg ZarfPackage + expectedErrs []string + }{ + { + name: "valid package", + pkg: ZarfPackage{ + Kind: ZarfPackageConfig, + Metadata: ZarfMetadata{ + Name: "valid-package", + }, + Components: []ZarfComponent{ + { + Name: "component1", + }, + }, + }, + expectedErrs: nil, + }, + { + name: "no components", + pkg: ZarfPackage{ + Kind: ZarfPackageConfig, + Metadata: ZarfMetadata{ + Name: "empty-components", + }, + Components: []ZarfComponent{}, + }, + expectedErrs: []string{"package must have at least 1 component"}, + }, + { + name: "invalid package", + pkg: ZarfPackage{ + Kind: ZarfPackageConfig, + Metadata: ZarfMetadata{ + Name: "-invalid-package", + }, + Components: []ZarfComponent{ + { + Name: "-invalid", + Only: ZarfComponentOnlyTarget{ + LocalOS: "unsupportedOS", + }, + Required: helpers.BoolPtr(true), + Default: true, + Charts: []ZarfChart{ + {Name: "chart1", Namespace: "whatever", URL: "http://whatever", Version: "v1.0.0"}, + {Name: "chart1", Namespace: "whatever", URL: "http://whatever", Version: "v1.0.0"}, + }, + Manifests: []ZarfManifest{ + {Name: "manifest1", Files: []string{"file1"}}, + {Name: "manifest1", Files: []string{"file2"}}, + }, + }, + { + Name: "required-in-group", + Required: helpers.BoolPtr(true), + DeprecatedGroup: "a-group", + }, + { + Name: "multi-default", + Default: true, + DeprecatedGroup: "multi-default", + }, + { + Name: "multi-default-2", + Default: true, + DeprecatedGroup: "multi-default", + }, + { + Name: "duplicate", + }, + { + Name: "duplicate", + }, + }, + Variables: []variables.InteractiveVariable{ + { + Variable: variables.Variable{Name: "not_uppercase"}, + }, + }, + Constants: []variables.Constant{ + { + Name: "not_uppercase", + }, + { + Name: "BAD", + Pattern: "^good_val$", + Value: "bad_val", + }, + }, + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrPkgName, "-invalid-package"), + fmt.Errorf(lang.PkgValidateErrVariable, fmt.Errorf(lang.PkgValidateMustBeUppercase, "not_uppercase")).Error(), + fmt.Errorf(lang.PkgValidateErrConstant, fmt.Errorf(lang.PkgValidateErrPkgConstantName, "not_uppercase")).Error(), + fmt.Errorf(lang.PkgValidateErrConstant, fmt.Errorf(lang.PkgValidateErrPkgConstantPattern, "BAD", "^good_val$")).Error(), + fmt.Sprintf(lang.PkgValidateErrComponentName, "-invalid"), + fmt.Sprintf(lang.PkgValidateErrComponentLocalOS, "-invalid", "unsupportedOS", supportedOS), + fmt.Sprintf(lang.PkgValidateErrComponentReqDefault, "-invalid"), + fmt.Sprintf(lang.PkgValidateErrChartNameNotUnique, "chart1"), + fmt.Sprintf(lang.PkgValidateErrManifestNameNotUnique, "manifest1"), + fmt.Sprintf(lang.PkgValidateErrComponentReqGrouped, "required-in-group"), + fmt.Sprintf(lang.PkgValidateErrComponentNameNotUnique, "duplicate"), + fmt.Sprintf(lang.PkgValidateErrGroupOneComponent, "a-group", "required-in-group"), + fmt.Sprintf(lang.PkgValidateErrGroupMultipleDefaults, "multi-default", "multi-default", "multi-default-2"), + }, + }, + { + name: "invalid yolo", + pkg: ZarfPackage{ + Kind: ZarfInitConfig, + Metadata: ZarfMetadata{ + Name: "invalid-yolo", + YOLO: true, + }, + Components: []ZarfComponent{ + { + Name: "yolo", + Images: []string{"an-image"}, + Repos: []string{"a-repo"}, + Only: ZarfComponentOnlyTarget{ + Cluster: ZarfComponentOnlyCluster{ + Architecture: "not-empty", + Distros: []string{"not-empty"}, + }, + }, + }, + }, + }, + expectedErrs: []string{ + lang.PkgValidateErrInitNoYOLO, + lang.PkgValidateErrYOLONoOCI, + lang.PkgValidateErrYOLONoGit, + lang.PkgValidateErrYOLONoArch, + lang.PkgValidateErrYOLONoDistro, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.pkg.Validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, errs, tt.expectedErrs) + }) + } +} + +func TestValidateManifest(t *testing.T) { + t.Parallel() + longName := strings.Repeat("a", ZarfMaxChartNameLength+1) + tests := []struct { + manifest ZarfManifest + expectedErrs []string + name string + }{ + { + name: "valid", + manifest: ZarfManifest{Name: "valid", Files: []string{"a-file"}}, + expectedErrs: nil, + }, + { + name: "empty name", + manifest: ZarfManifest{Name: "", Files: []string{"a-file"}}, + expectedErrs: []string{lang.PkgValidateErrManifestNameMissing}, + }, + { + name: "long name", + manifest: ZarfManifest{Name: longName, Files: []string{"a-file"}}, + expectedErrs: []string{fmt.Sprintf(lang.PkgValidateErrManifestNameLength, longName, ZarfMaxChartNameLength)}, + }, + { + name: "no files or kustomize", + manifest: ZarfManifest{Name: "nothing-there"}, + expectedErrs: []string{fmt.Sprintf(lang.PkgValidateErrManifestFileOrKustomize, "nothing-there")}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.manifest.Validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, errs, tt.expectedErrs) + }) + } +} + +func TestValidateChart(t *testing.T) { + t.Parallel() + longName := strings.Repeat("a", ZarfMaxChartNameLength+1) + tests := []struct { + chart ZarfChart + expectedErrs []string + name string + }{ + { + name: "valid", + chart: ZarfChart{Name: "chart1", Namespace: "whatever", URL: "http://whatever", Version: "v1.0.0"}, + expectedErrs: nil, + }, + { + name: "empty name", + chart: ZarfChart{Name: "", Namespace: "whatever", URL: "http://whatever", Version: "v1.0.0"}, + expectedErrs: []string{lang.PkgValidateErrChartNameMissing}, + }, + { + name: "long name", + chart: ZarfChart{Name: longName, Namespace: "whatever", URL: "http://whatever", Version: "v1.0.0"}, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrChartName, longName, ZarfMaxChartNameLength), + }, + }, + { + name: "no url or local path", + chart: ZarfChart{Name: "invalid"}, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrChartNamespaceMissing, "invalid"), + fmt.Sprintf(lang.PkgValidateErrChartURLOrPath, "invalid"), + fmt.Sprintf(lang.PkgValidateErrChartVersion, "invalid"), + }, + }, + { + name: "both url and local path", + chart: ZarfChart{Name: "invalid", Namespace: "whatever", URL: "http://whatever", LocalPath: "wherever", Version: "v1.0.0"}, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrChartURLOrPath, "invalid"), + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.chart.Validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, tt.expectedErrs, errs) + }) + } +} + +func TestValidateComponentActions(t *testing.T) { + t.Parallel() + tests := []struct { + name string + actions ZarfComponentActions + expectedErrs []string + }{ + { + name: "valid actions", + actions: ZarfComponentActions{ + OnCreate: ZarfComponentActionSet{ + Before: []ZarfComponentAction{ + { + Cmd: "echo 'onCreate before valid'", + }, + }, + }, + OnDeploy: ZarfComponentActionSet{ + Before: []ZarfComponentAction{ + { + Cmd: "echo 'onDeploy before valid'", + }, + }, + }, + }, + expectedErrs: nil, + }, + { + name: "setVariables in onCreate", + actions: ZarfComponentActions{ + OnCreate: ZarfComponentActionSet{ + Before: []ZarfComponentAction{ + { + Cmd: "echo 'invalid setVariable'", + SetVariables: []variables.Variable{{Name: "VAR"}}, + }, + }, + }, + }, + expectedErrs: []string{"cannot contain setVariables outside of onDeploy in actions"}, + }, + { + name: "invalid onCreate action", + actions: ZarfComponentActions{ + OnCreate: ZarfComponentActionSet{ + Before: []ZarfComponentAction{ + { + Cmd: "create", + Wait: &ZarfComponentActionWait{Cluster: &ZarfComponentActionWaitCluster{}}, + }, + }, + }, + OnDeploy: ZarfComponentActionSet{ + After: []ZarfComponentAction{ + { + Cmd: "deploy", + Wait: &ZarfComponentActionWait{Cluster: &ZarfComponentActionWaitCluster{}}, + }, + }, + }, + OnRemove: ZarfComponentActionSet{ + OnSuccess: []ZarfComponentAction{ + { + Cmd: "remove", + Wait: &ZarfComponentActionWait{Cluster: &ZarfComponentActionWaitCluster{}}, + }, + }, + OnFailure: []ZarfComponentAction{ + { + Cmd: "remove2", + Wait: &ZarfComponentActionWait{Cluster: &ZarfComponentActionWaitCluster{}}, + }, + }, + }, + }, + expectedErrs: []string{ + fmt.Errorf(lang.PkgValidateErrAction, fmt.Errorf(lang.PkgValidateErrActionCmdWait, "create")).Error(), + fmt.Errorf(lang.PkgValidateErrAction, fmt.Errorf(lang.PkgValidateErrActionCmdWait, "deploy")).Error(), + fmt.Errorf(lang.PkgValidateErrAction, fmt.Errorf(lang.PkgValidateErrActionCmdWait, "remove")).Error(), + fmt.Errorf(lang.PkgValidateErrAction, fmt.Errorf(lang.PkgValidateErrActionCmdWait, "remove2")).Error(), + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.actions.validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, tt.expectedErrs, errs) + }) + } +} + +func TestValidateComponentAction(t *testing.T) { + t.Parallel() + tests := []struct { + name string + action ZarfComponentAction + expectedErrs []string + }{ + { + name: "valid action no conditions", + action: ZarfComponentAction{}, + }, + { + name: "cmd and wait both set, nothing in wait", + action: ZarfComponentAction{ + Cmd: "ls", + Wait: &ZarfComponentActionWait{}, + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrActionCmdWait, "ls"), + lang.PkgValidateErrActionClusterNetwork, + }, + }, + { + name: "cluster and network both set", + action: ZarfComponentAction{ + Wait: &ZarfComponentActionWait{Cluster: &ZarfComponentActionWaitCluster{}, Network: &ZarfComponentActionWaitNetwork{}}, + }, + expectedErrs: []string{fmt.Sprintf(lang.PkgValidateErrActionClusterNetwork)}, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.action.Validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, tt.expectedErrs, errs) + }) + } +} + +func TestValidateZarfComponent(t *testing.T) { + t.Parallel() + absPath, err := filepath.Abs("abs") + require.NoError(t, err) + tests := []struct { + component ZarfComponent + expectedErrs []string + name string + }{ + { + name: "valid path", + component: ZarfComponent{ + Name: "component1", + Import: ZarfComponentImport{ + Path: "relative/path", + }, + }, + expectedErrs: nil, + }, + { + name: "valid URL", + component: ZarfComponent{ + Name: "component2", + Import: ZarfComponentImport{ + URL: "oci://example.com/package:v0.0.1", + }, + }, + expectedErrs: nil, + }, + { + name: "neither path nor URL provided", + component: ZarfComponent{ + Name: "neither", + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrImportDefinition, "neither", "neither a path nor a URL was provided"), + }, + }, + { + name: "both path and URL provided", + component: ZarfComponent{ + Name: "both", + Import: ZarfComponentImport{ + Path: "relative/path", + URL: "https://example.com", + }, + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrImportDefinition, "both", "both a path and a URL were provided"), + }, + }, + { + name: "absolute path provided", + component: ZarfComponent{ + Name: "abs-path", + Import: ZarfComponentImport{ + Path: absPath, + }, + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrImportDefinition, "abs-path", "path cannot be an absolute path"), + }, + }, + { + name: "invalid URL provided", + component: ZarfComponent{ + Name: "bad-url", + Import: ZarfComponentImport{ + URL: "https://example.com", + }, + }, + expectedErrs: []string{ + fmt.Sprintf(lang.PkgValidateErrImportDefinition, "bad-url", "URL is not a valid OCI URL"), + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + err := tt.component.Validate() + if tt.expectedErrs == nil { + require.NoError(t, err) + return + } + errs := strings.Split(err.Error(), "\n") + require.ElementsMatch(t, tt.expectedErrs, errs) + }) + } +} diff --git a/zarf.schema.json b/zarf.schema.json index 27a8a89a4c..0f43c9dbe5 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -420,7 +420,7 @@ "properties": { "name": { "type": "string", - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", + "pattern": "^[a-z0-9][a-z0-9\\-]*$", "description": "The name of the component" }, "description": { @@ -1003,7 +1003,7 @@ "properties": { "name": { "type": "string", - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", + "pattern": "^[a-z0-9][a-z0-9\\-]*$", "description": "Name to identify this Zarf package" }, "description": { From 3256cf8e20dc8114bd209ba5460188e7911e1612 Mon Sep 17 00:00:00 2001 From: Justin Bailey Date: Thu, 6 Jun 2024 16:21:15 +0000 Subject: [PATCH 047/132] chore: fix typos (#2590) --- SUPPORT.md | 2 +- adr/0007-use-rust-binary-for-both-injection-stages.md | 2 +- adr/0012-local-image-support-via-docker.md | 2 +- adr/0017-zarf-bundle.md | 2 +- adr/0020-package-sources.md | 2 +- adr/0024-starlight.md | 2 +- packages/zarf-registry/chart/templates/hpa.yaml | 2 +- src/cmd/tools/helm/root.go | 2 +- src/pkg/cluster/tunnel_test.go | 2 +- src/pkg/message/handler.go | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/SUPPORT.md b/SUPPORT.md index 690b7d8402..ad6ce504bd 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -10,7 +10,7 @@ For all questions documentation may not cover, we suggest utilizing [Github Disc All code issues should be a [Github Issue](https://github.com/defenseunicorns/zarf/issues/new/choose) that follows the issue template. Following the templates provides the Zarf community a foundation of understanding to be able to assist quickly. -After an issue is made, this issue can be brought into other chanels such as the [Kubernetes Slack #Zarf](https://zarf.dev/slack) channel or the [bi-weekly Zarf Community Meeting](https://docs.zarf.dev/contribute/contributor-guide/). +After an issue is made, this issue can be brought into other channels such as the [Kubernetes Slack #Zarf](https://zarf.dev/slack) channel or the [bi-weekly Zarf Community Meeting](https://docs.zarf.dev/contribute/contributor-guide/). Github Issue / \ diff --git a/adr/0007-use-rust-binary-for-both-injection-stages.md b/adr/0007-use-rust-binary-for-both-injection-stages.md index 776e02d47a..d402e2cd0b 100644 --- a/adr/0007-use-rust-binary-for-both-injection-stages.md +++ b/adr/0007-use-rust-binary-for-both-injection-stages.md @@ -10,7 +10,7 @@ Amends [3. Image injection into remote clusters without native support](0003-ima ## Context -In ADR 3, the decision was made to create a rust binary (`stage1`) that would re-assemble a `registry:2` image and a go registry binary (`stage2`) from a series of configmaps. While this solution works, it is overkill for the operations that `stage2` performs. The `stage2` binary is only responsible for 1. starting a docker registry in `rw` mode, 2. pushing the `registry:2` crane tarball into said registry, 3. starting the docker registry in `r` mode. This `registry:2` image is then immedietely consumed by the `zarf-registry` package, creating a true in-cluster docker registry. The injector pod is then destroyed. The overhead this operation creates: +In ADR 3, the decision was made to create a rust binary (`stage1`) that would re-assemble a `registry:2` image and a go registry binary (`stage2`) from a series of configmaps. While this solution works, it is overkill for the operations that `stage2` performs. The `stage2` binary is only responsible for 1. starting a docker registry in `rw` mode, 2. pushing the `registry:2` crane tarball into said registry, 3. starting the docker registry in `r` mode. This `registry:2` image is then immediately consumed by the `zarf-registry` package, creating a true in-cluster docker registry. The injector pod is then destroyed. The overhead this operation creates: - having to keep track of another binary (making the total number 3 for the zarf ecosystem) - nearly doubling the amount of configmaps loaded into the cluster (makes init slower) diff --git a/adr/0012-local-image-support-via-docker.md b/adr/0012-local-image-support-via-docker.md index b0070f7e48..32a0102d53 100644 --- a/adr/0012-local-image-support-via-docker.md +++ b/adr/0012-local-image-support-via-docker.md @@ -21,7 +21,7 @@ We did extensive investigation into various strategies of loading docker images ## Decision -We had hoped to leverage docker caching and avoid crane caching moreso, but realized that caching was still occurring via Syft for SBOM. Additionally, the extremely-large, local-only image is actually the edge case here and we created a recommended workaround in the FAQs as well as an inline alert when a large docker image is detected. This restores behavior to what it was before the docker daemon support was added, but with the added benefit of being able to load images from the docker daemon when they are available locally. +We had hoped to leverage docker caching and avoid crane caching more, but realized that caching was still occurring via Syft for SBOM. Additionally, the extremely-large, local-only image is actually the edge case here and we created a recommended workaround in the FAQs as well as an inline alert when a large docker image is detected. This restores behavior to what it was before the docker daemon support was added, but with the added benefit of being able to load images from the docker daemon when they are available locally. ## Consequences diff --git a/adr/0017-zarf-bundle.md b/adr/0017-zarf-bundle.md index 50c0e6a2b8..cb2764464d 100644 --- a/adr/0017-zarf-bundle.md +++ b/adr/0017-zarf-bundle.md @@ -80,7 +80,7 @@ Cons: The current proposition (subject to change before acceptance) is **Zarf Bundles**, which a following PR will focus on and create a POC of. -In essense the `zarf-bundle.yaml` would look something like so: +In essence the `zarf-bundle.yaml` would look something like so: ```yaml metadata: diff --git a/adr/0020-package-sources.md b/adr/0020-package-sources.md index 779929641e..df68d58455 100644 --- a/adr/0020-package-sources.md +++ b/adr/0020-package-sources.md @@ -70,7 +70,7 @@ The following sources have been implemented: - Published OCI package (`oci://`) - In-cluster (Deployed) package (`inspect` and `remove` only) -The `layout` library contains the `PackagePaths` struct which supercedes the prior `TempPaths` struct. This new struct contains access methods to different aspects of Zarf's internal package layout. This struct is passed to the `PackageSource` functions to allow for the loading of packages into the correct layout. In order for a package to be loaded into the correct layout, the package must follow the default Zarf package structure, or be converted to the expected structure during loading operations. +The `layout` library contains the `PackagePaths` struct which supersedes the prior `TempPaths` struct. This new struct contains access methods to different aspects of Zarf's internal package layout. This struct is passed to the `PackageSource` functions to allow for the loading of packages into the correct layout. In order for a package to be loaded into the correct layout, the package must follow the default Zarf package structure, or be converted to the expected structure during loading operations. ## Consequences diff --git a/adr/0024-starlight.md b/adr/0024-starlight.md index c4ff820248..9907b00fbb 100644 --- a/adr/0024-starlight.md +++ b/adr/0024-starlight.md @@ -70,7 +70,7 @@ To simplify the development process and improve the performance of the documenta ## Consequences -- During the transition, we will need to update the existing documentation content to work with the new site generator. Additionally, the site archictecture will be re-evaluated and optimized. This _will_ result in many links to current content breaking. +- During the transition, we will need to update the existing documentation content to work with the new site generator. Additionally, the site architecture will be re-evaluated and optimized. This _will_ result in many links to current content breaking. - Every documentation generator has its quirks and limitations, so we will need to adapt to the new workflow and learn how to best utilize the features of Starlight. - The migration will take time and effort, but the benefits of improved performance and flexibility will be worth it in the long run. - The team will need to learn how to use Astro and Starlight, which may require some training and experimentation. diff --git a/packages/zarf-registry/chart/templates/hpa.yaml b/packages/zarf-registry/chart/templates/hpa.yaml index 7045a709d3..cc4d00a85a 100644 --- a/packages/zarf-registry/chart/templates/hpa.yaml +++ b/packages/zarf-registry/chart/templates/hpa.yaml @@ -29,7 +29,7 @@ spec: averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} behavior: scaleDown: - # Use 60 second stabilization window becuase zarf will freeze scale down during deploys + # Use 60 second stabilization window because zarf will freeze scale down during deploys stabilizationWindowSeconds: 60 # Initially disable scale down - this gets set to Min later by Zarf (src/test/e2e/20_zarf_init_test.go) selectPolicy: Disabled diff --git a/src/cmd/tools/helm/root.go b/src/cmd/tools/helm/root.go index e496a93cec..50af36d7f1 100644 --- a/src/cmd/tools/helm/root.go +++ b/src/cmd/tools/helm/root.go @@ -235,7 +235,7 @@ func checkForExpiredRepos(repofile string) { } // parse repo file. - // Ignore the error because it is okay for a repo file to be unparseable at this + // Ignore the error because it is okay for a repo file to be unparsable at this // stage. Later checks will trap the error and respond accordingly. repoFile, err := repo.LoadFile(repofile) if err != nil { diff --git a/src/pkg/cluster/tunnel_test.go b/src/pkg/cluster/tunnel_test.go index 41c4aac407..debe363e54 100644 --- a/src/pkg/cluster/tunnel_test.go +++ b/src/pkg/cluster/tunnel_test.go @@ -40,7 +40,7 @@ func TestServiceInfoFromNodePortURL(t *testing.T) { expectedErr: "no matching node port services found", }, { - name: "found serivce", + name: "found service", nodePortURL: "http://localhost:30001", services: []corev1.Service{ { diff --git a/src/pkg/message/handler.go b/src/pkg/message/handler.go index 46fab9cbcf..825aa52fd4 100644 --- a/src/pkg/message/handler.go +++ b/src/pkg/message/handler.go @@ -17,7 +17,7 @@ func (z ZarfHandler) Enabled(_ context.Context, _ slog.Level) bool { return true } -// WithAttrs is not suppported +// WithAttrs is not supported func (z ZarfHandler) WithAttrs(_ []slog.Attr) slog.Handler { return z } From 2707a61df3af7c6118edcf91b909e300d38ed306 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:09:19 -0400 Subject: [PATCH 048/132] fix: docker containerd blob error (#2593) ## Description Fixes #2584 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/images/pull.go | 25 +++++++++++++++++++++++++ src/pkg/layout/image.go | 6 ++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go index c440f8d33d..9063075879 100644 --- a/src/internal/packager/images/pull.go +++ b/src/internal/packager/images/pull.go @@ -246,6 +246,31 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er doneSaving <- nil <-doneSaving + // Needed because when pulling from the local docker daemon, while using the docker containerd runtime + // Crane incorrectly names the blob of the docker image config to a sha that does not match the contents + // https://github.com/defenseunicorns/zarf/issues/2584 + // This is a band aid fix while we wait for crane and or docker to create the permanent fix + blobDir := filepath.Join(cfg.DestinationDirectory, "blobs", "sha256") + err = filepath.Walk(blobDir, func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + if fi.IsDir() { + return nil + } + + hash, err := helpers.GetSHA256OfFile(path) + if err != nil { + return err + } + newFile := filepath.Join(blobDir, hash) + return os.Rename(path, newFile) + }) + if err != nil { + return nil, err + } + return fetched, nil } diff --git a/src/pkg/layout/image.go b/src/pkg/layout/image.go index 15348bf1d3..7d236410bf 100644 --- a/src/pkg/layout/image.go +++ b/src/pkg/layout/image.go @@ -44,11 +44,13 @@ func (i *Images) AddV1Image(img v1.Image) error { } i.AddBlob(digest.Hex) } - imgCfgSha, err := img.ConfigName() + manifest, err := img.Manifest() if err != nil { return err } - i.AddBlob(imgCfgSha.Hex) + // Cannot use img.ConfigName to get this value because of an upstream bug in crane / docker using the containerd runtime + // https://github.com/defenseunicorns/zarf/issues/2584 + i.AddBlob(manifest.Config.Digest.Hex) manifestSha, err := img.Digest() if err != nil { return err From 245b36be91be16c992f0639c7b4f6c12a057a78c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 17:47:59 -0500 Subject: [PATCH 049/132] fix(deps): update module github.com/defenseunicorns/pkg/oci to v1 (#2511) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [github.com/defenseunicorns/pkg/oci](https://togithub.com/defenseunicorns/pkg) | `v0.0.1` -> `v1.0.1` | [![age](https://developer.mend.io/api/mc/badges/age/go/github.com%2fdefenseunicorns%2fpkg%2foci/v1.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/go/github.com%2fdefenseunicorns%2fpkg%2foci/v1.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/go/github.com%2fdefenseunicorns%2fpkg%2foci/v0.0.1/v1.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/go/github.com%2fdefenseunicorns%2fpkg%2foci/v0.0.1/v1.0.1?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/defenseunicorns/zarf). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lucas Rodriguez --- go.mod | 7 ++- go.sum | 8 +-- src/cmd/destroy.go | 2 +- src/cmd/dev.go | 2 +- src/cmd/initialize.go | 2 +- src/cmd/internal.go | 2 +- src/cmd/package.go | 2 +- src/cmd/tools/helm/repo_add.go | 2 +- src/cmd/tools/helm/repo_index.go | 2 +- src/cmd/tools/helm/repo_remove.go | 2 +- src/cmd/tools/zarf.go | 2 +- src/extensions/bigbang/bigbang.go | 2 +- src/extensions/bigbang/flux.go | 2 +- .../agent/hooks/argocd-application.go | 2 +- src/internal/agent/hooks/argocd-repository.go | 2 +- src/internal/agent/hooks/flux.go | 2 +- src/internal/packager/helm/chart.go | 2 +- src/internal/packager/helm/images.go | 2 +- src/internal/packager/helm/post-render.go | 2 +- src/internal/packager/helm/repo.go | 2 +- src/internal/packager/helm/utils.go | 2 +- src/internal/packager/images/common.go | 2 +- src/internal/packager/images/pull.go | 2 +- src/internal/packager/images/push.go | 6 +-- src/internal/packager/kustomize/build.go | 2 +- src/internal/packager/sbom/catalog.go | 2 +- src/internal/packager/template/template.go | 2 +- src/pkg/cluster/data.go | 2 +- src/pkg/cluster/injector.go | 2 +- src/pkg/cluster/state.go | 2 +- src/pkg/cluster/state_test.go | 2 +- src/pkg/cluster/tunnel.go | 2 +- src/pkg/k8s/tunnel.go | 2 +- src/pkg/layout/component.go | 2 +- src/pkg/layout/package.go | 2 +- src/pkg/layout/sbom.go | 2 +- src/pkg/message/message.go | 2 +- src/pkg/message/progress.go | 51 +++++++++++-------- src/pkg/packager/actions/actions.go | 2 +- src/pkg/packager/composer/list.go | 2 +- src/pkg/packager/composer/oci.go | 2 +- src/pkg/packager/composer/pathfixer.go | 2 +- src/pkg/packager/create.go | 2 +- src/pkg/packager/creator/normal.go | 2 +- src/pkg/packager/creator/skeleton.go | 2 +- src/pkg/packager/deploy.go | 2 +- src/pkg/packager/dev.go | 2 +- src/pkg/packager/filters/deploy.go | 2 +- src/pkg/packager/filters/deploy_test.go | 2 +- src/pkg/packager/filters/select.go | 2 +- src/pkg/packager/generate.go | 2 +- src/pkg/packager/inspect.go | 2 +- src/pkg/packager/interactive.go | 2 +- src/pkg/packager/lint/lint.go | 2 +- src/pkg/packager/lint/validator.go | 2 +- src/pkg/packager/prepare.go | 2 +- src/pkg/packager/publish.go | 2 +- src/pkg/packager/remove.go | 2 +- src/pkg/packager/sources/cluster.go | 2 +- src/pkg/packager/sources/new.go | 2 +- src/pkg/packager/sources/split.go | 2 +- src/pkg/packager/sources/tarball.go | 2 +- src/pkg/packager/sources/url.go | 2 +- src/pkg/packager/sources/utils.go | 2 +- src/pkg/packager/sources/validate.go | 2 +- src/pkg/pki/pki.go | 2 +- src/pkg/transform/artifact.go | 2 +- src/pkg/transform/git.go | 2 +- src/pkg/transform/image.go | 2 +- src/pkg/utils/bytes.go | 6 ++- src/pkg/utils/cosign.go | 2 +- src/pkg/utils/image.go | 2 +- src/pkg/utils/io.go | 6 +-- src/pkg/utils/network.go | 4 +- src/pkg/utils/network_test.go | 2 +- src/pkg/variables/templates.go | 2 +- src/pkg/zoci/copier.go | 2 +- src/pkg/zoci/pull.go | 2 +- src/pkg/zoci/push.go | 4 +- src/pkg/zoci/utils.go | 2 +- src/test/common.go | 2 +- src/test/e2e/00_use_cli_test.go | 2 +- src/test/e2e/05_tarball_test.go | 2 +- src/test/e2e/51_oci_compose_test.go | 2 +- src/test/external/ext_out_cluster_test.go | 2 +- src/types/k8s.go | 2 +- src/types/validate.go | 2 +- src/types/validate_test.go | 2 +- 88 files changed, 130 insertions(+), 122 deletions(-) diff --git a/go.mod b/go.mod index 137284437c..cc839e69ef 100644 --- a/go.mod +++ b/go.mod @@ -12,8 +12,8 @@ require ( github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f github.com/anchore/stereoscope v0.0.1 github.com/anchore/syft v0.100.0 - github.com/defenseunicorns/pkg/helpers v1.1.1 - github.com/defenseunicorns/pkg/oci v0.0.1 + github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 + github.com/defenseunicorns/pkg/oci v1.0.1 github.com/derailed/k9s v0.31.7 github.com/distribution/reference v0.5.0 github.com/fairwindsops/pluto/v5 v5.18.4 @@ -61,8 +61,6 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -require cuelang.org/go v0.7.0 // indirect - require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect @@ -76,6 +74,7 @@ require ( cloud.google.com/go/longrunning v0.5.7 // indirect cloud.google.com/go/storage v1.41.0 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 // indirect + cuelang.org/go v0.7.0 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect diff --git a/go.sum b/go.sum index cb92210e44..8b31775074 100644 --- a/go.sum +++ b/go.sum @@ -597,10 +597,10 @@ github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gwevOZ0fxT2nzM9hrtdPbsiOHjFqDRIYMzJHba3/G6Q= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= -github.com/defenseunicorns/pkg/helpers v1.1.1 h1:p3pKeK5SeFaoZUJZIX9sEsJqX1CGGMS8OpQMPgJtSqM= -github.com/defenseunicorns/pkg/helpers v1.1.1/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= -github.com/defenseunicorns/pkg/oci v0.0.1 h1:EFRp3NeiwzhOWKpQ6mAxi0l9chnrAvDcIgjMr0o0fkM= -github.com/defenseunicorns/pkg/oci v0.0.1/go.mod h1:zVBgRjckEAhfdvbnQrnfOP/3M/GYJkIgWtJtY7pjYdo= +github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= +github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= +github.com/defenseunicorns/pkg/oci v1.0.1 h1:WPrWRrae1L19X1vuhy6yYMR2zrTzgBbJHp3ImgUm4ZM= +github.com/defenseunicorns/pkg/oci v1.0.1/go.mod h1:qZ3up/d0P81taW37fKR4lb19jJhQZJVtNOEJMu00dHQ= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4imdLi4Asdha1Sc6feLMTfPLXh9IUYmysk= github.com/depcheck-test/depcheck-test v0.0.0-20220607135614-199033aaa936 h1:foGzavPWwtoyBvjWyKJYDYsyzy+23iBV7NKTwdk+LRY= diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index 429aec2c16..0034186089 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -9,7 +9,7 @@ import ( "os" "regexp" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 17962176a4..1266232cde 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/AlecAivazis/survey/v2" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index b41c3ca8d9..7673be27c4 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/AlecAivazis/survey/v2" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" diff --git a/src/cmd/internal.go b/src/cmd/internal.go index 59c1981b0a..ceca2d612e 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent" diff --git a/src/cmd/package.go b/src/cmd/package.go index a62f11e1c7..c6ac07e698 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -19,7 +19,7 @@ import ( "oras.land/oras-go/v2/registry" "github.com/AlecAivazis/survey/v2" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/packager" diff --git a/src/cmd/tools/helm/repo_add.go b/src/cmd/tools/helm/repo_add.go index 114cd020f5..5bbca44356 100644 --- a/src/cmd/tools/helm/repo_add.go +++ b/src/cmd/tools/helm/repo_add.go @@ -30,7 +30,7 @@ import ( "strings" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/gofrs/flock" diff --git a/src/cmd/tools/helm/repo_index.go b/src/cmd/tools/helm/repo_index.go index 1d6182e85e..ddccb7b46e 100644 --- a/src/cmd/tools/helm/repo_index.go +++ b/src/cmd/tools/helm/repo_index.go @@ -26,7 +26,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" diff --git a/src/cmd/tools/helm/repo_remove.go b/src/cmd/tools/helm/repo_remove.go index 00f5cd45d4..6decbadf64 100644 --- a/src/cmd/tools/helm/repo_remove.go +++ b/src/cmd/tools/helm/repo_remove.go @@ -27,7 +27,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/pkg/errors" "github.com/spf13/cobra" diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index bab8eb17a5..243bc74027 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -13,7 +13,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/spf13/cobra" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/cmd/common" diff --git a/src/extensions/bigbang/bigbang.go b/src/extensions/bigbang/bigbang.go index e71954f7d0..b89b61f460 100644 --- a/src/extensions/bigbang/bigbang.go +++ b/src/extensions/bigbang/bigbang.go @@ -13,7 +13,7 @@ import ( "time" "github.com/Masterminds/semver/v3" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/extensions/bigbang/flux.go b/src/extensions/bigbang/flux.go index 6b7f8ebc6e..e67129c07c 100644 --- a/src/extensions/bigbang/flux.go +++ b/src/extensions/bigbang/flux.go @@ -10,7 +10,7 @@ import ( "path" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/internal/packager/kustomize" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index 0e037fbb00..a09b6d003f 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -9,7 +9,7 @@ import ( "encoding/json" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 53d96af44b..40506a395f 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -10,7 +10,7 @@ import ( "encoding/json" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux.go index 77382aecf3..2638da0b5b 100644 --- a/src/internal/agent/hooks/flux.go +++ b/src/internal/agent/hooks/flux.go @@ -9,7 +9,7 @@ import ( "encoding/json" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go index 3f0fc1ac8d..0ff647b60a 100644 --- a/src/internal/packager/helm/chart.go +++ b/src/internal/packager/helm/chart.go @@ -22,7 +22,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/yaml" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/internal/packager/helm/images.go b/src/internal/packager/helm/images.go index dbe4d051ac..447aefc780 100644 --- a/src/internal/packager/helm/images.go +++ b/src/internal/packager/helm/images.go @@ -4,7 +4,7 @@ package helm import ( - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/goccy/go-yaml" "helm.sh/helm/v3/pkg/chart/loader" diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index f4789fdcca..0a44acd06b 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -13,7 +13,7 @@ import ( "reflect" "slices" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/internal/packager/helm/repo.go b/src/internal/packager/helm/repo.go index 97582a64b9..245625c787 100644 --- a/src/internal/packager/helm/repo.go +++ b/src/internal/packager/helm/repo.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/git" diff --git a/src/internal/packager/helm/utils.go b/src/internal/packager/helm/utils.go index 2f3b11035a..4f6119de8e 100644 --- a/src/internal/packager/helm/utils.go +++ b/src/internal/packager/helm/utils.go @@ -7,7 +7,7 @@ package helm import ( "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" "helm.sh/helm/v3/pkg/action" "helm.sh/helm/v3/pkg/chart" diff --git a/src/internal/packager/images/common.go b/src/internal/packager/images/common.go index 9fca5c0a0c..8efb18f12d 100644 --- a/src/internal/packager/images/common.go +++ b/src/internal/packager/images/common.go @@ -8,7 +8,7 @@ import ( "net/http" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go index 9063075879..11cc686239 100644 --- a/src/internal/packager/images/pull.go +++ b/src/internal/packager/images/pull.go @@ -18,7 +18,7 @@ import ( "sync/atomic" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index e9a3645335..84a00a2455 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -9,7 +9,7 @@ import ( "fmt" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -53,7 +53,7 @@ func Push(ctx context.Context, cfg PushConfig) error { ) progress := message.NewProgressBar(totalSize, fmt.Sprintf("Pushing %d images", len(toPush))) - defer progress.Stop() + defer progress.Close() if err := helpers.Retry(func() error { c, _ := cluster.NewCluster() @@ -86,7 +86,7 @@ func Push(ctx context.Context, cfg PushConfig) error { }() for refInfo, img := range toPush { refTruncated := helpers.Truncate(refInfo.Reference, 55, true) - progress.UpdateTitle(fmt.Sprintf("Pushing %s", refTruncated)) + progress.Updatef(fmt.Sprintf("Pushing %s", refTruncated)) size, err := calcImgSize(img) if err != nil { diff --git a/src/internal/packager/kustomize/build.go b/src/internal/packager/kustomize/build.go index 29f05281e0..8b640b905a 100644 --- a/src/internal/packager/kustomize/build.go +++ b/src/internal/packager/kustomize/build.go @@ -8,7 +8,7 @@ import ( "fmt" "os" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "sigs.k8s.io/kustomize/api/krusty" krustytypes "sigs.k8s.io/kustomize/api/types" "sigs.k8s.io/kustomize/kyaml/filesys" diff --git a/src/internal/packager/sbom/catalog.go b/src/internal/packager/sbom/catalog.go index 3fba53bb2d..2843791e3f 100755 --- a/src/internal/packager/sbom/catalog.go +++ b/src/internal/packager/sbom/catalog.go @@ -23,7 +23,7 @@ import ( "github.com/anchore/syft/syft/pkg/cataloger" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/internal/packager/template/template.go b/src/internal/packager/template/template.go index 20f0d42af4..ad7ee64f11 100644 --- a/src/internal/packager/template/template.go +++ b/src/internal/packager/template/template.go @@ -12,7 +12,7 @@ import ( "github.com/defenseunicorns/zarf/src/types" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/interactive" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index 0c5e526536..9b52b934ab 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -13,7 +13,7 @@ import ( "strings" "sync" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 3412553d29..8342315a3e 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -13,7 +13,7 @@ import ( "regexp" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 03ac17e32c..ba0cf0ee95 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -16,7 +16,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/k8s" diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index e056aab5f4..7259daa884 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -15,7 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 122a57abf4..113779671b 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -14,7 +14,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go index 7b890d2e20..a2e5f25db9 100644 --- a/src/pkg/k8s/tunnel.go +++ b/src/pkg/k8s/tunnel.go @@ -18,7 +18,7 @@ import ( "k8s.io/client-go/tools/portforward" "k8s.io/client-go/transport/spdy" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" ) // Global lock to synchronize port selections. diff --git a/src/pkg/layout/component.go b/src/pkg/layout/component.go index e8da0de72a..c933fce6f6 100644 --- a/src/pkg/layout/component.go +++ b/src/pkg/layout/component.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" "github.com/mholt/archiver/v3" diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go index 3949f4fcc2..decc7a82e1 100644 --- a/src/pkg/layout/package.go +++ b/src/pkg/layout/package.go @@ -12,7 +12,7 @@ import ( "strings" "github.com/Masterminds/semver/v3" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/interactive" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated" diff --git a/src/pkg/layout/sbom.go b/src/pkg/layout/sbom.go index 8cfaaae6a0..7ac39c02a7 100644 --- a/src/pkg/layout/sbom.go +++ b/src/pkg/layout/sbom.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/mholt/archiver/v3" ) diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 9cafa0f1c1..7c50315e77 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -14,7 +14,7 @@ import ( "strings" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/fatih/color" "github.com/pterm/pterm" diff --git a/src/pkg/message/progress.go b/src/pkg/message/progress.go index 1c6e732042..7c050b0ba2 100644 --- a/src/pkg/message/progress.go +++ b/src/pkg/message/progress.go @@ -5,6 +5,7 @@ package message import ( + "fmt" "os" "github.com/pterm/pterm" @@ -40,24 +41,43 @@ func NewProgressBar(total int64, text string) *ProgressBar { } } -// Update updates the ProgressBar with completed progress and new text. -func (p *ProgressBar) Update(complete int64, text string) { +// Updatef updates the ProgressBar with new text. +func (p *ProgressBar) Updatef(format string, a ...any) { + msg := fmt.Sprintf(format, a...) if NoProgress { - debugPrinter(2, text) + debugPrinter(2, msg) return } - p.progress.UpdateTitle(padding + text) - chunk := int(complete) - p.progress.Current - p.Add(chunk) + p.progress.UpdateTitle(padding + msg) +} + +// Failf marks the ProgressBar as failed in the CLI. +func (p *ProgressBar) Failf(format string, a ...any) { + p.Close() + Warnf(format, a...) +} + +// Close stops the ProgressBar from continuing. +func (p *ProgressBar) Close() error { + if p.progress == nil { + return nil + } + _, err := p.progress.Stop() + if err != nil { + return err + } + return nil } -// UpdateTitle updates the ProgressBar with new text. -func (p *ProgressBar) UpdateTitle(text string) { +// Update updates the ProgressBar with completed progress and new text. +func (p *ProgressBar) Update(complete int64, text string) { if NoProgress { debugPrinter(2, text) return } p.progress.UpdateTitle(padding + text) + chunk := int(complete) - p.progress.Current + p.Add(chunk) } // Add updates the ProgressBar with completed progress. @@ -83,23 +103,10 @@ func (p *ProgressBar) Write(data []byte) (int, error) { // Successf marks the ProgressBar as successful in the CLI. func (p *ProgressBar) Successf(format string, a ...any) { - p.Stop() + p.Close() pterm.Success.Printfln(format, a...) } -// Stop stops the ProgressBar from continuing. -func (p *ProgressBar) Stop() { - if p.progress != nil { - _, _ = p.progress.Stop() - } -} - -// Errorf marks the ProgressBar as failed in the CLI. -func (p *ProgressBar) Errorf(err error, format string, a ...any) { - p.Stop() - WarnErrf(err, format, a...) -} - // GetCurrent returns the current total func (p *ProgressBar) GetCurrent() int { if p.progress != nil { diff --git a/src/pkg/packager/actions/actions.go b/src/pkg/packager/actions/actions.go index e68947895b..2f01757d6c 100644 --- a/src/pkg/packager/actions/actions.go +++ b/src/pkg/packager/actions/actions.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/internal/packager/template" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go index c88a0bb019..5c2d378d14 100644 --- a/src/pkg/packager/composer/list.go +++ b/src/pkg/packager/composer/list.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/extensions/bigbang" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated" diff --git a/src/pkg/packager/composer/oci.go b/src/pkg/packager/composer/oci.go index 53fd42ad77..baa4d0fc81 100644 --- a/src/pkg/packager/composer/oci.go +++ b/src/pkg/packager/composer/oci.go @@ -11,7 +11,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/packager/composer/pathfixer.go b/src/pkg/packager/composer/pathfixer.go index a37dfe47f2..fac110c47c 100644 --- a/src/pkg/packager/composer/pathfixer.go +++ b/src/pkg/packager/composer/pathfixer.go @@ -7,7 +7,7 @@ package composer import ( "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/types" ) diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index bad88d5d4e..7613c2f6f4 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -8,7 +8,7 @@ import ( "fmt" "os" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 7747cfdd4d..96f219e584 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -15,7 +15,7 @@ import ( "strings" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" diff --git a/src/pkg/packager/creator/skeleton.go b/src/pkg/packager/creator/skeleton.go index b6e5498322..43866f8484 100644 --- a/src/pkg/packager/creator/skeleton.go +++ b/src/pkg/packager/creator/skeleton.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/extensions/bigbang" diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index b7268bb8ca..b6e26e7693 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -21,7 +21,7 @@ import ( kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index 187f74c70c..a18d1dec0f 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -10,7 +10,7 @@ import ( "os" "runtime" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/packager/filters/deploy.go b/src/pkg/packager/filters/deploy.go index 622f481229..f5ac17916f 100644 --- a/src/pkg/packager/filters/deploy.go +++ b/src/pkg/packager/filters/deploy.go @@ -10,7 +10,7 @@ import ( "strings" "github.com/agnivade/levenshtein" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/interactive" "github.com/defenseunicorns/zarf/src/types" ) diff --git a/src/pkg/packager/filters/deploy_test.go b/src/pkg/packager/filters/deploy_test.go index 310ed6286b..029a2a9d53 100644 --- a/src/pkg/packager/filters/deploy_test.go +++ b/src/pkg/packager/filters/deploy_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" ) diff --git a/src/pkg/packager/filters/select.go b/src/pkg/packager/filters/select.go index 2927fb827c..9116efe453 100644 --- a/src/pkg/packager/filters/select.go +++ b/src/pkg/packager/filters/select.go @@ -5,7 +5,7 @@ package filters import ( - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/types" ) diff --git a/src/pkg/packager/generate.go b/src/pkg/packager/generate.go index 64bb824285..d03e80c88d 100644 --- a/src/pkg/packager/generate.go +++ b/src/pkg/packager/generate.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go index 56645e2d54..5500be26da 100644 --- a/src/pkg/packager/inspect.go +++ b/src/pkg/packager/inspect.go @@ -8,7 +8,7 @@ import ( "fmt" "os" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/internal/packager/sbom" "github.com/defenseunicorns/zarf/src/pkg/utils" ) diff --git a/src/pkg/packager/interactive.go b/src/pkg/packager/interactive.go index d1c0711dc7..2326a4910a 100644 --- a/src/pkg/packager/interactive.go +++ b/src/pkg/packager/interactive.go @@ -10,7 +10,7 @@ import ( "path/filepath" "github.com/AlecAivazis/survey/v2" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go index 9f68150c3b..1a58ef857f 100644 --- a/src/pkg/packager/lint/lint.go +++ b/src/pkg/packager/lint/lint.go @@ -12,7 +12,7 @@ import ( "regexp" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/packager/lint/validator.go b/src/pkg/packager/lint/validator.go index 619947c591..532ea28a64 100644 --- a/src/pkg/packager/lint/validator.go +++ b/src/pkg/packager/lint/validator.go @@ -8,7 +8,7 @@ import ( "fmt" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" "github.com/fatih/color" diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go index 54c9158d5e..276cdcc4db 100644 --- a/src/pkg/packager/prepare.go +++ b/src/pkg/packager/prepare.go @@ -14,7 +14,7 @@ import ( "github.com/goccy/go-yaml" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/internal/packager/images" diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go index 899579332f..c06ecafb34 100644 --- a/src/pkg/packager/publish.go +++ b/src/pkg/packager/publish.go @@ -10,7 +10,7 @@ import ( "os" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index a107147714..535e7dedc4 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -13,7 +13,7 @@ import ( "slices" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/pkg/cluster" diff --git a/src/pkg/packager/sources/cluster.go b/src/pkg/packager/sources/cluster.go index c1478cb3da..ad14136afd 100644 --- a/src/pkg/packager/sources/cluster.go +++ b/src/pkg/packager/sources/cluster.go @@ -8,7 +8,7 @@ import ( "context" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" diff --git a/src/pkg/packager/sources/new.go b/src/pkg/packager/sources/new.go index c9b2e2438f..192bd39f88 100644 --- a/src/pkg/packager/sources/new.go +++ b/src/pkg/packager/sources/new.go @@ -9,7 +9,7 @@ import ( "net/url" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" diff --git a/src/pkg/packager/sources/split.go b/src/pkg/packager/sources/split.go index fe4e9fbade..d635159002 100644 --- a/src/pkg/packager/sources/split.go +++ b/src/pkg/packager/sources/split.go @@ -13,7 +13,7 @@ import ( "sort" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go index 2172d7ef13..b6a70d4355 100644 --- a/src/pkg/packager/sources/tarball.go +++ b/src/pkg/packager/sources/tarball.go @@ -12,7 +12,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" diff --git a/src/pkg/packager/sources/url.go b/src/pkg/packager/sources/url.go index 384be49668..67b6ccd1d9 100644 --- a/src/pkg/packager/sources/url.go +++ b/src/pkg/packager/sources/url.go @@ -10,7 +10,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" diff --git a/src/pkg/packager/sources/utils.go b/src/pkg/packager/sources/utils.go index 280e940930..f880e11d0e 100644 --- a/src/pkg/packager/sources/utils.go +++ b/src/pkg/packager/sources/utils.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/zoci" diff --git a/src/pkg/packager/sources/validate.go b/src/pkg/packager/sources/validate.go index cddf34d816..1d8a4f6255 100644 --- a/src/pkg/packager/sources/validate.go +++ b/src/pkg/packager/sources/validate.go @@ -13,7 +13,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/pki/pki.go b/src/pkg/pki/pki.go index 68373d3f81..f732b2e1cb 100644 --- a/src/pkg/pki/pki.go +++ b/src/pkg/pki/pki.go @@ -14,7 +14,7 @@ import ( "net" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" ) diff --git a/src/pkg/transform/artifact.go b/src/pkg/transform/artifact.go index 96a295e4c6..0aed7a46ea 100644 --- a/src/pkg/transform/artifact.go +++ b/src/pkg/transform/artifact.go @@ -10,7 +10,7 @@ import ( "regexp" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" ) const ( diff --git a/src/pkg/transform/git.go b/src/pkg/transform/git.go index 3710579ab2..d215df9481 100644 --- a/src/pkg/transform/git.go +++ b/src/pkg/transform/git.go @@ -9,7 +9,7 @@ import ( "net/url" "regexp" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" ) // For further explanation: https://regex101.com/r/YxpfhC/5 diff --git a/src/pkg/transform/image.go b/src/pkg/transform/image.go index 3b5de1d920..293cb61a64 100644 --- a/src/pkg/transform/image.go +++ b/src/pkg/transform/image.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/distribution/reference" ) diff --git a/src/pkg/utils/bytes.go b/src/pkg/utils/bytes.go index 7ef3ef5c8c..4ff6c06d59 100644 --- a/src/pkg/utils/bytes.go +++ b/src/pkg/utils/bytes.go @@ -12,7 +12,7 @@ import ( "strconv" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -74,7 +74,9 @@ func RenderProgressBarForLocalDirWrite(filepath string, expectedTotal int64, com completeChan <- nil return } else { - progressBar.Stop() + if err := progressBar.Close(); err != nil { + message.Debugf("unable to close progress bar: %s", err.Error()) + } completeChan <- nil return } diff --git a/src/pkg/utils/cosign.go b/src/pkg/utils/cosign.go index d9f017afce..d4b8fe695d 100644 --- a/src/pkg/utils/cosign.go +++ b/src/pkg/utils/cosign.go @@ -11,7 +11,7 @@ import ( "os" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" diff --git a/src/pkg/utils/image.go b/src/pkg/utils/image.go index 0074ea9036..80920cbc8b 100644 --- a/src/pkg/utils/image.go +++ b/src/pkg/utils/image.go @@ -10,7 +10,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/transform" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" diff --git a/src/pkg/utils/io.go b/src/pkg/utils/io.go index 55c60fc1dc..8b9592def3 100755 --- a/src/pkg/utils/io.go +++ b/src/pkg/utils/io.go @@ -12,7 +12,7 @@ import ( "os" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" @@ -108,7 +108,7 @@ func SplitFile(srcPath string, chunkSizeBytes int) (err error) { // start progress bar title := fmt.Sprintf("[0/%d] MB bytes written", fileSize/1000/1000) progressBar := message.NewProgressBar(fileSize, title) - defer progressBar.Stop() + defer progressBar.Close() // open srcFile srcFile, err := os.Open(srcPath) @@ -183,7 +183,7 @@ func SplitFile(srcPath string, chunkSizeBytes int) (err error) { // update progress bar progressBar.Add(bufferSize) title := fmt.Sprintf("[%d/%d] MB bytes written", progressBar.GetCurrent()/1000/1000, fileSize/1000/1000) - progressBar.UpdateTitle(title) + progressBar.Updatef(title) } srcFile.Close() _ = os.RemoveAll(srcPath) diff --git a/src/pkg/utils/network.go b/src/pkg/utils/network.go index 036d4fe57c..8a4a93821f 100644 --- a/src/pkg/utils/network.go +++ b/src/pkg/utils/network.go @@ -14,7 +14,7 @@ import ( "path/filepath" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -112,7 +112,7 @@ func httpGetFile(url string, destinationFile *os.File) error { progressBar := message.NewProgressBar(resp.ContentLength, title) if _, err = io.Copy(destinationFile, io.TeeReader(resp.Body, progressBar)); err != nil { - progressBar.Errorf(err, "Unable to save the file %s", destinationFile.Name()) + progressBar.Failf("Unable to save the file %s: %s", destinationFile.Name(), err.Error()) return err } diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go index 1728fe874b..ee30624885 100644 --- a/src/pkg/utils/network_test.go +++ b/src/pkg/utils/network_test.go @@ -15,7 +15,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" ) func TestParseChecksum(t *testing.T) { diff --git a/src/pkg/variables/templates.go b/src/pkg/variables/templates.go index 9363357d39..e350d77c28 100644 --- a/src/pkg/variables/templates.go +++ b/src/pkg/variables/templates.go @@ -11,7 +11,7 @@ import ( "regexp" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" ) // TextTemplate represents a value to be templated into a text file. diff --git a/src/pkg/zoci/copier.go b/src/pkg/zoci/copier.go index 8ff5b26c69..d6ed8dffb4 100644 --- a/src/pkg/zoci/copier.go +++ b/src/pkg/zoci/copier.go @@ -27,7 +27,7 @@ func CopyPackage(ctx context.Context, src *Remote, dst *Remote, concurrency int) title := fmt.Sprintf("[0/%d] layers copied", len(layers)) progressBar := message.NewProgressBar(size, title) - defer progressBar.Stop() + defer progressBar.Close() if err := oci.Copy(ctx, src.OrasRemote, dst.OrasRemote, nil, concurrency, progressBar); err != nil { return err diff --git a/src/pkg/zoci/pull.go b/src/pkg/zoci/pull.go index 9fb7a8408a..bd259259e1 100644 --- a/src/pkg/zoci/pull.go +++ b/src/pkg/zoci/pull.go @@ -9,7 +9,7 @@ import ( "fmt" "path/filepath" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/transform" diff --git a/src/pkg/zoci/push.go b/src/pkg/zoci/push.go index 380750574e..ccbad6862b 100644 --- a/src/pkg/zoci/push.go +++ b/src/pkg/zoci/push.go @@ -8,7 +8,7 @@ import ( "context" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/pkg/oci" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -68,7 +68,7 @@ func (r *Remote) PublishPackage(ctx context.Context, pkg *types.ZarfPackage, pat total += manifestConfigDesc.Size progressBar := message.NewProgressBar(total, fmt.Sprintf("Publishing %s:%s", r.Repo().Reference.Repository, r.Repo().Reference.Reference)) - defer progressBar.Stop() + defer progressBar.Close() r.SetProgressWriter(progressBar) defer r.ClearProgressWriter() diff --git a/src/pkg/zoci/utils.go b/src/pkg/zoci/utils.go index 968db5f591..e63910a738 100644 --- a/src/pkg/zoci/utils.go +++ b/src/pkg/zoci/utils.go @@ -9,7 +9,7 @@ import ( "fmt" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/types" "oras.land/oras-go/v2/registry" ) diff --git a/src/test/common.go b/src/test/common.go index 35d6820d28..4eb9844045 100644 --- a/src/test/common.go +++ b/src/test/common.go @@ -16,7 +16,7 @@ import ( "slices" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/stretchr/testify/require" ) diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index e830139086..c59fdf9d55 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/otiai10/copy" "github.com/stretchr/testify/require" ) diff --git a/src/test/e2e/05_tarball_test.go b/src/test/e2e/05_tarball_test.go index e929d3b420..7293b378bc 100644 --- a/src/test/e2e/05_tarball_test.go +++ b/src/test/e2e/05_tarball_test.go @@ -11,7 +11,7 @@ import ( "path/filepath" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" diff --git a/src/test/e2e/51_oci_compose_test.go b/src/test/e2e/51_oci_compose_test.go index 7c55e3238d..c59d7477c1 100644 --- a/src/test/e2e/51_oci_compose_test.go +++ b/src/test/e2e/51_oci_compose_test.go @@ -12,7 +12,7 @@ import ( "strings" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index eeef4b94db..050150e5fa 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -15,7 +15,7 @@ import ( "path/filepath" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/stretchr/testify/require" diff --git a/src/types/k8s.go b/src/types/k8s.go index 581b02e947..183125c72b 100644 --- a/src/types/k8s.go +++ b/src/types/k8s.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/k8s" ) diff --git a/src/types/validate.go b/src/types/validate.go index fa5cfcbd87..131c556e39 100644 --- a/src/types/validate.go +++ b/src/types/validate.go @@ -11,7 +11,7 @@ import ( "regexp" "slices" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" ) diff --git a/src/types/validate_test.go b/src/types/validate_test.go index 5fcce6821b..64af534f58 100644 --- a/src/types/validate_test.go +++ b/src/types/validate_test.go @@ -10,7 +10,7 @@ import ( "strings" "testing" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/variables" "github.com/stretchr/testify/require" From 7ad3e53899e9f8f3a56298e7f739ac5309987394 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 7 Jun 2024 16:53:37 +0200 Subject: [PATCH 050/132] fix: change so that second SIGINT signal immediately exits program (#2598) ## Description This changes the behavior so that the second SIGINT signal immediately exits. This is useful if a process is not quitting in context cancel or if it is taking too long to quit. ## Related Issue Relates to #2594 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- main.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 0449ccd119..8068f4b4fc 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ package main import ( "context" "embed" + "os" "os/signal" "syscall" @@ -22,8 +23,22 @@ var cosignPublicKey string var zarfSchema embed.FS func main() { - ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + ctx, cancel := context.WithCancel(context.Background()) defer cancel() + signalCh := make(chan os.Signal, 1) + signal.Notify(signalCh, syscall.SIGINT, syscall.SIGTERM) + go func() { + first := true + for { + <-signalCh + if first { + first = false + cancel() + continue + } + os.Exit(1) + } + }() config.CosignPublicKey = cosignPublicKey lint.ZarfSchema = zarfSchema From 68a386933f0ddfb9eeb1ba8384ffb8b0d76667dd Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 7 Jun 2024 17:26:31 +0200 Subject: [PATCH 051/132] refactor: add context in packager (#2597) ## Description This change adds contexts to packager functions to enable them to be cancelled. ## Related Issue Relates to #2594 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/dev.go | 8 ++++---- src/cmd/initialize.go | 11 ++++++----- src/cmd/package.go | 16 ++++++++-------- src/cmd/tools/zarf.go | 4 ++-- src/pkg/packager/composer/list.go | 10 +++++----- src/pkg/packager/composer/list_test.go | 5 +++-- src/pkg/packager/composer/oci.go | 10 ++++------ src/pkg/packager/create.go | 9 +++++---- src/pkg/packager/creator/compose.go | 8 +++++--- src/pkg/packager/creator/compose_test.go | 3 ++- src/pkg/packager/creator/creator.go | 8 +++++--- src/pkg/packager/creator/differential.go | 5 +++-- src/pkg/packager/creator/normal.go | 14 +++++--------- src/pkg/packager/creator/normal_test.go | 3 ++- src/pkg/packager/creator/skeleton.go | 9 +++++---- src/pkg/packager/creator/skeleton_test.go | 3 ++- src/pkg/packager/deploy.go | 4 ++-- src/pkg/packager/dev.go | 4 ++-- src/pkg/packager/inspect.go | 5 +++-- src/pkg/packager/lint/lint.go | 9 +++++---- src/pkg/packager/lint/lint_test.go | 3 ++- src/pkg/packager/mirror.go | 2 +- src/pkg/packager/prepare.go | 5 +++-- src/pkg/packager/publish.go | 12 +++++------- src/pkg/packager/pull.go | 5 +++-- src/pkg/packager/remove.go | 2 +- src/pkg/packager/sources/cluster.go | 8 +++----- src/pkg/packager/sources/new.go | 7 ++++--- src/pkg/packager/sources/new_test.go | 7 ++++--- src/pkg/packager/sources/oci.go | 10 +++------- src/pkg/packager/sources/split.go | 15 ++++++++------- src/pkg/packager/sources/tarball.go | 7 ++++--- src/pkg/packager/sources/url.go | 15 ++++++++------- 33 files changed, 127 insertions(+), 119 deletions(-) diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 1266232cde..adc5420a15 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -204,7 +204,7 @@ var devFindImagesCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevFindImagesShort, Long: lang.CmdDevFindImagesLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() @@ -216,7 +216,7 @@ var devFindImagesCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if _, err := pkgClient.FindImages(); err != nil { + if _, err := pkgClient.FindImages(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdDevFindImagesErr, err.Error()) } }, @@ -249,12 +249,12 @@ var devLintCmd = &cobra.Command{ Aliases: []string{"l"}, Short: lang.CmdDevLintShort, Long: lang.CmdDevLintLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - validator, err := lint.Validate(pkgConfig.CreateOpts) + validator, err := lint.Validate(cmd.Context(), pkgConfig.CreateOpts) if err != nil { message.Fatal(err, err.Error()) } diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index 7673be27c4..75a8a049e0 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "errors" "fmt" "os" @@ -49,7 +50,7 @@ var initCmd = &cobra.Command{ // Try to use an init-package in the executable directory if none exist in current working directory var err error - if pkgConfig.PkgOpts.PackageSource, err = findInitPackage(initPackageName); err != nil { + if pkgConfig.PkgOpts.PackageSource, err = findInitPackage(cmd.Context(), initPackageName); err != nil { message.Fatal(err, err.Error()) } @@ -74,7 +75,7 @@ var initCmd = &cobra.Command{ }, } -func findInitPackage(initPackageName string) (string, error) { +func findInitPackage(ctx context.Context, initPackageName string) (string, error) { // First, look for the init package in the current working directory if !helpers.InvalidPath(initPackageName) { return initPackageName, nil @@ -103,7 +104,7 @@ func findInitPackage(initPackageName string) (string, error) { } // Finally, if the init-package doesn't exist in the cache directory, suggest downloading it - downloadCacheTarget, err := downloadInitPackage(config.GetAbsCachePath()) + downloadCacheTarget, err := downloadInitPackage(ctx, config.GetAbsCachePath()) if err != nil { if errors.Is(err, lang.ErrInitNotFound) { message.Fatal(err, err.Error()) @@ -114,7 +115,7 @@ func findInitPackage(initPackageName string) (string, error) { return downloadCacheTarget, nil } -func downloadInitPackage(cacheDirectory string) (string, error) { +func downloadInitPackage(ctx context.Context, cacheDirectory string) (string, error) { if config.CommonOptions.Confirm { return "", lang.ErrInitNotFound } @@ -144,7 +145,7 @@ func downloadInitPackage(cacheDirectory string) (string, error) { return "", err } source := &sources.OCISource{Remote: remote} - return source.Collect(cacheDirectory) + return source.Collect(ctx, cacheDirectory) } // Otherwise, exit and tell the user to manually download the init-package return "", errors.New(lang.CmdInitPullErrManual) diff --git a/src/cmd/package.go b/src/cmd/package.go index c6ac07e698..b94fccee23 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -39,7 +39,7 @@ var packageCreateCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdPackageCreateShort, Long: lang.CmdPackageCreateLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) var isCleanPathRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-\/\.\~\\:]+$`) @@ -55,7 +55,7 @@ var packageCreateCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if err := pkgClient.Create(); err != nil { + if err := pkgClient.Create(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdPackageCreateErr, err.Error()) } }, @@ -112,7 +112,7 @@ var packageInspectCmd = &cobra.Command{ Short: lang.CmdPackageInspectShort, Long: lang.CmdPackageInspectLong, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) src := identifyAndFallbackToClusterSource() @@ -120,7 +120,7 @@ var packageInspectCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - if err := pkgClient.Inspect(); err != nil { + if err := pkgClient.Inspect(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdPackageInspectErr, err.Error()) } }, @@ -190,7 +190,7 @@ var packagePublishCmd = &cobra.Command{ Short: lang.CmdPackagePublishShort, Example: lang.CmdPackagePublishExample, Args: cobra.ExactArgs(2), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = args[0] if !helpers.IsOCIURL(args[1]) { @@ -216,7 +216,7 @@ var packagePublishCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if err := pkgClient.Publish(); err != nil { + if err := pkgClient.Publish(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdPackagePublishErr, err.Error()) } }, @@ -227,13 +227,13 @@ var packagePullCmd = &cobra.Command{ Short: lang.CmdPackagePullShort, Example: lang.CmdPackagePullExample, Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = args[0] pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if err := pkgClient.Pull(); err != nil { + if err := pkgClient.Pull(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdPackagePullErr, err.Error()) } }, diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 243bc74027..843fcb9ae3 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -186,7 +186,7 @@ var clearCacheCmd = &cobra.Command{ var downloadInitCmd = &cobra.Command{ Use: "download-init", Short: lang.CmdToolsDownloadInitShort, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { url := zoci.GetInitPackageURL(config.CLIVersion) remote, err := zoci.NewRemote(url, oci.PlatformForArch(config.GetArch())) @@ -196,7 +196,7 @@ var downloadInitCmd = &cobra.Command{ source := &sources.OCISource{Remote: remote} - _, err = source.Collect(outputDirectory) + _, err = source.Collect(cmd.Context(), outputDirectory) if err != nil { message.Fatalf(err, lang.CmdToolsDownloadInitErr, err.Error()) } diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go index 5c2d378d14..69aa5fdb8e 100644 --- a/src/pkg/packager/composer/list.go +++ b/src/pkg/packager/composer/list.go @@ -120,7 +120,7 @@ func (ic *ImportChain) append(c types.ZarfComponent, index int, originalPackageN // NewImportChain creates a new import chain from a component // Returning the chain on error so we can have additional information to use during lint -func NewImportChain(head types.ZarfComponent, index int, originalPackageName, arch, flavor string) (*ImportChain, error) { +func NewImportChain(ctx context.Context, head types.ZarfComponent, index int, originalPackageName, arch, flavor string) (*ImportChain, error) { ic := &ImportChain{} if arch == "" { return ic, fmt.Errorf("cannot build import chain: architecture must be provided") @@ -179,11 +179,11 @@ func NewImportChain(head types.ZarfComponent, index int, originalPackageName, ar } } else if isRemote { importURL = node.Import.URL - remote, err := ic.getRemote(node.Import.URL) + remote, err := ic.getRemote(ctx, node.Import.URL) if err != nil { return ic, err } - pkg, err = remote.FetchZarfYAML(context.TODO()) + pkg, err = remote.FetchZarfYAML(ctx) if err != nil { return ic, err } @@ -274,7 +274,7 @@ func (ic *ImportChain) Migrate(build types.ZarfBuildData) (warnings []string) { // Compose merges the import chain into a single component // fixing paths, overriding metadata, etc -func (ic *ImportChain) Compose() (composed *types.ZarfComponent, err error) { +func (ic *ImportChain) Compose(ctx context.Context) (composed *types.ZarfComponent, err error) { composed = &ic.tail.ZarfComponent if ic.tail.prev == nil { @@ -282,7 +282,7 @@ func (ic *ImportChain) Compose() (composed *types.ZarfComponent, err error) { return composed, nil } - if err := ic.fetchOCISkeleton(); err != nil { + if err := ic.fetchOCISkeleton(ctx); err != nil { return nil, err } diff --git a/src/pkg/packager/composer/list_test.go b/src/pkg/packager/composer/list_test.go index 622647357c..0f96e0beb0 100644 --- a/src/pkg/packager/composer/list_test.go +++ b/src/pkg/packager/composer/list_test.go @@ -5,6 +5,7 @@ package composer import ( + "context" "fmt" "os" "path/filepath" @@ -48,7 +49,7 @@ func TestNewImportChain(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - _, err := NewImportChain(tt.head, 0, testPackageName, tt.arch, tt.flavor) + _, err := NewImportChain(context.Background(), tt.head, 0, testPackageName, tt.arch, tt.flavor) require.ErrorContains(t, err, tt.expectedErr) }) } @@ -239,7 +240,7 @@ func TestCompose(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - composed, err := tt.ic.Compose() + composed, err := tt.ic.Compose(context.Background()) require.NoError(t, err) require.EqualValues(t, &tt.expectedComposed, composed) }) diff --git a/src/pkg/packager/composer/oci.go b/src/pkg/packager/composer/oci.go index baa4d0fc81..f4f02fbaf3 100644 --- a/src/pkg/packager/composer/oci.go +++ b/src/pkg/packager/composer/oci.go @@ -23,7 +23,7 @@ import ( ocistore "oras.land/oras-go/v2/content/oci" ) -func (ic *ImportChain) getRemote(url string) (*zoci.Remote, error) { +func (ic *ImportChain) getRemote(ctx context.Context, url string) (*zoci.Remote, error) { if ic.remote != nil { return ic.remote, nil } @@ -32,7 +32,7 @@ func (ic *ImportChain) getRemote(url string) (*zoci.Remote, error) { if err != nil { return nil, err } - _, err = ic.remote.ResolveRoot(context.TODO()) + _, err = ic.remote.ResolveRoot(ctx) if err != nil { return nil, fmt.Errorf("published skeleton package for %q does not exist: %w", url, err) } @@ -45,17 +45,16 @@ func (ic *ImportChain) ContainsOCIImport() bool { return ic.tail.prev != nil && ic.tail.prev.Import.URL != "" } -func (ic *ImportChain) fetchOCISkeleton() error { +func (ic *ImportChain) fetchOCISkeleton(ctx context.Context) error { if !ic.ContainsOCIImport() { return nil } node := ic.tail.prev - remote, err := ic.getRemote(node.Import.URL) + remote, err := ic.getRemote(ctx, node.Import.URL) if err != nil { return err } - ctx := context.TODO() manifest, err := remote.FetchRoot(ctx) if err != nil { return err @@ -91,7 +90,6 @@ func (ic *ImportChain) fetchOCISkeleton() error { return err } - ctx := context.TODO() // ensure the tarball is in the cache exists, err := store.Exists(ctx, componentDesc) if err != nil { diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index 7613c2f6f4..ab46bdaa30 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" @@ -16,7 +17,7 @@ import ( ) // Create generates a Zarf package tarball for a given PackageConfig and optional base directory. -func (p *Packager) Create() (err error) { +func (p *Packager) Create(ctx context.Context) (err error) { cwd, err := os.Getwd() if err != nil { return err @@ -34,7 +35,7 @@ func (p *Packager) Create() (err error) { return err } - p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(p.layout) + p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } @@ -43,7 +44,7 @@ func (p *Packager) Create() (err error) { return fmt.Errorf("package creation canceled") } - if err := pc.Assemble(p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil { + if err := pc.Assemble(ctx, p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil { return err } @@ -52,5 +53,5 @@ func (p *Packager) Create() (err error) { return err } - return pc.Output(p.layout, &p.cfg.Pkg) + return pc.Output(ctx, p.layout, &p.cfg.Pkg) } diff --git a/src/pkg/packager/creator/compose.go b/src/pkg/packager/creator/compose.go index dd2e429731..6cd5277388 100644 --- a/src/pkg/packager/creator/compose.go +++ b/src/pkg/packager/creator/compose.go @@ -5,13 +5,15 @@ package creator import ( + "context" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/composer" "github.com/defenseunicorns/zarf/src/types" ) // ComposeComponents composes components and their dependencies into a single Zarf package using an import chain. -func ComposeComponents(pkg types.ZarfPackage, flavor string) (types.ZarfPackage, []string, error) { +func ComposeComponents(ctx context.Context, pkg types.ZarfPackage, flavor string) (types.ZarfPackage, []string, error) { components := []types.ZarfComponent{} warnings := []string{} @@ -31,7 +33,7 @@ func ComposeComponents(pkg types.ZarfPackage, flavor string) (types.ZarfPackage, component.Only.Flavor = "" // build the import chain - chain, err := composer.NewImportChain(component, i, pkg.Metadata.Name, arch, flavor) + chain, err := composer.NewImportChain(ctx, component, i, pkg.Metadata.Name, arch, flavor) if err != nil { return types.ZarfPackage{}, nil, err } @@ -42,7 +44,7 @@ func ComposeComponents(pkg types.ZarfPackage, flavor string) (types.ZarfPackage, warnings = append(warnings, warning...) // get the composed component - composed, err := chain.Compose() + composed, err := chain.Compose(ctx) if err != nil { return types.ZarfPackage{}, nil, err } diff --git a/src/pkg/packager/creator/compose_test.go b/src/pkg/packager/creator/compose_test.go index 7d1310bf3e..35e298f553 100644 --- a/src/pkg/packager/creator/compose_test.go +++ b/src/pkg/packager/creator/compose_test.go @@ -5,6 +5,7 @@ package creator import ( + "context" "testing" "github.com/defenseunicorns/zarf/src/types" @@ -160,7 +161,7 @@ func TestComposeComponents(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - pkg, _, err := ComposeComponents(tt.pkg, tt.flavor) + pkg, _, err := ComposeComponents(context.Background(), tt.pkg, tt.flavor) if tt.expectedErr == "" { require.NoError(t, err) diff --git a/src/pkg/packager/creator/creator.go b/src/pkg/packager/creator/creator.go index 5e34fd46c1..aae86172c6 100644 --- a/src/pkg/packager/creator/creator.go +++ b/src/pkg/packager/creator/creator.go @@ -5,13 +5,15 @@ package creator import ( + "context" + "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/types" ) // Creator is an interface for creating Zarf packages. type Creator interface { - LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) - Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error - Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) error + LoadPackageDefinition(ctx context.Context, src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) + Assemble(ctx context.Context, dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error + Output(ctx context.Context, dst *layout.PackagePaths, pkg *types.ZarfPackage) error } diff --git a/src/pkg/packager/creator/differential.go b/src/pkg/packager/creator/differential.go index ee373836d0..d2c3c80480 100644 --- a/src/pkg/packager/creator/differential.go +++ b/src/pkg/packager/creator/differential.go @@ -5,6 +5,7 @@ package creator import ( + "context" "os" "github.com/defenseunicorns/zarf/src/config" @@ -15,7 +16,7 @@ import ( ) // loadDifferentialData sets any images and repos from the existing reference package in the DifferentialData and returns it. -func loadDifferentialData(diffPkgPath string) (diffData *types.DifferentialData, err error) { +func loadDifferentialData(ctx context.Context, diffPkgPath string) (diffData *types.DifferentialData, err error) { tmpdir, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { return nil, err @@ -31,7 +32,7 @@ func loadDifferentialData(diffPkgPath string) (diffData *types.DifferentialData, return nil, err } - diffPkg, _, err := src.LoadPackageMetadata(diffLayout, false, false) + diffPkg, _, err := src.LoadPackageMetadata(ctx, diffLayout, false, false) if err != nil { return nil, err } diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 96f219e584..8af841f060 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -61,7 +61,7 @@ func NewPackageCreator(createOpts types.ZarfCreateOptions, cwd string) *PackageC } // LoadPackageDefinition loads and configures a zarf.yaml file during package create. -func (pc *PackageCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { +func (pc *PackageCreator) LoadPackageDefinition(ctx context.Context, src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { pkg, warnings, err = src.ReadZarfYAML() if err != nil { return types.ZarfPackage{}, nil, err @@ -70,7 +70,7 @@ func (pc *PackageCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg t pkg.Metadata.Architecture = config.GetArch(pkg.Metadata.Architecture) // Compose components into a single zarf.yaml file - pkg, composeWarnings, err := ComposeComponents(pkg, pc.createOpts.Flavor) + pkg, composeWarnings, err := ComposeComponents(ctx, pkg, pc.createOpts.Flavor) if err != nil { return types.ZarfPackage{}, nil, err } @@ -95,7 +95,7 @@ func (pc *PackageCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg t if pc.createOpts.DifferentialPackagePath != "" { pkg.Build.Differential = true - diffData, err := loadDifferentialData(pc.createOpts.DifferentialPackagePath) + diffData, err := loadDifferentialData(ctx, pc.createOpts.DifferentialPackagePath) if err != nil { return types.ZarfPackage{}, nil, err } @@ -127,7 +127,7 @@ func (pc *PackageCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg t } // Assemble assembles all of the package assets into Zarf's tmp directory layout. -func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error { +func (pc *PackageCreator) Assemble(ctx context.Context, dst *layout.PackagePaths, components []types.ZarfComponent, arch string) error { var imageList []transform.Image skipSBOMFlagUsed := pc.createOpts.SkipSBOM @@ -184,8 +184,6 @@ func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types. dst.AddImages() - ctx := context.TODO() - pullCfg := images.PullConfig{ DestinationDirectory: dst.Images.Base, ImageList: imageList, @@ -238,7 +236,7 @@ func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types. // // - writes the Zarf package as a tarball to a local directory, // or an OCI registry based on the --output flag -func (pc *PackageCreator) Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) { +func (pc *PackageCreator) Output(ctx context.Context, dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) { // Process the component directories into compressed tarballs // NOTE: This is purposefully being done after the SBOM cataloging for _, component := range pkg.Components { @@ -278,8 +276,6 @@ func (pc *PackageCreator) Output(dst *layout.PackagePaths, pkg *types.ZarfPackag if err != nil { return err } - - ctx := context.TODO() err = remote.PublishPackage(ctx, pkg, dst, config.CommonOptions.OCIConcurrency) if err != nil { return fmt.Errorf("unable to publish package: %w", err) diff --git a/src/pkg/packager/creator/normal_test.go b/src/pkg/packager/creator/normal_test.go index 95998ba1ca..32b1a2ce4b 100644 --- a/src/pkg/packager/creator/normal_test.go +++ b/src/pkg/packager/creator/normal_test.go @@ -5,6 +5,7 @@ package creator import ( + "context" "path/filepath" "testing" @@ -86,7 +87,7 @@ func TestLoadPackageDefinition(t *testing.T) { src := layout.New(filepath.Join("testdata", tt.testDir)) pc := NewPackageCreator(types.ZarfCreateOptions{}, "") - pkg, _, err := pc.LoadPackageDefinition(src) + pkg, _, err := pc.LoadPackageDefinition(context.Background(), src) if tt.expectedErr == "" { require.NoError(t, err) diff --git a/src/pkg/packager/creator/skeleton.go b/src/pkg/packager/creator/skeleton.go index 43866f8484..e0e86126b4 100644 --- a/src/pkg/packager/creator/skeleton.go +++ b/src/pkg/packager/creator/skeleton.go @@ -5,6 +5,7 @@ package creator import ( + "context" "fmt" "os" "path/filepath" @@ -42,7 +43,7 @@ func NewSkeletonCreator(createOpts types.ZarfCreateOptions, publishOpts types.Za } // LoadPackageDefinition loads and configure a zarf.yaml file when creating and publishing a skeleton package. -func (sc *SkeletonCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { +func (sc *SkeletonCreator) LoadPackageDefinition(ctx context.Context, src *layout.PackagePaths) (pkg types.ZarfPackage, warnings []string, err error) { pkg, warnings, err = src.ReadZarfYAML() if err != nil { return types.ZarfPackage{}, nil, err @@ -51,7 +52,7 @@ func (sc *SkeletonCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg pkg.Metadata.Architecture = config.GetArch() // Compose components into a single zarf.yaml file - pkg, composeWarnings, err := ComposeComponents(pkg, sc.createOpts.Flavor) + pkg, composeWarnings, err := ComposeComponents(ctx, pkg, sc.createOpts.Flavor) if err != nil { return types.ZarfPackage{}, nil, err } @@ -79,7 +80,7 @@ func (sc *SkeletonCreator) LoadPackageDefinition(src *layout.PackagePaths) (pkg // Assemble updates all components of the loaded Zarf package with necessary modifications for package assembly. // // It processes each component to ensure correct structure and resource locations. -func (sc *SkeletonCreator) Assemble(dst *layout.PackagePaths, components []types.ZarfComponent, _ string) error { +func (sc *SkeletonCreator) Assemble(_ context.Context, dst *layout.PackagePaths, components []types.ZarfComponent, _ string) error { for _, component := range components { c, err := sc.addComponent(component, dst) if err != nil { @@ -100,7 +101,7 @@ func (sc *SkeletonCreator) Assemble(dst *layout.PackagePaths, components []types // - writes the loaded zarf.yaml to disk // // - signs the package -func (sc *SkeletonCreator) Output(dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) { +func (sc *SkeletonCreator) Output(_ context.Context, dst *layout.PackagePaths, pkg *types.ZarfPackage) (err error) { for _, component := range pkg.Components { if err := dst.Components.Archive(component, false); err != nil { return err diff --git a/src/pkg/packager/creator/skeleton_test.go b/src/pkg/packager/creator/skeleton_test.go index 1fed6f985d..ca2e9cd352 100644 --- a/src/pkg/packager/creator/skeleton_test.go +++ b/src/pkg/packager/creator/skeleton_test.go @@ -5,6 +5,7 @@ package creator import ( + "context" "path/filepath" "testing" @@ -40,7 +41,7 @@ func TestSkeletonLoadPackageDefinition(t *testing.T) { src := layout.New(filepath.Join("testdata", tt.testDir)) sc := NewSkeletonCreator(types.ZarfCreateOptions{}, types.ZarfPublishOptions{}) - pkg, _, err := sc.LoadPackageDefinition(src) + pkg, _, err := sc.LoadPackageDefinition(context.Background(), src) if tt.expectedErr == "" { require.NoError(t, err) diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index b6e26e7693..39fb2eaa88 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -60,12 +60,12 @@ func (p *Packager) Deploy(ctx context.Context) (err error) { if isInteractive { filter := filters.Empty() - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(p.layout, filter, true) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } } else { - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(p.layout, deployFilter, true) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, deployFilter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index a18d1dec0f..4bb07fcb0c 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -39,7 +39,7 @@ func (p *Packager) DevDeploy(ctx context.Context) error { return err } - p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(p.layout) + p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } @@ -69,7 +69,7 @@ func (p *Packager) DevDeploy(ctx context.Context) error { } } - if err := pc.Assemble(p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil { + if err := pc.Assemble(ctx, p.layout, p.cfg.Pkg.Components, p.cfg.Pkg.Metadata.Architecture); err != nil { return err } diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go index 5500be26da..d7e4be3e18 100644 --- a/src/pkg/packager/inspect.go +++ b/src/pkg/packager/inspect.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" @@ -14,10 +15,10 @@ import ( ) // Inspect list the contents of a package. -func (p *Packager) Inspect() (err error) { +func (p *Packager) Inspect(ctx context.Context) (err error) { wantSBOM := p.cfg.InspectOpts.ViewSBOM || p.cfg.InspectOpts.SBOMOutputDir != "" - p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(p.layout, wantSBOM, true) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(ctx, p.layout, wantSBOM, true) if err != nil { return err } diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go index 1a58ef857f..b835e0ad58 100644 --- a/src/pkg/packager/lint/lint.go +++ b/src/pkg/packager/lint/lint.go @@ -5,6 +5,7 @@ package lint import ( + "context" "embed" "fmt" "os" @@ -33,7 +34,7 @@ func getSchemaFile() ([]byte, error) { // Validate validates a zarf file against the zarf schema, returns *validator with warnings or errors if they exist // along with an error if the validation itself failed -func Validate(createOpts types.ZarfCreateOptions) (*Validator, error) { +func Validate(ctx context.Context, createOpts types.ZarfCreateOptions) (*Validator, error) { validator := Validator{} var err error @@ -51,7 +52,7 @@ func Validate(createOpts types.ZarfCreateOptions) (*Validator, error) { validator.baseDir = createOpts.BaseDir - lintComponents(&validator, &createOpts) + lintComponents(ctx, &validator, &createOpts) if validator.jsonSchema, err = getSchemaFile(); err != nil { return nil, err @@ -64,7 +65,7 @@ func Validate(createOpts types.ZarfCreateOptions) (*Validator, error) { return &validator, nil } -func lintComponents(validator *Validator, createOpts *types.ZarfCreateOptions) { +func lintComponents(ctx context.Context, validator *Validator, createOpts *types.ZarfCreateOptions) { for i, component := range validator.typedZarfPackage.Components { arch := config.GetArch(validator.typedZarfPackage.Metadata.Architecture) @@ -72,7 +73,7 @@ func lintComponents(validator *Validator, createOpts *types.ZarfCreateOptions) { continue } - chain, err := composer.NewImportChain(component, i, validator.typedZarfPackage.Metadata.Name, arch, createOpts.Flavor) + chain, err := composer.NewImportChain(ctx, component, i, validator.typedZarfPackage.Metadata.Name, arch, createOpts.Flavor) baseComponent := chain.Head() var badImportYqPath string diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go index eba52b7743..b259fd89ed 100644 --- a/src/pkg/packager/lint/lint_test.go +++ b/src/pkg/packager/lint/lint_test.go @@ -5,6 +5,7 @@ package lint import ( + "context" "errors" "fmt" "os" @@ -182,7 +183,7 @@ func TestValidateSchema(t *testing.T) { Metadata: types.ZarfMetadata{Name: "test-zarf-package"}}} createOpts := types.ZarfCreateOptions{Flavor: "", BaseDir: "."} - lintComponents(&validator, &createOpts) + lintComponents(context.Background(), &validator, &createOpts) // Require.contains rather than equals since the error message changes from linux to windows require.Contains(t, validator.findings[0].description, fmt.Sprintf("open %s", filepath.Join("fake-path", "zarf.yaml"))) require.Equal(t, ".components.[0].import.path", validator.findings[0].yqPath) diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go index 658967560d..f19536443c 100644 --- a/src/pkg/packager/mirror.go +++ b/src/pkg/packager/mirror.go @@ -23,7 +23,7 @@ func (p *Packager) Mirror(ctx context.Context) (err error) { filters.BySelectState(p.cfg.PkgOpts.OptionalComponents), ) - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(p.layout, filter, true) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go index 276cdcc4db..4bd9c88e54 100644 --- a/src/pkg/packager/prepare.go +++ b/src/pkg/packager/prepare.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" "path/filepath" @@ -37,7 +38,7 @@ import ( type imageMap map[string]bool // FindImages iterates over a Zarf.yaml and attempts to parse any images. -func (p *Packager) FindImages() (imgMap map[string][]string, err error) { +func (p *Packager) FindImages(ctx context.Context) (imgMap map[string][]string, err error) { cwd, err := os.Getwd() if err != nil { return nil, err @@ -59,7 +60,7 @@ func (p *Packager) FindImages() (imgMap map[string][]string, err error) { return nil, err } - p.cfg.Pkg, p.warnings, err = c.LoadPackageDefinition(p.layout) + p.cfg.Pkg, p.warnings, err = c.LoadPackageDefinition(ctx, p.layout) if err != nil { return nil, err } diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go index c06ecafb34..e889ea89b6 100644 --- a/src/pkg/packager/publish.go +++ b/src/pkg/packager/publish.go @@ -25,10 +25,9 @@ import ( ) // Publish publishes the package to a registry -func (p *Packager) Publish() (err error) { +func (p *Packager) Publish(ctx context.Context) (err error) { _, isOCISource := p.source.(*sources.OCISource) if isOCISource && p.cfg.PublishOpts.SigningKeyPath == "" { - ctx := context.TODO() // oci --> oci is a special case, where we will use oci.CopyPackage so that we can transfer the package // w/o layers touching the filesystem srcRemote := p.source.(*sources.OCISource).Remote @@ -59,21 +58,21 @@ func (p *Packager) Publish() (err error) { return err } - p.cfg.Pkg, p.warnings, err = sc.LoadPackageDefinition(p.layout) + p.cfg.Pkg, p.warnings, err = sc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } - if err := sc.Assemble(p.layout, p.cfg.Pkg.Components, ""); err != nil { + if err := sc.Assemble(ctx, p.layout, p.cfg.Pkg.Components, ""); err != nil { return err } - if err := sc.Output(p.layout, &p.cfg.Pkg); err != nil { + if err := sc.Output(ctx, p.layout, &p.cfg.Pkg); err != nil { return err } } else { filter := filters.Empty() - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(p.layout, filter, false) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, false) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } @@ -103,7 +102,6 @@ func (p *Packager) Publish() (err error) { message.HeaderInfof("📦 PACKAGE PUBLISH %s:%s", p.cfg.Pkg.Metadata.Name, ref) // Publish the package/skeleton to the registry - ctx := context.TODO() if err := remote.PublishPackage(ctx, &p.cfg.Pkg, p.layout, config.CommonOptions.OCIConcurrency); err != nil { return err } diff --git a/src/pkg/packager/pull.go b/src/pkg/packager/pull.go index 5d915dabb1..7c00d36c9c 100644 --- a/src/pkg/packager/pull.go +++ b/src/pkg/packager/pull.go @@ -5,16 +5,17 @@ package packager import ( + "context" "fmt" ) // Pull pulls a Zarf package and saves it as a compressed tarball. -func (p *Packager) Pull() (err error) { +func (p *Packager) Pull(ctx context.Context) (err error) { if p.cfg.PkgOpts.OptionalComponents != "" { return fmt.Errorf("pull does not support optional components") } - _, err = p.source.Collect(p.cfg.PullOpts.OutputDirectory) + _, err = p.source.Collect(ctx, p.cfg.PullOpts.OutputDirectory) if err != nil { return err } diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index 535e7dedc4..5169e53cbf 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -39,7 +39,7 @@ func (p *Packager) Remove(ctx context.Context) (err error) { // we do not want to allow removal of signed packages without a signature if there are remove actions // as this is arbitrary code execution from an untrusted source - p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(p.layout, false, false) + p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(ctx, p.layout, false, false) if err != nil { return err } diff --git a/src/pkg/packager/sources/cluster.go b/src/pkg/packager/sources/cluster.go index ad14136afd..5936c5d6f0 100644 --- a/src/pkg/packager/sources/cluster.go +++ b/src/pkg/packager/sources/cluster.go @@ -46,21 +46,19 @@ type ClusterSource struct { // LoadPackage loads a package from a cluster. // // This is not implemented. -func (s *ClusterSource) LoadPackage(_ *layout.PackagePaths, _ filters.ComponentFilterStrategy, _ bool) (types.ZarfPackage, []string, error) { +func (s *ClusterSource) LoadPackage(_ context.Context, _ *layout.PackagePaths, _ filters.ComponentFilterStrategy, _ bool) (types.ZarfPackage, []string, error) { return types.ZarfPackage{}, nil, fmt.Errorf("not implemented") } // Collect collects a package from a cluster. // // This is not implemented. -func (s *ClusterSource) Collect(_ string) (string, error) { +func (s *ClusterSource) Collect(_ context.Context, _ string) (string, error) { return "", fmt.Errorf("not implemented") } // LoadPackageMetadata loads package metadata from a cluster. -func (s *ClusterSource) LoadPackageMetadata(dst *layout.PackagePaths, _ bool, _ bool) (types.ZarfPackage, []string, error) { - ctx := context.Background() - +func (s *ClusterSource) LoadPackageMetadata(ctx context.Context, dst *layout.PackagePaths, _ bool, _ bool) (types.ZarfPackage, []string, error) { dpkg, err := s.GetDeployedPackage(ctx, s.PackageSource) if err != nil { return types.ZarfPackage{}, nil, err diff --git a/src/pkg/packager/sources/new.go b/src/pkg/packager/sources/new.go index 192bd39f88..c9f1f46a14 100644 --- a/src/pkg/packager/sources/new.go +++ b/src/pkg/packager/sources/new.go @@ -5,6 +5,7 @@ package sources import ( + "context" "fmt" "net/url" "strings" @@ -30,13 +31,13 @@ import ( // `sources.ValidatePackageSignature` and `sources.ValidatePackageIntegrity` can be leveraged for this purpose. type PackageSource interface { // LoadPackage loads a package from a source. - LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) + LoadPackage(ctx context.Context, dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) // LoadPackageMetadata loads a package's metadata from a source. - LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) + LoadPackageMetadata(ctx context.Context, dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) // Collect relocates a package from its source to a tarball in a given destination directory. - Collect(destinationDirectory string) (tarball string, err error) + Collect(ctx context.Context, destinationDirectory string) (tarball string, err error) } // Identify returns the type of package source based on the provided package source string. diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index ee770f51aa..bad95a9051 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -5,6 +5,7 @@ package sources import ( + "context" "encoding/json" "fmt" "io" @@ -160,7 +161,7 @@ func TestPackageSource(t *testing.T) { require.NoError(t, err) packageDir := t.TempDir() pkgLayout := layout.New(packageDir) - pkg, warnings, err := ps.LoadPackage(pkgLayout, filters.Empty(), false) + pkg, warnings, err := ps.LoadPackage(context.Background(), pkgLayout, filters.Empty(), false) require.NoError(t, err) require.Empty(t, warnings) require.Equal(t, expectedPkg, pkg) @@ -169,7 +170,7 @@ func TestPackageSource(t *testing.T) { require.NoError(t, err) metadataDir := t.TempDir() metadataLayout := layout.New(metadataDir) - metadata, warnings, err := ps.LoadPackageMetadata(metadataLayout, true, false) + metadata, warnings, err := ps.LoadPackageMetadata(context.Background(), metadataLayout, true, false) require.NoError(t, err) require.Empty(t, warnings) require.Equal(t, expectedPkg, metadata) @@ -177,7 +178,7 @@ func TestPackageSource(t *testing.T) { ps, err = New(opts) require.NoError(t, err) collectDir := t.TempDir() - fp, err := ps.Collect(collectDir) + fp, err := ps.Collect(context.Background(), collectDir) require.NoError(t, err) require.Equal(t, filepath.Join(collectDir, filepath.Base(tt.src)), fp) }) diff --git a/src/pkg/packager/sources/oci.go b/src/pkg/packager/sources/oci.go index 83735a9503..84ba85ebaf 100644 --- a/src/pkg/packager/sources/oci.go +++ b/src/pkg/packager/sources/oci.go @@ -34,9 +34,7 @@ type OCISource struct { } // LoadPackage loads a package from an OCI registry. -func (s *OCISource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { - ctx := context.TODO() - +func (s *OCISource) LoadPackage(ctx context.Context, dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { message.Debugf("Loading package from %q", s.PackageSource) pkg, err = s.FetchZarfYAML(ctx) @@ -112,12 +110,11 @@ func (s *OCISource) LoadPackage(dst *layout.PackagePaths, filter filters.Compone } // LoadPackageMetadata loads a package's metadata from an OCI registry. -func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { +func (s *OCISource) LoadPackageMetadata(ctx context.Context, dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { toPull := zoci.PackageAlwaysPull if wantSBOM { toPull = append(toPull, layout.SBOMTar) } - ctx := context.TODO() layersFetched, err := s.PullPaths(ctx, dst.Base, toPull) if err != nil { return pkg, nil, err @@ -165,13 +162,12 @@ func (s *OCISource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, } // Collect pulls a package from an OCI registry and writes it to a tarball. -func (s *OCISource) Collect(dir string) (string, error) { +func (s *OCISource) Collect(ctx context.Context, dir string) (string, error) { tmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { return "", err } defer os.RemoveAll(tmp) - ctx := context.TODO() fetched, err := s.PullPackage(ctx, tmp, config.CommonOptions.OCIConcurrency) if err != nil { return "", err diff --git a/src/pkg/packager/sources/split.go b/src/pkg/packager/sources/split.go index d635159002..1594a51c20 100644 --- a/src/pkg/packager/sources/split.go +++ b/src/pkg/packager/sources/split.go @@ -5,6 +5,7 @@ package sources import ( + "context" "encoding/json" "fmt" "io" @@ -31,7 +32,7 @@ type SplitTarballSource struct { } // Collect turns a split tarball into a full tarball. -func (s *SplitTarballSource) Collect(dir string) (string, error) { +func (s *SplitTarballSource) Collect(_ context.Context, dir string) (string, error) { pattern := strings.Replace(s.PackageSource, ".part000", ".part*", 1) fileList, err := filepath.Glob(pattern) if err != nil { @@ -109,8 +110,8 @@ func (s *SplitTarballSource) Collect(dir string) (string, error) { } // LoadPackage loads a package from a split tarball. -func (s *SplitTarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { - tb, err := s.Collect(filepath.Dir(s.PackageSource)) +func (s *SplitTarballSource) LoadPackage(ctx context.Context, dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { + tb, err := s.Collect(ctx, filepath.Dir(s.PackageSource)) if err != nil { return pkg, nil, err } @@ -123,12 +124,12 @@ func (s *SplitTarballSource) LoadPackage(dst *layout.PackagePaths, filter filter ts := &TarballSource{ s.ZarfPackageOptions, } - return ts.LoadPackage(dst, filter, unarchiveAll) + return ts.LoadPackage(ctx, dst, filter, unarchiveAll) } // LoadPackageMetadata loads a package's metadata from a split tarball. -func (s *SplitTarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { - tb, err := s.Collect(filepath.Dir(s.PackageSource)) +func (s *SplitTarballSource) LoadPackageMetadata(ctx context.Context, dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { + tb, err := s.Collect(ctx, filepath.Dir(s.PackageSource)) if err != nil { return pkg, nil, err } @@ -139,5 +140,5 @@ func (s *SplitTarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantS ts := &TarballSource{ s.ZarfPackageOptions, } - return ts.LoadPackageMetadata(dst, wantSBOM, skipValidation) + return ts.LoadPackageMetadata(ctx, dst, wantSBOM, skipValidation) } diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go index b6a70d4355..663a4bd31c 100644 --- a/src/pkg/packager/sources/tarball.go +++ b/src/pkg/packager/sources/tarball.go @@ -6,6 +6,7 @@ package sources import ( "archive/tar" + "context" "errors" "fmt" "io" @@ -32,7 +33,7 @@ type TarballSource struct { } // LoadPackage loads a package from a tarball. -func (s *TarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { +func (s *TarballSource) LoadPackage(_ context.Context, dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { spinner := message.NewProgressSpinner("Loading package from %q", s.PackageSource) defer spinner.Stop() @@ -137,7 +138,7 @@ func (s *TarballSource) LoadPackage(dst *layout.PackagePaths, filter filters.Com } // LoadPackageMetadata loads a package's metadata from a tarball. -func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { +func (s *TarballSource) LoadPackageMetadata(_ context.Context, dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { if s.Shasum != "" { if err := helpers.SHAsMatch(s.PackageSource, s.Shasum); err != nil { return pkg, nil, err @@ -202,7 +203,7 @@ func (s *TarballSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM b } // Collect for the TarballSource is essentially an `mv` -func (s *TarballSource) Collect(dir string) (string, error) { +func (s *TarballSource) Collect(_ context.Context, dir string) (string, error) { dst := filepath.Join(dir, filepath.Base(s.PackageSource)) err := os.Rename(s.PackageSource, dst) linkErr := &os.LinkError{} diff --git a/src/pkg/packager/sources/url.go b/src/pkg/packager/sources/url.go index 67b6ccd1d9..9fa3cfb8e0 100644 --- a/src/pkg/packager/sources/url.go +++ b/src/pkg/packager/sources/url.go @@ -5,6 +5,7 @@ package sources import ( + "context" "fmt" "os" "path/filepath" @@ -29,7 +30,7 @@ type URLSource struct { } // Collect downloads a package from the source URL. -func (s *URLSource) Collect(dir string) (string, error) { +func (s *URLSource) Collect(_ context.Context, dir string) (string, error) { if !config.CommonOptions.Insecure && s.Shasum == "" && !strings.HasPrefix(s.PackageSource, helpers.SGETURLPrefix) { return "", fmt.Errorf("remote package provided without a shasum, use --insecure to ignore, or provide one w/ --shasum") } @@ -50,14 +51,14 @@ func (s *URLSource) Collect(dir string) (string, error) { } // LoadPackage loads a package from an http, https or sget URL. -func (s *URLSource) LoadPackage(dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { +func (s *URLSource) LoadPackage(ctx context.Context, dst *layout.PackagePaths, filter filters.ComponentFilterStrategy, unarchiveAll bool) (pkg types.ZarfPackage, warnings []string, err error) { tmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { return pkg, nil, err } defer os.Remove(tmp) - dstTarball, err := s.Collect(tmp) + dstTarball, err := s.Collect(ctx, tmp) if err != nil { return pkg, nil, err } @@ -70,18 +71,18 @@ func (s *URLSource) LoadPackage(dst *layout.PackagePaths, filter filters.Compone s.ZarfPackageOptions, } - return ts.LoadPackage(dst, filter, unarchiveAll) + return ts.LoadPackage(ctx, dst, filter, unarchiveAll) } // LoadPackageMetadata loads a package's metadata from an http, https or sget URL. -func (s *URLSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { +func (s *URLSource) LoadPackageMetadata(ctx context.Context, dst *layout.PackagePaths, wantSBOM bool, skipValidation bool) (pkg types.ZarfPackage, warnings []string, err error) { tmp, err := utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { return pkg, nil, err } defer os.Remove(tmp) - dstTarball, err := s.Collect(tmp) + dstTarball, err := s.Collect(ctx, tmp) if err != nil { return pkg, nil, err } @@ -92,5 +93,5 @@ func (s *URLSource) LoadPackageMetadata(dst *layout.PackagePaths, wantSBOM bool, s.ZarfPackageOptions, } - return ts.LoadPackageMetadata(dst, wantSBOM, skipValidation) + return ts.LoadPackageMetadata(ctx, dst, wantSBOM, skipValidation) } From 42cbd4e2ed27819aed1bcc90d15351bbd6b4677b Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Fri, 7 Jun 2024 10:59:49 -0500 Subject: [PATCH 052/132] chore: update go version to 1.22.4 (#2595) ## Description Updates Go version to the latest stable version of `1.22` https://go.dev/dl/ --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cc839e69ef..5af4b55f94 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/defenseunicorns/zarf -go 1.21.8 +go 1.22.4 // TODO (@AABRO): Pending merge into github.com/gojsonschema/gojsonschema (https://github.com/gojsonschema/gojsonschema/pull/5) replace github.com/xeipuuv/gojsonschema => github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 From d62ce1fe8f6e0dcb09490c21856e2c3735d446a1 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 7 Jun 2024 18:56:59 +0200 Subject: [PATCH 053/132] fix: handle errors in version command (#2589) ## Description Fixes version command error handling. ## Related Issue N/A ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Lucas Rodriguez --- src/cmd/version.go | 48 ++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/src/cmd/version.go b/src/cmd/version.go index 1daa7af61f..589cab9929 100644 --- a/src/cmd/version.go +++ b/src/cmd/version.go @@ -6,17 +6,17 @@ package cmd import ( "encoding/json" + "errors" "fmt" "runtime" + "runtime/debug" "github.com/Masterminds/semver/v3" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" + goyaml "github.com/goccy/go-yaml" "github.com/spf13/cobra" - "runtime/debug" - - goyaml "github.com/goccy/go-yaml" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" ) var outputFormat string @@ -29,13 +29,18 @@ var versionCmd = &cobra.Command{ }, Short: lang.CmdVersionShort, Long: lang.CmdVersionLong, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { + if outputFormat == "" { + fmt.Println(config.CLIVersion) + return nil + } + output := make(map[string]interface{}) + output["version"] = config.CLIVersion buildInfo, ok := debug.ReadBuildInfo() - if !ok && outputFormat != "" { - fmt.Println("Failed to get build info") - return + if !ok { + return errors.New("failed to get build info") } depMap := map[string]string{} for _, dep := range buildInfo.Deps { @@ -50,28 +55,33 @@ var versionCmd = &cobra.Command{ buildMap := make(map[string]interface{}) buildMap["platform"] = runtime.GOOS + "/" + runtime.GOARCH buildMap["goVersion"] = runtime.Version() - ver, _ := semver.NewVersion(config.CLIVersion) + ver, err := semver.NewVersion(config.CLIVersion) + if err != nil && !errors.Is(err, semver.ErrInvalidSemVer) { + return fmt.Errorf("Could not parse CLI version %s: %w", config.CLIVersion, err) + } if ver != nil { buildMap["major"] = ver.Major() buildMap["minor"] = ver.Minor() buildMap["patch"] = ver.Patch() buildMap["prerelease"] = ver.Prerelease() } - - output["version"] = config.CLIVersion - output["build"] = buildMap switch outputFormat { case "yaml": - text, _ := goyaml.Marshal(output) - fmt.Println(string(text)) + b, err := goyaml.Marshal(output) + if err != nil { + return fmt.Errorf("could not marshal yaml output: %w", err) + } + fmt.Println(string(b)) case "json": - text, _ := json.Marshal(output) - fmt.Println(string(text)) - default: - fmt.Println(config.CLIVersion) + b, err := json.Marshal(output) + if err != nil { + return fmt.Errorf("could not marshal json output: %w", err) + } + fmt.Println(string(b)) } + return nil }, } From b2b504bfac9ff7ae264c70f2dff152963eb7cc8c Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:01:25 -0400 Subject: [PATCH 054/132] fix: cosign image pulls (#2599) Fixes #2591 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/images/pull.go | 8 ++++- src/internal/packager/images/pull_test.go | 39 +++++++++++++++++++++++ src/pkg/packager/creator/normal.go | 2 +- src/pkg/utils/image.go | 4 +-- 4 files changed, 49 insertions(+), 4 deletions(-) create mode 100644 src/internal/packager/images/pull_test.go diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go index 11cc686239..f7203cc684 100644 --- a/src/internal/packager/images/pull.go +++ b/src/internal/packager/images/pull.go @@ -165,7 +165,13 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er return fmt.Errorf("%s resolved to an index, please select a specific platform to use", refInfo.Reference) } - img = cache.Image(img, cache.NewFilesystemCache(cfg.CacheDirectory)) + cacheImg, err := utils.OnlyHasImageLayers(img) + if err != nil { + return err + } + if cacheImg { + img = cache.Image(img, cache.NewFilesystemCache(cfg.CacheDirectory)) + } manifest, err := img.Manifest() if err != nil { diff --git a/src/internal/packager/images/pull_test.go b/src/internal/packager/images/pull_test.go new file mode 100644 index 0000000000..4ee0ca5478 --- /dev/null +++ b/src/internal/packager/images/pull_test.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package images provides functions for building and pushing images. +package images + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/stretchr/testify/require" +) + +func TestPull(t *testing.T) { + t.Run("pulling a cosign image is successful and doesn't add anything to the cache", func(t *testing.T) { + ref, err := transform.ParseImageRef("ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.sig") + require.NoError(t, err) + destDir := t.TempDir() + cacheDir := t.TempDir() + pullConfig := PullConfig{ + DestinationDirectory: destDir, + CacheDirectory: cacheDir, + ImageList: []transform.Image{ + ref, + }, + } + + _, err = Pull(context.Background(), pullConfig) + require.NoError(t, err) + require.FileExists(t, filepath.Join(destDir, "blobs/sha256/3e84ea487b4c52a3299cf2996f70e7e1721236a0998da33a0e30107108486b3e")) + + dir, err := os.ReadDir(cacheDir) + require.NoError(t, err) + require.Empty(t, dir) + }) +} diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 8af841f060..027df385fa 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -201,7 +201,7 @@ func (pc *PackageCreator) Assemble(ctx context.Context, dst *layout.PackagePaths if err := dst.Images.AddV1Image(img); err != nil { return err } - ok, err := utils.HasImageLayers(img) + ok, err := utils.OnlyHasImageLayers(img) if err != nil { return fmt.Errorf("failed to validate %s is an image and not an artifact: %w", info, err) } diff --git a/src/pkg/utils/image.go b/src/pkg/utils/image.go index 80920cbc8b..a6490375dc 100644 --- a/src/pkg/utils/image.go +++ b/src/pkg/utils/image.go @@ -86,8 +86,8 @@ func AddImageNameAnnotation(ociPath string, referenceToDigest map[string]string) return os.WriteFile(indexPath, b, helpers.ReadWriteUser) } -// HasImageLayers checks if all layers in the v1.Image are known image layers. -func HasImageLayers(img v1.Image) (bool, error) { +// OnlyHasImageLayers checks if all layers in the v1.Image are known image layers. +func OnlyHasImageLayers(img v1.Image) (bool, error) { layers, err := img.Layers() if err != nil { return false, err From 3c3c4fd139e67066a0c08173a963850c6c2e3ee6 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 10 Jun 2024 15:32:38 +0200 Subject: [PATCH 055/132] refactor: move k8s tunnel to cluster package (#2566) ## Description Moves the tunnel code to cluster pacakge. This is far from a perfect solution but I do not want to make a enormous change. The tunnel code needs some refactoring in the future but before that can be done we need to understand how dependencies on Cluster looks. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- go.mod | 18 +- go.sum | 37 ++-- src/cmd/connect.go | 5 +- src/cmd/tools/zarf.go | 2 +- src/internal/packager/git/gitea.go | 7 +- src/internal/packager/helm/zarf.go | 30 ++- src/internal/packager/images/push.go | 3 +- src/pkg/cluster/injector.go | 2 +- src/pkg/cluster/tunnel.go | 279 ++++++++++++++++++++++++++- src/pkg/k8s/common.go | 7 + src/pkg/k8s/tunnel.go | 275 -------------------------- src/pkg/k8s/types.go | 2 + src/pkg/packager/deploy.go | 3 +- 13 files changed, 345 insertions(+), 325 deletions(-) delete mode 100644 src/pkg/k8s/tunnel.go diff --git a/go.mod b/go.mod index 5af4b55f94..cc3784ac57 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/anchore/stereoscope v0.0.1 github.com/anchore/syft v0.100.0 github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 + github.com/defenseunicorns/pkg/kubernetes v0.0.1 github.com/defenseunicorns/pkg/oci v1.0.1 github.com/derailed/k9s v0.31.7 github.com/distribution/reference v0.5.0 @@ -45,9 +46,9 @@ require ( github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/crypto v0.23.0 + golang.org/x/crypto v0.24.0 golang.org/x/sync v0.7.0 - golang.org/x/term v0.20.0 + golang.org/x/term v0.21.0 helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.1 k8s.io/apimachinery v0.29.1 @@ -56,11 +57,14 @@ require ( k8s.io/klog/v2 v2.120.1 k8s.io/kubectl v0.29.1 oras.land/oras-go/v2 v2.5.0 + sigs.k8s.io/cli-utils v0.36.0 sigs.k8s.io/kustomize/api v0.16.0 sigs.k8s.io/kustomize/kyaml v0.16.0 sigs.k8s.io/yaml v1.4.0 ) +require github.com/evanphx/json-patch/v5 v5.6.0 // indirect + require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect @@ -474,13 +478,13 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.25.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.26.0 // indirect golang.org/x/oauth2 v0.20.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.16.1 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.180.0 // indirect google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect diff --git a/go.sum b/go.sum index 8b31775074..3fbc8acdb4 100644 --- a/go.sum +++ b/go.sum @@ -599,6 +599,8 @@ github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gw github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= +github.com/defenseunicorns/pkg/kubernetes v0.0.1 h1:HNQBV6XXFvlDvFdOCCWam0/LCgq67M+ggQKiRIoM2vU= +github.com/defenseunicorns/pkg/kubernetes v0.0.1/go.mod h1:AWB1iBbDO4VTmRO/E/8e0tVN0kkWbg+v8dhs9Hd9KXA= github.com/defenseunicorns/pkg/oci v1.0.1 h1:WPrWRrae1L19X1vuhy6yYMR2zrTzgBbJHp3ImgUm4ZM= github.com/defenseunicorns/pkg/oci v1.0.1/go.mod h1:qZ3up/d0P81taW37fKR4lb19jJhQZJVtNOEJMu00dHQ= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= @@ -684,6 +686,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= +github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= @@ -1109,6 +1113,7 @@ github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -1807,8 +1812,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1849,8 +1854,8 @@ golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1916,8 +1921,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2065,8 +2070,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2080,8 +2085,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2099,8 +2104,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2166,8 +2171,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -2178,6 +2183,8 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -2497,6 +2504,8 @@ oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZH rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/cli-utils v0.36.0 h1:k7GM6LmIMydtvM6Ad91XuqKk0QEVL9bVbaiX1uvWIrA= +sigs.k8s.io/cli-utils v0.36.0/go.mod h1:uCFC3BPXB3xHFQyKkWUlTrncVDCKzbdDfqZqRTCrk24= sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/src/cmd/connect.go b/src/cmd/connect.go index ba6eed3d2d..054587337c 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -11,7 +11,6 @@ import ( "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/spf13/cobra" @@ -43,7 +42,7 @@ var ( ctx := cmd.Context() - var tunnel *k8s.Tunnel + var tunnel *cluster.Tunnel if connectResourceName != "" { zt := cluster.NewTunnelInfo(connectNamespace, connectResourceType, connectResourceName, "", connectLocalPort, connectRemotePort) tunnel, err = c.ConnectTunnelInfo(ctx, zt) @@ -100,7 +99,7 @@ func init() { connectCmd.Flags().StringVar(&connectResourceName, "name", "", lang.CmdConnectFlagName) connectCmd.Flags().StringVar(&connectNamespace, "namespace", cluster.ZarfNamespaceName, lang.CmdConnectFlagNamespace) - connectCmd.Flags().StringVar(&connectResourceType, "type", k8s.SvcResource, lang.CmdConnectFlagType) + connectCmd.Flags().StringVar(&connectResourceType, "type", cluster.SvcResource, lang.CmdConnectFlagType) connectCmd.Flags().IntVar(&connectLocalPort, "local-port", 0, lang.CmdConnectFlagLocalPort) connectCmd.Flags().IntVar(&connectRemotePort, "remote-port", 0, lang.CmdConnectFlagRemotePort) connectCmd.Flags().BoolVar(&cliOnly, "cli-only", false, lang.CmdConnectFlagCliOnly) diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 843fcb9ae3..84395d0e82 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -145,7 +145,7 @@ var updateCredsCmd = &cobra.Command{ h := helm.NewClusterOnly(&types.PackagerConfig{}, template.GetZarfVariableConfig(), newState, c) if slices.Contains(args, message.RegistryKey) && newState.RegistryInfo.InternalRegistry { - err = h.UpdateZarfRegistryValues() + err = h.UpdateZarfRegistryValues(ctx) if err != nil { // Warn if we couldn't actually update the registry (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateRegistry, err.Error()) diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go index c6644c6b1a..7bf9afe53f 100644 --- a/src/internal/packager/git/gitea.go +++ b/src/internal/packager/git/gitea.go @@ -18,7 +18,6 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -41,7 +40,7 @@ func (g *Git) CreateReadOnlyUser(ctx context.Context) error { } // Establish a git tunnel to send the repo - tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, k8s.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) + tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) if err != nil { return err } @@ -128,7 +127,7 @@ func (g *Git) UpdateGitUser(ctx context.Context, oldAdminPass string, username s return err } // Establish a git tunnel to send the repo - tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, k8s.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) + tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) if err != nil { return err } @@ -167,7 +166,7 @@ func (g *Git) CreatePackageRegistryToken(ctx context.Context) (CreateTokenRespon } // Establish a git tunnel to send the repo - tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, k8s.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) + tunnel, err := c.NewTunnel(cluster.ZarfNamespaceName, cluster.SvcResource, cluster.ZarfGitServerName, "", 0, cluster.ZarfGitServerPort) if err != nil { return CreateTokenResponse{}, err } diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index 94996b4173..b3a13f8d6e 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -7,6 +7,13 @@ package helm import ( "context" "fmt" + "time" + + "helm.sh/helm/v3/pkg/action" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/object" + + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/defenseunicorns/zarf/src/internal/packager/template" "github.com/defenseunicorns/zarf/src/pkg/cluster" @@ -16,37 +23,48 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/pkg/variables" "github.com/defenseunicorns/zarf/src/types" - "helm.sh/helm/v3/pkg/action" ) // UpdateZarfRegistryValues updates the Zarf registry deployment with the new state values -func (h *Helm) UpdateZarfRegistryValues() error { +func (h *Helm) UpdateZarfRegistryValues(ctx context.Context) error { pushUser, err := utils.GetHtpasswdString(h.state.RegistryInfo.PushUsername, h.state.RegistryInfo.PushPassword) if err != nil { return fmt.Errorf("error generating htpasswd string: %w", err) } - pullUser, err := utils.GetHtpasswdString(h.state.RegistryInfo.PullUsername, h.state.RegistryInfo.PullPassword) if err != nil { return fmt.Errorf("error generating htpasswd string: %w", err) } - registryValues := map[string]interface{}{ "secrets": map[string]interface{}{ "htpasswd": fmt.Sprintf("%s\n%s", pushUser, pullUser), }, } - h.chart = types.ZarfChart{ Namespace: "zarf", ReleaseName: "zarf-docker-registry", } - err = h.UpdateReleaseValues(registryValues) if err != nil { return fmt.Errorf("error updating the release values: %w", err) } + objs := []object.ObjMetadata{ + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "zarf", + Name: "zarf-docker-registry", + }, + } + waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) + defer waitCancel() + err = pkgkubernetes.WaitForReady(waitCtx, h.cluster.Watcher, objs) + if err != nil { + return err + } return nil } diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index 84a00a2455..3048218662 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -11,7 +11,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" @@ -48,7 +47,7 @@ func Push(ctx context.Context, cfg PushConfig) error { var ( err error - tunnel *k8s.Tunnel + tunnel *cluster.Tunnel registryURL = cfg.RegInfo.Address ) diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 8342315a3e..2af0694113 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -276,7 +276,7 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta // Test for pod readiness and seed image presence. func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Image, spinner *message.Spinner) bool { - tunnel, err := c.NewTunnel(ZarfNamespaceName, k8s.SvcResource, ZarfInjectorName, "", 0, ZarfInjectorPort) + tunnel, err := c.NewTunnel(ZarfNamespaceName, SvcResource, ZarfInjectorName, "", 0, ZarfInjectorPort) if err != nil { return false } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 113779671b..7a2f635016 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -7,16 +7,23 @@ package cluster import ( "context" "fmt" + "io" + "net/http" "net/url" "strconv" "strings" + "sync" + "time" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -36,7 +43,7 @@ const ( ZarfGitServerPort = 3000 ) -// TunnelInfo is a struct that contains the necessary info to create a new k8s.Tunnel +// TunnelInfo is a struct that contains the necessary info to create a new Tunnel type TunnelInfo struct { localPort int remotePort int @@ -88,11 +95,11 @@ func (c *Cluster) PrintConnectTable(ctx context.Context) error { } // Connect will establish a tunnel to the specified target. -func (c *Cluster) Connect(ctx context.Context, target string) (*k8s.Tunnel, error) { +func (c *Cluster) Connect(ctx context.Context, target string) (*Tunnel, error) { var err error zt := TunnelInfo{ namespace: ZarfNamespaceName, - resourceType: k8s.SvcResource, + resourceType: SvcResource, } switch strings.ToUpper(target) { @@ -134,7 +141,7 @@ func (c *Cluster) Connect(ctx context.Context, target string) (*k8s.Tunnel, erro } // ConnectTunnelInfo connects to the cluster with the provided TunnelInfo -func (c *Cluster) ConnectTunnelInfo(ctx context.Context, zt TunnelInfo) (*k8s.Tunnel, error) { +func (c *Cluster) ConnectTunnelInfo(ctx context.Context, zt TunnelInfo) (*Tunnel, error) { tunnel, err := c.NewTunnel(zt.namespace, zt.resourceType, zt.resourceName, zt.urlSuffix, zt.localPort, zt.remotePort) if err != nil { return nil, err @@ -149,14 +156,14 @@ func (c *Cluster) ConnectTunnelInfo(ctx context.Context, zt TunnelInfo) (*k8s.Tu } // ConnectToZarfRegistryEndpoint determines if a registry endpoint is in cluster, and if so opens a tunnel to connect to it -func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) { +func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInfo types.RegistryInfo) (string, *Tunnel, error) { registryEndpoint := registryInfo.Address var err error - var tunnel *k8s.Tunnel + var tunnel *Tunnel if registryInfo.InternalRegistry { // Establish a registry tunnel to send the images to the zarf registry - if tunnel, err = c.NewTunnel(ZarfNamespaceName, k8s.SvcResource, ZarfRegistryName, "", 0, ZarfRegistryPort); err != nil { + if tunnel, err = c.NewTunnel(ZarfNamespaceName, SvcResource, ZarfRegistryName, "", 0, ZarfRegistryPort); err != nil { return "", tunnel, err } } else { @@ -168,7 +175,7 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInf // If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource if err == nil { - if tunnel, err = c.NewTunnel(namespace, k8s.SvcResource, name, "", 0, port); err != nil { + if tunnel, err = c.NewTunnel(namespace, SvcResource, name, "", 0, port); err != nil { return "", tunnel, err } } @@ -211,7 +218,7 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu svc := serviceList.Items[0] // Reset based on the matched params. - zt.resourceType = k8s.SvcResource + zt.resourceType = SvcResource zt.resourceName = svc.Name zt.namespace = svc.Namespace // Only support a service with a single port. @@ -273,3 +280,255 @@ func serviceInfoFromNodePortURL(services []corev1.Service, nodePortURL string) ( return "", "", 0, fmt.Errorf("no matching node port services found") } + +// Global lock to synchronize port selections. +var globalMutex sync.Mutex + +// Zarf Tunnel Configuration Constants. +const ( + PodResource = "pod" + SvcResource = "svc" +) + +// Tunnel is the main struct that configures and manages port forwarding tunnels to Kubernetes resources. +type Tunnel struct { + clientset kubernetes.Interface + restConfig *rest.Config + out io.Writer + localPort int + remotePort int + namespace string + resourceType string + resourceName string + urlSuffix string + attempt int + stopChan chan struct{} + readyChan chan struct{} + errChan chan error +} + +// NewTunnel will create a new Tunnel struct. +// Note that if you use 0 for the local port, an open port on the host system +// will be selected automatically, and the Tunnel struct will be updated with the selected port. +func (c *Cluster) NewTunnel(namespace, resourceType, resourceName, urlSuffix string, local, remote int) (*Tunnel, error) { + return &Tunnel{ + clientset: c.Clientset, + restConfig: c.RestConfig, + out: io.Discard, + localPort: local, + remotePort: remote, + namespace: namespace, + resourceType: resourceType, + resourceName: resourceName, + urlSuffix: urlSuffix, + stopChan: make(chan struct{}, 1), + readyChan: make(chan struct{}, 1), + }, nil +} + +// Wrap takes a function that returns an error and wraps it to check for tunnel errors as well. +func (tunnel *Tunnel) Wrap(function func() error) error { + var err error + funcErrChan := make(chan error) + + go func() { + funcErrChan <- function() + }() + + select { + case err = <-funcErrChan: + return err + case err = <-tunnel.ErrChan(): + return err + } +} + +// Connect will establish a tunnel to the specified target. +func (tunnel *Tunnel) Connect(ctx context.Context) (string, error) { + url, err := tunnel.establish(ctx) + + // Try to establish the tunnel up to 3 times. + if err != nil { + tunnel.attempt++ + + // If we have exceeded the number of attempts, exit with an error. + if tunnel.attempt > 3 { + return "", fmt.Errorf("unable to establish tunnel after 3 attempts: %w", err) + } + + // Otherwise, retry the connection but delay increasing intervals between attempts. + delay := tunnel.attempt * 10 + message.Debugf("%s", err.Error()) + message.Debugf("Delay creating tunnel, waiting %d seconds...", delay) + + timer := time.NewTimer(0) + defer timer.Stop() + + select { + case <-ctx.Done(): + return "", ctx.Err() + case <-timer.C: + url, err = tunnel.Connect(ctx) + if err != nil { + return "", err + } + + timer.Reset(time.Duration(delay) * time.Second) + } + } + + return url, nil +} + +// Endpoint returns the tunnel ip address and port (i.e. for docker registries) +func (tunnel *Tunnel) Endpoint() string { + return fmt.Sprintf("%s:%d", helpers.IPV4Localhost, tunnel.localPort) +} + +// ErrChan returns the tunnel's error channel +func (tunnel *Tunnel) ErrChan() chan error { + return tunnel.errChan +} + +// HTTPEndpoint returns the tunnel endpoint as a HTTP URL string. +func (tunnel *Tunnel) HTTPEndpoint() string { + return fmt.Sprintf("http://%s", tunnel.Endpoint()) +} + +// FullURL returns the tunnel endpoint as a HTTP URL string with the urlSuffix appended. +func (tunnel *Tunnel) FullURL() string { + return fmt.Sprintf("%s%s", tunnel.HTTPEndpoint(), tunnel.urlSuffix) +} + +// Close disconnects a tunnel connection by closing the StopChan, thereby stopping the goroutine. +func (tunnel *Tunnel) Close() { + close(tunnel.stopChan) +} + +// establish opens a tunnel to a kubernetes resource, as specified by the provided tunnel struct. +func (tunnel *Tunnel) establish(ctx context.Context) (string, error) { + var err error + + // Track this locally as we may need to retry if the tunnel fails. + localPort := tunnel.localPort + + // If the local-port is 0, get an available port before continuing. We do this here instead of relying on the + // underlying port-forwarder library, because the port-forwarder library does not expose the selected local port in a + // machine-readable manner. + // Synchronize on the global lock to avoid race conditions with concurrently selecting the same available port, + // since there is a brief moment between `GetAvailablePort` and `forwarder.ForwardPorts` where the selected port + // is available for selection again. + if localPort == 0 { + message.Debugf("Requested local port is 0. Selecting an open port on host system") + localPort, err = helpers.GetAvailablePort() + if err != nil { + return "", fmt.Errorf("unable to find an available port: %w", err) + } + message.Debugf("Selected port %d", localPort) + globalMutex.Lock() + defer globalMutex.Unlock() + } + + msg := fmt.Sprintf("Opening tunnel %d -> %d for %s/%s in namespace %s", + localPort, + tunnel.remotePort, + tunnel.resourceType, + tunnel.resourceName, + tunnel.namespace, + ) + message.Debugf(msg) + + // Find the pod to port forward to + podName, err := tunnel.getAttachablePodForResource(ctx) + if err != nil { + return "", fmt.Errorf("unable to find pod attached to given resource: %w", err) + } + message.Debugf("Selected pod %s to open port forward to", podName) + + // Build url to the port forward endpoint. + // Example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward. + postEndpoint := tunnel.clientset.CoreV1().RESTClient().Post() + namespace := tunnel.namespace + portForwardCreateURL := postEndpoint. + Resource("pods"). + Namespace(namespace). + Name(podName). + SubResource("portforward"). + URL() + + message.Debugf("Using URL %s to create portforward", portForwardCreateURL) + + // Construct the spdy client required by the client-go portforward library. + transport, upgrader, err := spdy.RoundTripperFor(tunnel.restConfig) + if err != nil { + return "", fmt.Errorf("unable to create the spdy client %w", err) + } + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", portForwardCreateURL) + + // Construct a new PortForwarder struct that manages the instructed port forward tunnel. + ports := []string{fmt.Sprintf("%d:%d", localPort, tunnel.remotePort)} + portforwarder, err := portforward.New(dialer, ports, tunnel.stopChan, tunnel.readyChan, tunnel.out, tunnel.out) + if err != nil { + return "", fmt.Errorf("unable to create the port forward: %w", err) + } + + // Open the tunnel in a goroutine so that it is available in the background. Report errors to the main goroutine via + // a new channel. + errChan := make(chan error) + go func() { + errChan <- portforwarder.ForwardPorts() + }() + + // Wait for an error or the tunnel to be ready. + select { + case err = <-errChan: + return "", fmt.Errorf("unable to start the tunnel: %w", err) + case <-portforwarder.Ready: + // Store for endpoint output + tunnel.localPort = localPort + url := tunnel.FullURL() + + // Store the error channel to listen for errors + tunnel.errChan = errChan + + message.Debugf("Creating port forwarding tunnel at %s", url) + return url, nil + } +} + +// getAttachablePodForResource will find a pod that can be port forwarded to the provided resource type and return +// the name. +func (tunnel *Tunnel) getAttachablePodForResource(ctx context.Context) (string, error) { + switch tunnel.resourceType { + case PodResource: + return tunnel.resourceName, nil + case SvcResource: + return tunnel.getAttachablePodForService(ctx) + default: + return "", fmt.Errorf("unknown resource type: %s", tunnel.resourceType) + } +} + +// getAttachablePodForService will find an active pod associated with the Service and return the pod name. +func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, error) { + service, err := tunnel.clientset.CoreV1().Services(tunnel.namespace).Get(ctx, tunnel.resourceName, metav1.GetOptions{}) + if err != nil { + return "", fmt.Errorf("unable to find the service: %w", err) + } + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: service.Spec.Selector}) + if err != nil { + return "", err + } + listOpt := metav1.ListOptions{ + LabelSelector: selector.String(), + FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), + } + podList, err := tunnel.clientset.CoreV1().Pods(tunnel.namespace).List(ctx, listOpt) + if err != nil { + return "", err + } + if len(podList.Items) < 1 { + return "", fmt.Errorf("no pods found for service %s", tunnel.resourceName) + } + return podList.Items[0].Name, nil +} diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index 9041775825..42d2da270a 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -19,6 +19,8 @@ import ( _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" + + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" ) const ( @@ -38,10 +40,15 @@ func New(logger Log) (*K8s, error) { if err != nil { return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) } + watcher, err := pkgkubernetes.WatcherForConfig(config) + if err != nil { + return nil, err + } return &K8s{ RestConfig: config, Clientset: clientset, + Watcher: watcher, Log: logger, }, nil } diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go deleted file mode 100644 index a2e5f25db9..0000000000 --- a/src/pkg/k8s/tunnel.go +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -// Forked from https://github.com/gruntwork-io/terratest/blob/v0.38.8/modules/k8s/tunnel.go - -import ( - "context" - "fmt" - "io" - "net/http" - "sync" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/tools/portforward" - "k8s.io/client-go/transport/spdy" - - "github.com/defenseunicorns/pkg/helpers/v2" -) - -// Global lock to synchronize port selections. -var globalMutex sync.Mutex - -// Zarf Tunnel Configuration Constants. -const ( - PodResource = "pod" - SvcResource = "svc" -) - -// Tunnel is the main struct that configures and manages port forwarding tunnels to Kubernetes resources. -type Tunnel struct { - kube *K8s - out io.Writer - localPort int - remotePort int - namespace string - resourceType string - resourceName string - urlSuffix string - attempt int - stopChan chan struct{} - readyChan chan struct{} - errChan chan error -} - -// NewTunnel will create a new Tunnel struct. -// Note that if you use 0 for the local port, an open port on the host system -// will be selected automatically, and the Tunnel struct will be updated with the selected port. -func (k *K8s) NewTunnel(namespace, resourceType, resourceName, urlSuffix string, local, remote int) (*Tunnel, error) { - return &Tunnel{ - out: io.Discard, - localPort: local, - remotePort: remote, - namespace: namespace, - resourceType: resourceType, - resourceName: resourceName, - urlSuffix: urlSuffix, - stopChan: make(chan struct{}, 1), - readyChan: make(chan struct{}, 1), - kube: k, - }, nil -} - -// Wrap takes a function that returns an error and wraps it to check for tunnel errors as well. -func (tunnel *Tunnel) Wrap(function func() error) error { - var err error - funcErrChan := make(chan error) - - go func() { - funcErrChan <- function() - }() - - select { - case err = <-funcErrChan: - return err - case err = <-tunnel.ErrChan(): - return err - } -} - -// Connect will establish a tunnel to the specified target. -func (tunnel *Tunnel) Connect(ctx context.Context) (string, error) { - url, err := tunnel.establish(ctx) - - // Try to establish the tunnel up to 3 times. - if err != nil { - tunnel.attempt++ - - // If we have exceeded the number of attempts, exit with an error. - if tunnel.attempt > 3 { - return "", fmt.Errorf("unable to establish tunnel after 3 attempts: %w", err) - } - - // Otherwise, retry the connection but delay increasing intervals between attempts. - delay := tunnel.attempt * 10 - tunnel.kube.Log("%s", err.Error()) - tunnel.kube.Log("Delay creating tunnel, waiting %d seconds...", delay) - - timer := time.NewTimer(0) - defer timer.Stop() - - select { - case <-ctx.Done(): - return "", ctx.Err() - case <-timer.C: - url, err = tunnel.Connect(ctx) - if err != nil { - return "", err - } - - timer.Reset(time.Duration(delay) * time.Second) - } - } - - return url, nil -} - -// Endpoint returns the tunnel ip address and port (i.e. for docker registries) -func (tunnel *Tunnel) Endpoint() string { - return fmt.Sprintf("%s:%d", helpers.IPV4Localhost, tunnel.localPort) -} - -// ErrChan returns the tunnel's error channel -func (tunnel *Tunnel) ErrChan() chan error { - return tunnel.errChan -} - -// HTTPEndpoint returns the tunnel endpoint as a HTTP URL string. -func (tunnel *Tunnel) HTTPEndpoint() string { - return fmt.Sprintf("http://%s", tunnel.Endpoint()) -} - -// FullURL returns the tunnel endpoint as a HTTP URL string with the urlSuffix appended. -func (tunnel *Tunnel) FullURL() string { - return fmt.Sprintf("%s%s", tunnel.HTTPEndpoint(), tunnel.urlSuffix) -} - -// Close disconnects a tunnel connection by closing the StopChan, thereby stopping the goroutine. -func (tunnel *Tunnel) Close() { - close(tunnel.stopChan) -} - -// establish opens a tunnel to a kubernetes resource, as specified by the provided tunnel struct. -func (tunnel *Tunnel) establish(ctx context.Context) (string, error) { - var err error - - // Track this locally as we may need to retry if the tunnel fails. - localPort := tunnel.localPort - - // If the local-port is 0, get an available port before continuing. We do this here instead of relying on the - // underlying port-forwarder library, because the port-forwarder library does not expose the selected local port in a - // machine-readable manner. - // Synchronize on the global lock to avoid race conditions with concurrently selecting the same available port, - // since there is a brief moment between `GetAvailablePort` and `forwarder.ForwardPorts` where the selected port - // is available for selection again. - if localPort == 0 { - tunnel.kube.Log("Requested local port is 0. Selecting an open port on host system") - localPort, err = helpers.GetAvailablePort() - if err != nil { - return "", fmt.Errorf("unable to find an available port: %w", err) - } - tunnel.kube.Log("Selected port %d", localPort) - globalMutex.Lock() - defer globalMutex.Unlock() - } - - message := fmt.Sprintf("Opening tunnel %d -> %d for %s/%s in namespace %s", - localPort, - tunnel.remotePort, - tunnel.resourceType, - tunnel.resourceName, - tunnel.namespace, - ) - - tunnel.kube.Log(message) - - // Find the pod to port forward to - podName, err := tunnel.getAttachablePodForResource(ctx) - if err != nil { - return "", fmt.Errorf("unable to find pod attached to given resource: %w", err) - } - tunnel.kube.Log("Selected pod %s to open port forward to", podName) - - // Build url to the port forward endpoint. - // Example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-deploy-9itlq/portforward. - postEndpoint := tunnel.kube.Clientset.CoreV1().RESTClient().Post() - namespace := tunnel.namespace - portForwardCreateURL := postEndpoint. - Resource("pods"). - Namespace(namespace). - Name(podName). - SubResource("portforward"). - URL() - - tunnel.kube.Log("Using URL %s to create portforward", portForwardCreateURL) - - // Construct the spdy client required by the client-go portforward library. - transport, upgrader, err := spdy.RoundTripperFor(tunnel.kube.RestConfig) - if err != nil { - return "", fmt.Errorf("unable to create the spdy client %w", err) - } - dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", portForwardCreateURL) - - // Construct a new PortForwarder struct that manages the instructed port forward tunnel. - ports := []string{fmt.Sprintf("%d:%d", localPort, tunnel.remotePort)} - portforwarder, err := portforward.New(dialer, ports, tunnel.stopChan, tunnel.readyChan, tunnel.out, tunnel.out) - if err != nil { - return "", fmt.Errorf("unable to create the port forward: %w", err) - } - - // Open the tunnel in a goroutine so that it is available in the background. Report errors to the main goroutine via - // a new channel. - errChan := make(chan error) - go func() { - errChan <- portforwarder.ForwardPorts() - }() - - // Wait for an error or the tunnel to be ready. - select { - case err = <-errChan: - return "", fmt.Errorf("unable to start the tunnel: %w", err) - case <-portforwarder.Ready: - // Store for endpoint output - tunnel.localPort = localPort - url := tunnel.FullURL() - - // Store the error channel to listen for errors - tunnel.errChan = errChan - - tunnel.kube.Log("Creating port forwarding tunnel at %s", url) - return url, nil - } -} - -// getAttachablePodForResource will find a pod that can be port forwarded to the provided resource type and return -// the name. -func (tunnel *Tunnel) getAttachablePodForResource(ctx context.Context) (string, error) { - switch tunnel.resourceType { - case PodResource: - return tunnel.resourceName, nil - case SvcResource: - return tunnel.getAttachablePodForService(ctx) - default: - return "", fmt.Errorf("unknown resource type: %s", tunnel.resourceType) - } -} - -// getAttachablePodForService will find an active pod associated with the Service and return the pod name. -func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, error) { - service, err := tunnel.kube.Clientset.CoreV1().Services(tunnel.namespace).Get(ctx, tunnel.resourceName, metav1.GetOptions{}) - if err != nil { - return "", fmt.Errorf("unable to find the service: %w", err) - } - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: service.Spec.Selector}) - if err != nil { - return "", err - } - - servicePods := tunnel.kube.WaitForPodsAndContainers( - ctx, - PodLookup{ - Namespace: tunnel.namespace, - Selector: selector.String(), - }, - nil, - ) - - if len(servicePods) < 1 { - return "", fmt.Errorf("no pods found for service %s", tunnel.resourceName) - } - return servicePods[0].Name, nil -} diff --git a/src/pkg/k8s/types.go b/src/pkg/k8s/types.go index d52ea6389b..5c3a2cc36c 100644 --- a/src/pkg/k8s/types.go +++ b/src/pkg/k8s/types.go @@ -8,6 +8,7 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" + "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" ) // Log is a function that logs a message. @@ -20,6 +21,7 @@ type Labels map[string]string type K8s struct { Clientset kubernetes.Interface RestConfig *rest.Config + Watcher watcher.StatusWatcher Log Log } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 39fb2eaa88..4fe2b770e6 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -30,7 +30,6 @@ import ( "github.com/defenseunicorns/zarf/src/internal/packager/images" "github.com/defenseunicorns/zarf/src/internal/packager/template" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/actions" @@ -545,7 +544,7 @@ func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, } } - tunnel, err := p.cluster.NewTunnel(namespace, k8s.SvcResource, name, "", 0, port) + tunnel, err := p.cluster.NewTunnel(namespace, cluster.SvcResource, name, "", 0, port) if err != nil { return err } From fd452917584382f7ed588ffc5eaf428ecc7bb868 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 10 Jun 2024 10:59:35 -0500 Subject: [PATCH 056/132] test: cleanup e2e tests (#2601) ## Description Simplify `TestMain` and update the `build-examples` make target to build only the binary for the local system. ## Related Issue Relates to #2562 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- Makefile | 2 +- src/test/e2e/main_test.go | 52 +++++++++------------------------------ 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 2f397ae821..925837fb26 100644 --- a/Makefile +++ b/Makefile @@ -145,7 +145,7 @@ publish-init-package: $(ZARF_BIN) package publish . oci://$(REPOSITORY_URL) build-examples: ## Build all of the example packages - @test -s $(ZARF_BIN) || $(MAKE) build-cli + @test -s $(ZARF_BIN) || $(MAKE) @test -s ./build/zarf-package-dos-games-$(ARCH)-1.0.0.tar.zst || $(ZARF_BIN) package create examples/dos-games -o build -a $(ARCH) --confirm diff --git a/src/test/e2e/main_test.go b/src/test/e2e/main_test.go index a58a133130..da33bbcea2 100644 --- a/src/test/e2e/main_test.go +++ b/src/test/e2e/main_test.go @@ -5,9 +5,9 @@ package test import ( - "fmt" + "log" "os" - "path" + "path/filepath" "testing" "github.com/defenseunicorns/zarf/src/config" @@ -25,53 +25,25 @@ const ( skipK8sEnvVar = "SKIP_K8S" ) -// TestMain lets us customize the test run. See https://medium.com/goingogo/why-use-testmain-for-testing-in-go-dafb52b406bc. func TestMain(m *testing.M) { - // Work from the root directory of the project - err := os.Chdir("../../../") + rootDir, err := filepath.Abs("../../../") if err != nil { - fmt.Println(err) //nolint:forbidigo + log.Fatal(err) } - - // K3d use the intern package, which requires this to be set in go 1.19 - os.Setenv("ASSUME_NO_MOVING_GC_UNSAFE_RISK_IT_WITH", "go1.19") - - // Set the log level to trace for when we call Zarf functions internally - message.SetLogLevel(message.TraceLevel) - - retCode, err := doAllTheThings(m) - if err != nil { - fmt.Println(err) //nolint:forbidigo + if err := os.Chdir(rootDir); err != nil { + log.Fatal(err) } - os.Exit(retCode) -} - -// doAllTheThings just wraps what should go in TestMain. It's in its own function so it can -// [a] Not have a bunch of `os.Exit()` calls in it -// [b] Do defers properly -// [c] Handle errors cleanly -// -// It returns the return code passed from `m.Run()` and any error thrown. -func doAllTheThings(m *testing.M) (int, error) { - var err error - - // Set up constants in the global variable that all the tests are able to access e2e.Arch = config.GetArch() - e2e.ZarfBinPath = path.Join("build", test.GetCLIName()) + e2e.ZarfBinPath = filepath.Join("build", test.GetCLIName()) e2e.ApplianceMode = os.Getenv(applianceModeEnvVar) == "true" e2e.ApplianceModeKeep = os.Getenv(applianceModeKeepEnvVar) == "true" e2e.RunClusterTests = os.Getenv(skipK8sEnvVar) != "true" - // Validate that the Zarf binary exists. If it doesn't that means the dev hasn't built it, usually by running - // `make build-cli` - _, err = os.Stat(e2e.ZarfBinPath) - if err != nil { - return 1, fmt.Errorf("zarf binary %s not found", e2e.ZarfBinPath) - } - - // Run the tests, with the cluster cleanup being deferred to the end of the function call - returnCode := m.Run() + message.SetLogLevel(message.TraceLevel) - return returnCode, nil + if _, err := os.Stat(e2e.ZarfBinPath); err != nil { + log.Fatalf("zarf binary %s not found: %v", e2e.ZarfBinPath, err) + } + os.Exit(m.Run()) } From 18e910d78332d5d35de1d62fdd6ada3b17feaad6 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 11 Jun 2024 15:32:02 +0200 Subject: [PATCH 057/132] refactor: enable errcheck linter (#2501) ## Description This change enables the errcheck linter which requires all returned errors to be checked. ## Related Issue Part of #2503 Depends on #2499 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- .golangci.yaml | 7 +++++++ src/internal/agent/http/admission/handler.go | 2 ++ src/internal/agent/http/proxy.go | 1 + src/internal/agent/http/server.go | 1 + src/pkg/cluster/state_test.go | 1 + src/pkg/cluster/zarf_test.go | 6 ++++-- src/pkg/message/message.go | 1 + src/pkg/packager/lint/lint.go | 1 + src/pkg/packager/sources/new_test.go | 1 + src/pkg/utils/network_test.go | 1 + src/pkg/variables/templates_test.go | 3 ++- src/pkg/zoci/push.go | 5 ++++- src/test/nightly/ecr_publish_test.go | 3 ++- 13 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index cd775b13e2..ab7cc77904 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,6 +3,7 @@ run: linters: disable-all: true enable: + - errcheck - gosimple - govet - ineffassign @@ -54,6 +55,12 @@ linters-settings: - name: redefines-builtin-id testifylint: enable-all: true + errcheck: + exclude-functions: + - (*github.com/spf13/cobra.Command).Help + - (*github.com/spf13/cobra.Command).MarkFlagRequired + - (*github.com/spf13/pflag.FlagSet).MarkHidden + - (*github.com/spf13/pflag.FlagSet).MarkDeprecated issues: # Revive rules that are disabled by default. include: diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index 4839073038..68a17cbee5 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -87,6 +87,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { return } w.WriteHeader(http.StatusInternalServerError) + //nolint:errcheck // ignore w.Write(jsonResponse) return } @@ -124,6 +125,7 @@ func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { message.Infof(lang.AgentInfoWebhookAllowed, r.URL.Path, review.Request.Operation, result.Allowed) w.WriteHeader(http.StatusOK) + //nolint: errcheck // ignore w.Write(jsonResponse) } } diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go index 860d147811..2a78b88eea 100644 --- a/src/internal/agent/http/proxy.go +++ b/src/internal/agent/http/proxy.go @@ -27,6 +27,7 @@ func ProxyHandler() http.HandlerFunc { if err != nil { message.Debugf("%#v", err) w.WriteHeader(http.StatusInternalServerError) + //nolint: errcheck // ignore w.Write([]byte(lang.AgentErrUnableTransform)) return } diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 2bf93f4d7f..7938f35d6e 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -68,6 +68,7 @@ func NewProxyServer(port string) *http.Server { func healthz() http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) + //nolint: errcheck // ignore w.Write([]byte("ok")) } } diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index 7259daa884..e8c42983b3 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -150,6 +150,7 @@ func TestInitZarfState(t *testing.T) { Name: "default", }, } + //nolint:errcheck // ignore cs.CoreV1().ServiceAccounts(ns.Name).Create(ctx, sa, metav1.CreateOptions{}) break } diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index c83e680628..6c400bb97b 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -227,7 +227,8 @@ func TestGetDeployedPackage(t *testing.T) { "data": b, }, } - c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{}) + _, err = c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &secret, metav1.CreateOptions{}) + require.NoError(t, err) actual, err := c.GetDeployedPackage(ctx, p.Name) require.NoError(t, err) require.Equal(t, p, *actual) @@ -242,7 +243,8 @@ func TestGetDeployedPackage(t *testing.T) { }, }, } - c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &nonPackageSecret, metav1.CreateOptions{}) + _, err := c.Clientset.CoreV1().Secrets("zarf").Create(ctx, &nonPackageSecret, metav1.CreateOptions{}) + require.NoError(t, err) actualList, err := c.GetDeployedZarfPackages(ctx) require.NoError(t, err) diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 7c50315e77..206176813f 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -319,6 +319,7 @@ func Table(header []string, data [][]string) { table = append(table, pterm.TableData{row}...) } + //nolint:errcheck // never returns an error pterm.DefaultTable.WithHasHeader().WithData(table).Render() } diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go index b835e0ad58..c6e1c4dde0 100644 --- a/src/pkg/packager/lint/lint.go +++ b/src/pkg/packager/lint/lint.go @@ -152,6 +152,7 @@ func fillComponentTemplate(validator *Validator, node *composer.Node, createOpts // [DEPRECATION] Set the Package Variable syntax as well for backward compatibility setVarsAndWarn(types.ZarfPackageVariablePrefix, true) + //nolint: errcheck // This error should bubble up utils.ReloadYamlTemplate(node, templateMap) } diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index bad95a9051..d8d9c8ecd3 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -129,6 +129,7 @@ func TestPackageSource(t *testing.T) { return } defer f.Close() + //nolint:errcheck // ignore io.Copy(rw, f) })) t.Cleanup(func() { ts.Close() }) diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go index ee30624885..c2f059d7fe 100644 --- a/src/pkg/utils/network_test.go +++ b/src/pkg/utils/network_test.go @@ -83,6 +83,7 @@ func TestDownloadToFile(t *testing.T) { rw.WriteHeader(http.StatusNotFound) return } + //nolint:errcheck // ignore rw.Write([]byte(content)) })) t.Cleanup(func() { srv.Close() }) diff --git a/src/pkg/variables/templates_test.go b/src/pkg/variables/templates_test.go index 7dfb540029..8becae631a 100644 --- a/src/pkg/variables/templates_test.go +++ b/src/pkg/variables/templates_test.go @@ -135,7 +135,8 @@ func TestReplaceTextTemplate(t *testing.T) { f, _ := os.Create(tc.path) defer f.Close() - f.WriteString(start) + _, err := f.WriteString(start) + require.NoError(t, err) } gotErr := tc.vc.ReplaceTextTemplate(tc.path) diff --git a/src/pkg/zoci/push.go b/src/pkg/zoci/push.go index ccbad6862b..19ee37a761 100644 --- a/src/pkg/zoci/push.go +++ b/src/pkg/zoci/push.go @@ -53,7 +53,10 @@ func (r *Remote) PublishPackage(ctx context.Context, pkg *types.ZarfPackage, pat // assumes referrers API is not supported since OCI artifact // media type is not supported - r.Repo().SetReferrersCapability(false) + err = r.Repo().SetReferrersCapability(false) + if err != nil { + return err + } // push the manifest config manifestConfigDesc, err := r.CreateAndPushManifestConfig(ctx, annotations, ZarfConfigMediaType) diff --git a/src/test/nightly/ecr_publish_test.go b/src/test/nightly/ecr_publish_test.go index b201f80793..a1d07e73e1 100644 --- a/src/test/nightly/ecr_publish_test.go +++ b/src/test/nightly/ecr_publish_test.go @@ -30,7 +30,8 @@ func TestECRPublishing(t *testing.T) { t.Log("E2E: Testing component actions") // Work from the root directory of the project - os.Chdir("../../../") + err := os.Chdir("../../../") + require.NoError(t, err) // Create a tmpDir for us to use during this test tmpDir := t.TempDir() From e803d7a45b15b5ab758f4c73d98e34d198f97116 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 11 Jun 2024 16:04:01 +0200 Subject: [PATCH 058/132] fix: crane option argument parameters (#2609) ## Description During refactoring in #2460 we broke the option parameters for Crane. Honestly I am not surprised that this has happened. The way that these options are parsed and passed around are a threading mess. The issue is that this seems to also be the method in which Crane does it. This change reverts the specific pointer changes which caused the issue in the first place. This is one the reasons why I do not like Cobra. The use of init functions and global variables are doomed to cause threading issues. Reminds me of writing Java programs with Spring Boot. ## Related Issue Fixes #2571 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/tools/crane.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index a89041bb26..ad0b98ab0e 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -82,12 +82,12 @@ func init() { craneCopy := craneCmd.NewCmdCopy(&craneOptions) registryCmd.AddCommand(craneCopy) - registryCmd.AddCommand(zarfCraneCatalog(craneOptions)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdList, craneOptions, lang.CmdToolsRegistryListExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPush, craneOptions, lang.CmdToolsRegistryPushExample, 1)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPull, craneOptions, lang.CmdToolsRegistryPullExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDelete, craneOptions, lang.CmdToolsRegistryDeleteExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDigest, craneOptions, lang.CmdToolsRegistryDigestExample, 0)) + registryCmd.AddCommand(zarfCraneCatalog(&craneOptions)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdList, &craneOptions, lang.CmdToolsRegistryListExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPush, &craneOptions, lang.CmdToolsRegistryPushExample, 1)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPull, &craneOptions, lang.CmdToolsRegistryPullExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDelete, &craneOptions, lang.CmdToolsRegistryDeleteExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDigest, &craneOptions, lang.CmdToolsRegistryDigestExample, 0)) registryCmd.AddCommand(pruneCmd) registryCmd.AddCommand(craneCmd.NewCmdVersion()) @@ -100,8 +100,8 @@ func init() { } // Wrap the original crane catalog with a zarf specific version -func zarfCraneCatalog(cranePlatformOptions []crane.Option) *cobra.Command { - craneCatalog := craneCmd.NewCmdCatalog(&cranePlatformOptions) +func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { + craneCatalog := craneCmd.NewCmdCatalog(cranePlatformOptions) craneCatalog.Example = lang.CmdToolsRegistryCatalogExample craneCatalog.Args = nil @@ -134,7 +134,7 @@ func zarfCraneCatalog(cranePlatformOptions []crane.Option) *cobra.Command { // Add the correct authentication to the crane command options authOption := images.WithPullAuth(zarfState.RegistryInfo) - cranePlatformOptions = append(cranePlatformOptions, authOption) + *cranePlatformOptions = append(*cranePlatformOptions, authOption) if tunnel != nil { message.Notef(lang.CmdToolsRegistryTunnel, registryEndpoint, zarfState.RegistryInfo.Address) @@ -149,8 +149,8 @@ func zarfCraneCatalog(cranePlatformOptions []crane.Option) *cobra.Command { } // Wrap the original crane list with a zarf specific version -func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command, cranePlatformOptions []crane.Option, exampleText string, imageNameArgumentIndex int) *cobra.Command { - wrappedCommand := commandToWrap(&cranePlatformOptions) +func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command, cranePlatformOptions *[]crane.Option, exampleText string, imageNameArgumentIndex int) *cobra.Command { + wrappedCommand := commandToWrap(cranePlatformOptions) wrappedCommand.Example = exampleText wrappedCommand.Args = nil @@ -190,7 +190,7 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command // Add the correct authentication to the crane command options authOption := images.WithPushAuth(zarfState.RegistryInfo) - cranePlatformOptions = append(cranePlatformOptions, authOption) + *cranePlatformOptions = append(*cranePlatformOptions, authOption) if tunnel != nil { message.Notef(lang.CmdToolsRegistryTunnel, tunnel.Endpoint(), zarfState.RegistryInfo.Address) From ed2f876dc0ff2fed647b64d62f5a0843d41eed75 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Tue, 11 Jun 2024 10:35:01 -0400 Subject: [PATCH 059/132] feat: remove .metadata.image from schema (#2606) ## Description This was a field reserved for the, now deprecated, Zarf UI ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/types/package.go | 1 - zarf.schema.json | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/types/package.go b/src/types/package.go index 99ac9ef008..5b7e734f3c 100644 --- a/src/types/package.go +++ b/src/types/package.go @@ -47,7 +47,6 @@ type ZarfMetadata struct { Description string `json:"description,omitempty" jsonschema:"description=Additional information about this package"` Version string `json:"version,omitempty" jsonschema:"description=Generic string set by a package author to track the package version (Note: ZarfInitConfigs will always be versioned to the CLIVersion they were created with)"` URL string `json:"url,omitempty" jsonschema:"description=Link to package information when online"` - Image string `json:"image,omitempty" jsonschema:"description=An image URL to embed in this package (Reserved for future use in Zarf UI)"` Uncompressed bool `json:"uncompressed,omitempty" jsonschema:"description=Disable compression of this package"` Architecture string `json:"architecture,omitempty" jsonschema:"description=The target cluster architecture for this package,example=arm64,example=amd64"` YOLO bool `json:"yolo,omitempty" jsonschema:"description=Yaml OnLy Online (YOLO): True enables deploying a Zarf package without first running zarf init against the cluster. This is ideal for connected environments where you want to use existing VCS and container registries."` diff --git a/zarf.schema.json b/zarf.schema.json index 0f43c9dbe5..c26bc550ff 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -1018,10 +1018,6 @@ "type": "string", "description": "Link to package information when online" }, - "image": { - "type": "string", - "description": "An image URL to embed in this package (Reserved for future use in Zarf UI)" - }, "uncompressed": { "type": "boolean", "description": "Disable compression of this package" From fa57f6550787f5e4808a601ea044d42ac7711527 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 11 Jun 2024 18:44:12 +0200 Subject: [PATCH 060/132] refactor: remove use of k8s pods (#2553) ## Description Removes use of k8s pod functions. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/helm/zarf.go | 48 ++++--- src/pkg/cluster/data.go | 111 +++++++++++++++- src/pkg/cluster/injector.go | 184 +++++++++++++------------ src/pkg/cluster/tunnel.go | 26 +++- src/pkg/k8s/common.go | 2 +- src/pkg/k8s/pods.go | 207 ----------------------------- src/pkg/k8s/types.go | 14 -- 7 files changed, 259 insertions(+), 333 deletions(-) delete mode 100644 src/pkg/k8s/pods.go diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index b3a13f8d6e..90c314fae0 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -15,9 +15,10 @@ import ( pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/zarf/src/internal/packager/template" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" @@ -79,18 +80,21 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { } // Get the current agent image from one of its pods. - pods := h.cluster.WaitForPodsAndContainers( - ctx, - k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }, - nil, - ) + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"app": "agent-hook"}}) + if err != nil { + return err + } + listOpts := metav1.ListOptions{ + LabelSelector: selector.String(), + } + podList, err := h.cluster.Clientset.CoreV1().Pods(cluster.ZarfNamespaceName).List(ctx, listOpts) + if err != nil { + return err + } var currentAgentImage transform.Image - if len(pods) > 0 && len(pods[0].Spec.Containers) > 0 { - currentAgentImage, err = transform.ParseImageRef(pods[0].Spec.Containers[0].Image) + if len(podList.Items) > 0 && len(podList.Items[0].Spec.Containers) > 0 { + currentAgentImage, err = transform.ParseImageRef(podList.Items[0].Spec.Containers[0].Image) if err != nil { return fmt.Errorf("unable to parse current agent image reference: %w", err) } @@ -142,13 +146,21 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { defer spinner.Stop() // Force pods to be recreated to get the updated secret. - err = h.cluster.DeletePods( - ctx, - k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }, - ) + // TODO: Explain why no grace period is given. + deleteGracePeriod := int64(0) + deletePolicy := metav1.DeletePropagationForeground + deleteOpts := metav1.DeleteOptions{ + GracePeriodSeconds: &deleteGracePeriod, + PropagationPolicy: &deletePolicy, + } + selector, err = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"app": "agent-hook"}}) + if err != nil { + return err + } + listOpts = metav1.ListOptions{ + LabelSelector: selector.String(), + } + err = h.cluster.Clientset.CoreV1().Pods(cluster.ZarfNamespaceName).DeleteCollection(ctx, deleteOpts, listOpts) if err != nil { return fmt.Errorf("error recycling pods for the Zarf Agent: %w", err) } diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index 9b52b934ab..e47e80cbfe 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -9,26 +9,30 @@ import ( "fmt" "os" "path/filepath" + "sort" "strconv" "strings" "sync" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" "github.com/defenseunicorns/pkg/helpers/v2" + "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/defenseunicorns/zarf/src/types" - corev1 "k8s.io/api/core/v1" ) // HandleDataInjection waits for the target pod(s) to come up and inject the data into them // todo: this currently requires kubectl but we should have enough k8s work to make this native now. func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { defer wg.Done() - injectionCompletionMarker := filepath.Join(componentPath.DataInjections, config.GetDataInjectionMarker()) if err := os.WriteFile(injectionCompletionMarker, []byte("🦄"), helpers.ReadWriteUser); err != nil { message.WarnErrf(err, "Unable to create the data injection completion marker") @@ -68,14 +72,14 @@ iterator: } } - target := k8s.PodLookup{ + target := podLookup{ Namespace: data.Target.Namespace, Selector: data.Target.Selector, Container: data.Target.Container, } // Wait until the pod we are injecting data into becomes available - pods := c.WaitForPodsAndContainers(ctx, target, podFilterByInitContainer) + pods := waitForPodsAndContainers(ctx, c.Clientset, target, podFilterByInitContainer) if len(pods) < 1 { continue } @@ -132,7 +136,7 @@ iterator: } // Do not look for a specific container after injection in case they are running an init container - podOnlyTarget := k8s.PodLookup{ + podOnlyTarget := podLookup{ Namespace: data.Target.Namespace, Selector: data.Target.Selector, } @@ -140,7 +144,7 @@ iterator: // Block one final time to make sure at least one pod has come up and injected the data // Using only the pod as the final selector because we don't know what the container name will be // Still using the init container filter to make sure we have the right running pod - _ = c.WaitForPodsAndContainers(ctx, podOnlyTarget, podFilterByInitContainer) + _ = waitForPodsAndContainers(ctx, c.Clientset, podOnlyTarget, podFilterByInitContainer) // Cleanup now to reduce disk pressure _ = os.RemoveAll(source) @@ -149,3 +153,96 @@ iterator: return } } + +// podLookup is a struct for specifying a pod to target for data injection or lookups. +type podLookup struct { + Namespace string + Selector string + Container string +} + +// podFilter is a function that returns true if the pod should be targeted for data injection or lookups. +type podFilter func(pod corev1.Pod) bool + +// WaitForPodsAndContainers attempts to find pods matching the given selector and optional inclusion filter +// It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names +// If the timeout is reached, an empty list will be returned. +// TODO: Test, refactor and/or remove. +func waitForPodsAndContainers(ctx context.Context, clientset kubernetes.Interface, target podLookup, include podFilter) []corev1.Pod { + waitCtx, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() + + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-waitCtx.Done(): + message.Debug("Pod lookup failed: %v", ctx.Err()) + return nil + case <-timer.C: + listOpts := metav1.ListOptions{ + LabelSelector: target.Selector, + } + podList, err := clientset.CoreV1().Pods(target.Namespace).List(ctx, listOpts) + if err != nil { + message.Debug("Unable to find matching pods: %w", err) + return nil + } + + message.Debug("Found %d pods for target %#v", len(podList.Items), target) + + var readyPods = []corev1.Pod{} + + // Sort the pods from newest to oldest + sort.Slice(podList.Items, func(i, j int) bool { + return podList.Items[i].CreationTimestamp.After(podList.Items[j].CreationTimestamp.Time) + }) + + for _, pod := range podList.Items { + message.Debug("Testing pod %q", pod.Name) + + // If an include function is provided, only keep pods that return true + if include != nil && !include(pod) { + continue + } + + // Handle container targeting + if target.Container != "" { + message.Debug("Testing pod %q for container %q", pod.Name, target.Container) + + // Check the status of initContainers for a running match + for _, initContainer := range pod.Status.InitContainerStatuses { + isRunning := initContainer.State.Running != nil + if initContainer.Name == target.Container && isRunning { + // On running match in initContainer break this loop + readyPods = append(readyPods, pod) + break + } + } + + // Check the status of regular containers for a running match + for _, container := range pod.Status.ContainerStatuses { + isRunning := container.State.Running != nil + if container.Name == target.Container && isRunning { + readyPods = append(readyPods, pod) + break + } + } + } else { + status := pod.Status.Phase + message.Debug("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) + // Regular status checking without a container + if status == corev1.PodRunning { + readyPods = append(readyPods, pod) + break + } + } + } + if len(readyPods) > 0 { + return readyPods + } + timer.Reset(3 * time.Second) + } + } +} diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 2af0694113..177c8a25ac 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -108,7 +108,14 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) // Make sure the pod is not there first - err = c.DeletePod(ctx, ZarfNamespaceName, "injector") + // TODO: Explain why no grace period is given. + deleteGracePeriod := int64(0) + deletePolicy := metav1.DeletePropagationForeground + deleteOpts := metav1.DeleteOptions{ + GracePeriodSeconds: &deleteGracePeriod, + PropagationPolicy: &deletePolicy, + } + err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Delete(ctx, "injector", deleteOpts) if err != nil { message.Debug("could not delete pod injector:", err) } @@ -123,7 +130,7 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag } // Create the pod in the cluster - pod, err = c.CreatePod(ctx, pod) + pod, err = c.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) if err != nil { // Just debug log the output because failures just result in trying the next image message.Debug("error creating pod in cluster:", pod, err) @@ -146,7 +153,8 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag // StopInjectionMadness handles cleanup once the seed registry is up. func (c *Cluster) StopInjectionMadness(ctx context.Context) error { // Try to kill the injector pod now - if err := c.DeletePod(ctx, ZarfNamespaceName, "injector"); err != nil { + err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Delete(ctx, "injector", metav1.DeleteOptions{}) + if err != nil { return err } @@ -375,94 +383,99 @@ func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { // buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []string, payloadShasum string) (*corev1.Pod, error) { - pod := c.GeneratePod("injector", ZarfNamespaceName) executeMode := int32(0777) - - pod.Labels["app"] = "zarf-injector" - - // Ensure zarf agent doesn't break the injector on future runs - pod.Labels[k8s.AgentLabel] = "ignore" - - // Bind the pod to the node the image was found on - pod.Spec.NodeName = node - - // Do not try to restart the pod as it will be deleted/re-created instead - pod.Spec.RestartPolicy = corev1.RestartPolicyNever - - pod.Spec.Containers = []corev1.Container{ - { - Name: "injector", - - // An existing image already present on the cluster - Image: image, - - // PullIfNotPresent because some distros provide a way (even in airgap) to pull images from local or direct-connected registries - ImagePullPolicy: corev1.PullIfNotPresent, - - // This directory's contents come from the init container output - WorkingDir: "/zarf-init", - - // Call the injector with shasum of the tarball - Command: []string{"/zarf-init/zarf-injector", payloadShasum}, - - // Shared mount between the init and regular containers - VolumeMounts: []corev1.VolumeMount{ - { - Name: "init", - MountPath: "/zarf-init/zarf-injector", - SubPath: "zarf-injector", - }, - { - Name: "seed", - MountPath: "/zarf-seed", - }, + pod := &corev1.Pod{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Pod", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "injector", + Namespace: ZarfNamespaceName, + Labels: map[string]string{ + "app": "zarf-injector", + k8s.AgentLabel: "ignore", }, + }, + Spec: corev1.PodSpec{ + NodeName: node, + // Do not try to restart the pod as it will be deleted/re-created instead + RestartPolicy: corev1.RestartPolicyNever, + Containers: []corev1.Container{ + { + Name: "injector", + + // An existing image already present on the cluster + Image: image, + + // PullIfNotPresent because some distros provide a way (even in airgap) to pull images from local or direct-connected registries + ImagePullPolicy: corev1.PullIfNotPresent, + + // This directory's contents come from the init container output + WorkingDir: "/zarf-init", + + // Call the injector with shasum of the tarball + Command: []string{"/zarf-init/zarf-injector", payloadShasum}, + + // Shared mount between the init and regular containers + VolumeMounts: []corev1.VolumeMount{ + { + Name: "init", + MountPath: "/zarf-init/zarf-injector", + SubPath: "zarf-injector", + }, + { + Name: "seed", + MountPath: "/zarf-seed", + }, + }, - // Readiness probe to optimize the pod startup time - ReadinessProbe: &corev1.Probe{ - PeriodSeconds: 2, - SuccessThreshold: 1, - FailureThreshold: 10, - ProbeHandler: corev1.ProbeHandler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/v2/", // path to health check - Port: intstr.FromInt(5000), // port to health check + // Readiness probe to optimize the pod startup time + ReadinessProbe: &corev1.Probe{ + PeriodSeconds: 2, + SuccessThreshold: 1, + FailureThreshold: 10, + ProbeHandler: corev1.ProbeHandler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/v2/", // path to health check + Port: intstr.FromInt(5000), // port to health check + }, + }, }, - }, - }, - // Keep resources as light as possible as we aren't actually running the container's other binaries - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: injectorRequestedCPU, - corev1.ResourceMemory: injectorRequestedMemory, - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: injectorLimitCPU, - corev1.ResourceMemory: injectorLimitMemory, + // Keep resources as light as possible as we aren't actually running the container's other binaries + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: injectorRequestedCPU, + corev1.ResourceMemory: injectorRequestedMemory, + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: injectorLimitCPU, + corev1.ResourceMemory: injectorLimitMemory, + }, + }, }, }, - }, - } - - pod.Spec.Volumes = []corev1.Volume{ - // Contains the rust binary and collection of configmaps from the tarball (seed image). - { - Name: "init", - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: "rust-binary", + Volumes: []corev1.Volume{ + // Contains the rust binary and collection of configmaps from the tarball (seed image). + { + Name: "init", + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: "rust-binary", + }, + DefaultMode: &executeMode, + }, + }, + }, + // Empty directory to hold the seed image (new dir to avoid permission issues) + { + Name: "seed", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, }, - DefaultMode: &executeMode, }, - }, - }, - // Empty directory to hold the seed image (new dir to avoid permission issues) - { - Name: "seed", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }, } @@ -504,14 +517,15 @@ func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeM case <-ctx.Done(): return nil, fmt.Errorf("get image list timed-out: %w", ctx.Err()) case <-timer.C: - pods, err := c.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{ + listOpts := metav1.ListOptions{ FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), - }) + } + podList, err := c.Clientset.CoreV1().Pods(corev1.NamespaceAll).List(ctx, listOpts) if err != nil { return nil, fmt.Errorf("unable to get the list of %q pods in the cluster: %w", corev1.PodRunning, err) } - for _, pod := range pods.Items { + for _, pod := range podList.Items { nodeName := pod.Spec.NodeName nodeDetails, err := c.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 7a2f635016..3ce53fa53e 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -225,10 +225,12 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu zt.remotePort = svc.Spec.Ports[0].TargetPort.IntValue() // if targetPort == 0, look for Port (which is required) if zt.remotePort == 0 { - zt.remotePort, err = c.FindPodContainerPort(ctx, svc) + // TODO: Need a check for if container port is not found + remotePort, err := c.findPodContainerPort(ctx, svc) if err != nil { return TunnelInfo{}, err } + zt.remotePort = remotePort } // Add the url suffix too. @@ -242,6 +244,28 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu return zt, nil } +func (c *Cluster) findPodContainerPort(ctx context.Context, svc corev1.Service) (int, error) { + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: svc.Spec.Selector}) + if err != nil { + return 0, err + } + podList, err := c.Clientset.CoreV1().Pods(svc.Namespace).List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + return 0, err + } + for _, pod := range podList.Items { + // Find the matching name on the port in the pod + for _, container := range pod.Spec.Containers { + for _, port := range container.Ports { + if port.Name == svc.Spec.Ports[0].TargetPort.String() { + return int(port.ContainerPort), nil + } + } + } + } + return 0, nil +} + // TODO: Refactor to use netip.AddrPort instead of a string for nodePortURL. func serviceInfoFromNodePortURL(services []corev1.Service, nodePortURL string) (string, string, int, error) { // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index 42d2da270a..2d24f6d66c 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -86,7 +86,7 @@ func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { } // Get the cluster pod list - pods, err := k.GetAllPods(ctx) + pods, err := k.Clientset.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{}) if err != nil { k.Log("Could not get the pod list: %w", err) timer.Reset(waitDuration) diff --git a/src/pkg/k8s/pods.go b/src/pkg/k8s/pods.go deleted file mode 100644 index cbdc77161b..0000000000 --- a/src/pkg/k8s/pods.go +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "sort" - "time" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GeneratePod creates a new pod without adding it to the k8s cluster. -func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod { - pod := &corev1.Pod{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Pod", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: make(Labels), - }, - } - - return pod -} - -// DeletePod removes a pod from the cluster by namespace & name. -func (k *K8s) DeletePod(ctx context.Context, namespace string, name string) error { - deleteGracePeriod := int64(0) - deletePolicy := metav1.DeletePropagationForeground - - err := k.Clientset.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{ - GracePeriodSeconds: &deleteGracePeriod, - PropagationPolicy: &deletePolicy, - }) - if err != nil { - return err - } - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case <-timer.C: - _, err := k.Clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil - } - - timer.Reset(1 * time.Second) - } - } -} - -// DeletePods removes a collection of pods from the cluster by pod lookup. -func (k *K8s) DeletePods(ctx context.Context, target PodLookup) error { - deleteGracePeriod := int64(0) - deletePolicy := metav1.DeletePropagationForeground - return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection( - ctx, - metav1.DeleteOptions{ - GracePeriodSeconds: &deleteGracePeriod, - PropagationPolicy: &deletePolicy, - }, - metav1.ListOptions{ - LabelSelector: target.Selector, - }, - ) -} - -// CreatePod inserts the given pod into the cluster. -func (k *K8s) CreatePod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, error) { - createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, createOptions) -} - -// GetAllPods returns a list of pods from the cluster for all namespaces. -func (k *K8s) GetAllPods(ctx context.Context) (*corev1.PodList, error) { - return k.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{}) -} - -// GetPods returns a list of pods from the cluster by namespace. -func (k *K8s) GetPods(ctx context.Context, namespace string, listOpts metav1.ListOptions) (*corev1.PodList, error) { - return k.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts) -} - -// WaitForPodsAndContainers attempts to find pods matching the given selector and optional inclusion filter -// It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names -// If the timeout is reached, an empty list will be returned. -func (k *K8s) WaitForPodsAndContainers(ctx context.Context, target PodLookup, include PodFilter) []corev1.Pod { - waitCtx, cancel := context.WithTimeout(ctx, 90*time.Second) - defer cancel() - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-waitCtx.Done(): - k.Log("Pod lookup failed: %v", ctx.Err()) - return nil - case <-timer.C: - pods, err := k.GetPods(ctx, target.Namespace, metav1.ListOptions{ - LabelSelector: target.Selector, - }) - if err != nil { - k.Log("Unable to find matching pods: %w", err) - return nil - } - - k.Log("Found %d pods for target %#v", len(pods.Items), target) - - var readyPods = []corev1.Pod{} - - // Sort the pods from newest to oldest - sort.Slice(pods.Items, func(i, j int) bool { - return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time) - }) - - for _, pod := range pods.Items { - k.Log("Testing pod %q", pod.Name) - - // If an include function is provided, only keep pods that return true - if include != nil && !include(pod) { - continue - } - - // Handle container targeting - if target.Container != "" { - k.Log("Testing pod %q for container %q", pod.Name, target.Container) - - // Check the status of initContainers for a running match - for _, initContainer := range pod.Status.InitContainerStatuses { - isRunning := initContainer.State.Running != nil - if initContainer.Name == target.Container && isRunning { - // On running match in initContainer break this loop - readyPods = append(readyPods, pod) - break - } - } - - // Check the status of regular containers for a running match - for _, container := range pod.Status.ContainerStatuses { - isRunning := container.State.Running != nil - if container.Name == target.Container && isRunning { - readyPods = append(readyPods, pod) - break - } - } - } else { - status := pod.Status.Phase - k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) - // Regular status checking without a container - if status == corev1.PodRunning { - readyPods = append(readyPods, pod) - break - } - } - } - if len(readyPods) > 0 { - return readyPods - } - timer.Reset(3 * time.Second) - } - } -} - -// FindPodContainerPort will find a pod's container port from a service and return it. -// -// Returns 0 if no port is found. -func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) (int, error) { - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: svc.Spec.Selector}) - if err != nil { - return 0, err - } - pods := k.WaitForPodsAndContainers( - ctx, - PodLookup{ - Namespace: svc.Namespace, - Selector: selector.String(), - }, - nil, - ) - - for _, pod := range pods { - // Find the matching name on the port in the pod - for _, container := range pod.Spec.Containers { - for _, port := range container.Ports { - if port.Name == svc.Spec.Ports[0].TargetPort.String() { - return int(port.ContainerPort), nil - } - } - } - } - - return 0, nil -} diff --git a/src/pkg/k8s/types.go b/src/pkg/k8s/types.go index 5c3a2cc36c..409aa2fe96 100644 --- a/src/pkg/k8s/types.go +++ b/src/pkg/k8s/types.go @@ -5,7 +5,6 @@ package k8s import ( - corev1 "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" @@ -14,9 +13,6 @@ import ( // Log is a function that logs a message. type Log func(string, ...any) -// Labels is a map of K8s labels. -type Labels map[string]string - // K8s is a client for interacting with a Kubernetes cluster. type K8s struct { Clientset kubernetes.Interface @@ -25,16 +21,6 @@ type K8s struct { Log Log } -// PodLookup is a struct for specifying a pod to target for data injection or lookups. -type PodLookup struct { - Namespace string `json:"namespace" jsonschema:"description=The namespace to target for data injection"` - Selector string `json:"selector" jsonschema:"description=The K8s selector to target for data injection"` - Container string `json:"container" jsonschema:"description=The container to target for data injection"` -} - -// PodFilter is a function that returns true if the pod should be targeted for data injection or lookups. -type PodFilter func(pod corev1.Pod) bool - // GeneratedPKI is a struct for storing generated PKI data. type GeneratedPKI struct { CA []byte `json:"ca"` From 83da6d2d0ab3a8e2e4df11dab3dd37097cb90cff Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Tue, 11 Jun 2024 15:10:56 -0500 Subject: [PATCH 061/132] fix: pass image reference to syft sbom source object (#2612) ## Description When creating an image SBOM with syft, we currently pass an empty string `""` to `NewFromStereoscopeImageObject` when creating a new image source object. ```go source.NewFromStereoscopeImageObject(syftImage, "", nil) ``` The second argument is the image reference. This data is ultimately used to populate the source metadata in the final SBOM, but since we pass an empty string, the `userInput` field is empty. The fix is to pass in the image reference so that the `userInput` field is populated in the final SBOM. ```go source.NewFromStereoscopeImageObject(syftImage, ref, nil) ``` Steps to reproduce: 1. Create this package ```yaml kind: ZarfPackageConfig metadata: name: sbom-test architecture: amd64 components: - name: sbom-test required: true images: - quay.io/minio/minio:RELEASE.2024-03-21T23-13-43Z ``` 1. Extract the package ```shell zarf tools archiver decompress zarf-package-sbom-test-amd64.tar.zst sbom ``` 1. Extract the sbom tarball ```shell zarf tools archiver decompress sbom/sboms.tar sbom/syft ``` 1. Check the `.source.metadata.userInput` field in the image SBOM ```shell cat sbom/syft/quay.io_minio_minio_RELEASE.2024-03-21T23-13-43Z.json | jq .source.metadata.userInput ``` With current Zarf, you will see that it returns an empty string. Re-run all of the above steps with Zarf built from this PR branch, and you will see it returns the image reference `quay.io/minio/minio:RELEASE.2024-03-21T23-13-43Z` 1. Generate a vulnerability scan report with `grype` ```shell cat sbom/syft/quay.io_minio_minio_RELEASE.2024-03-21T23-13-43Z.json | grype -o sarif > zarf-scan.json ``` You will see that the generated report is missing the image reference in the output `"text": "A medium vulnerability in go-module package: gopkg.in/square/go-jose.v2, version v2.6.0 was found in image at: /usr/bin/minio"` Re-run all of the above steps with Zarf built from this PR branch, and you will see the image reference in the generated report: `"text": "A medium vulnerability in go-module package: gopkg.in/square/go-jose.v2, version v2.6.0 was found in image quay.io/minio/minio:RELEASE.2024-03-21T23-13-43Z at: /usr/bin/minio"` ## Related Issue Fixes #2608 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/sbom/catalog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/internal/packager/sbom/catalog.go b/src/internal/packager/sbom/catalog.go index 2843791e3f..d60d7a70b7 100755 --- a/src/internal/packager/sbom/catalog.go +++ b/src/internal/packager/sbom/catalog.go @@ -161,7 +161,7 @@ func (b *Builder) createImageSBOM(img v1.Image, src string) ([]byte, error) { return nil, err } - syftSource, err := source.NewFromStereoscopeImageObject(syftImage, "", nil) + syftSource, err := source.NewFromStereoscopeImageObject(syftImage, refInfo.Reference, nil) if err != nil { return nil, err } From 5c25319d2ec77be4a39e73d0899f5c38fe53656f Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Wed, 12 Jun 2024 10:13:30 -0500 Subject: [PATCH 062/132] fix: only build a single binary in the init-package make target (#2614) ## Description This PR updates the `init-package` make target to ensure it builds only the binary appropriate for the local system --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 925837fb26..3ec06565a3 100644 --- a/Makefile +++ b/Makefile @@ -124,7 +124,7 @@ build-local-agent-image: ## Build the Zarf agent image to be used in a locally b @ if [ "$(ARCH)" = "arm64" ]; then rm build/zarf-linux-arm64; fi init-package: ## Create the zarf init package (must `brew install coreutils` on macOS and have `docker` first) - @test -s $(ZARF_BIN) || $(MAKE) build-cli + @test -s $(ZARF_BIN) || $(MAKE) $(ZARF_BIN) package create -o build -a $(ARCH) --confirm . # INTERNAL: used to build a release version of the init package with a specific agent image From 3932a1394bb63674de9cf8652040f40003dbff94 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 13 Jun 2024 14:04:19 -0500 Subject: [PATCH 063/132] fix: avoid injector pod name collisions (#2620) ## Description This PR updates the injector pod naming by appending a UUID. This helps avoid collisions with pods in a terminating state from previous zarf init runs. --------- Co-authored-by: Austin Abro --- go.mod | 2 +- src/pkg/cluster/injector.go | 195 ++++++++---------- src/pkg/cluster/injector_test.go | 3 + .../testdata/expected-injection-pod.json | 2 +- src/pkg/packager/deploy.go | 13 +- 5 files changed, 104 insertions(+), 111 deletions(-) diff --git a/go.mod b/go.mod index cc3784ac57..3f8140481c 100644 --- a/go.mod +++ b/go.mod @@ -279,7 +279,7 @@ require ( github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 // indirect + github.com/google/uuid v1.6.0 github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/gookit/color v1.5.4 // indirect diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 177c8a25ac..04cd80a580 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -7,13 +7,13 @@ package cluster import ( "context" "fmt" - "net/http" "os" "path/filepath" "regexp" "time" "github.com/defenseunicorns/pkg/helpers/v2" + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/layout" @@ -21,12 +21,15 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/google/go-containerregistry/pkg/crane" + "github.com/google/uuid" "github.com/mholt/archiver/v3" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" + "sigs.k8s.io/cli-utils/pkg/object" ) // The chunk size for the tarball chunks. @@ -43,7 +46,7 @@ var ( type imageNodeMap map[string][]string // StartInjectionMadness initializes a Zarf injection into the cluster. -func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imagesDir string, injectorSeedSrcs []string) { +func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imagesDir string, injectorSeedSrcs []string) error { spinner := message.NewProgressSpinner("Attempting to bootstrap the seed image into the cluster") defer spinner.Stop() @@ -56,110 +59,130 @@ func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imag } if err := helpers.CreateDirectory(tmp.SeedImagesDir, helpers.ReadWriteExecuteUser); err != nil { - spinner.Fatalf(err, "Unable to create the seed images directory") + return fmt.Errorf("unable to create the seed images directory: %w", err) } var err error var images imageNodeMap var payloadConfigmaps []string var sha256sum string - var seedImages []transform.Image - // Get all the images from the cluster - spinner.Updatef("Getting the list of existing cluster images") findImagesCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() - if images, err = c.getImagesAndNodesForInjection(findImagesCtx); err != nil { - spinner.Fatalf(err, "Unable to generate a list of candidate images to perform the registry injection") + images, err = c.getImagesAndNodesForInjection(findImagesCtx) + if err != nil { + return err } - spinner.Updatef("Creating the injector configmap") if err = c.createInjectorConfigMap(ctx, tmp.InjectionBinary); err != nil { - spinner.Fatalf(err, "Unable to create the injector configmap") + return fmt.Errorf("unable to create the injector configmap: %w", err) } - spinner.Updatef("Creating the injector service") - if service, err := c.createService(ctx); err != nil { - spinner.Fatalf(err, "Unable to create the injector service") - } else { - config.ZarfSeedPort = fmt.Sprintf("%d", service.Spec.Ports[0].NodePort) + service, err := c.createService(ctx) + if err != nil { + return fmt.Errorf("unable to create the injector service: %w", err) } + config.ZarfSeedPort = fmt.Sprintf("%d", service.Spec.Ports[0].NodePort) - spinner.Updatef("Loading the seed image from the package") - if seedImages, err = c.loadSeedImages(imagesDir, tmp.SeedImagesDir, injectorSeedSrcs, spinner); err != nil { - spinner.Fatalf(err, "Unable to load the injector seed image from the package") + _, err = c.loadSeedImages(imagesDir, tmp.SeedImagesDir, injectorSeedSrcs) + if err != nil { + return fmt.Errorf("unable to load the injector seed image from the package: %w", err) } - spinner.Updatef("Loading the seed registry configmaps") if payloadConfigmaps, sha256sum, err = c.createPayloadConfigMaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { - spinner.Fatalf(err, "Unable to generate the injector payload configmaps") + return fmt.Errorf("unable to generate the injector payload configmaps: %w", err) } // https://regex101.com/r/eLS3at/1 zarfImageRegex := regexp.MustCompile(`(?m)^127\.0\.0\.1:`) + var injectorImage string + var injectorNode string // Try to create an injector pod using an existing image in the cluster for image, node := range images { // Don't try to run against the seed image if this is a secondary zarf init run if zarfImageRegex.MatchString(image) { continue } - spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) + injectorImage = image + injectorNode = node[0] + } + // Make sure the pod is not there first + // TODO: Explain why no grace period is given. + deleteGracePeriod := int64(0) + deletePolicy := metav1.DeletePropagationForeground + deleteOpts := metav1.DeleteOptions{ + GracePeriodSeconds: &deleteGracePeriod, + PropagationPolicy: &deletePolicy, + } + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "zarf-injector", + }, + }) + if err != nil { + return err + } + listOpts := metav1.ListOptions{ + LabelSelector: selector.String(), + } + err = c.Clientset.CoreV1().Pods(ZarfNamespaceName).DeleteCollection(ctx, deleteOpts, listOpts) + if err != nil { + return err + } - // Make sure the pod is not there first - // TODO: Explain why no grace period is given. - deleteGracePeriod := int64(0) - deletePolicy := metav1.DeletePropagationForeground - deleteOpts := metav1.DeleteOptions{ - GracePeriodSeconds: &deleteGracePeriod, - PropagationPolicy: &deletePolicy, - } - err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Delete(ctx, "injector", deleteOpts) - if err != nil { - message.Debug("could not delete pod injector:", err) - } - - // Update the podspec image path and use the first node found - - pod, err := c.buildInjectionPod(node[0], image, payloadConfigmaps, sha256sum) - if err != nil { - // Just debug log the output because failures just result in trying the next image - message.Debug("error making injection pod:", err) - continue - } - - // Create the pod in the cluster - pod, err = c.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) - if err != nil { - // Just debug log the output because failures just result in trying the next image - message.Debug("error creating pod in cluster:", pod, err) - continue - } + pod, err := c.buildInjectionPod(injectorNode, injectorImage, payloadConfigmaps, sha256sum) + if err != nil { + return fmt.Errorf("error making injection pod: %w", err) + } - // if no error, try and wait for a seed image to be present, return if successful - if c.injectorIsReady(ctx, seedImages, spinner) { - spinner.Success() - return - } + pod, err = c.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("error creating pod in cluster: %w", err) + } - // Otherwise just continue to try next image + objs := []object.ObjMetadata{ + { + GroupKind: schema.GroupKind{ + Kind: "Pod", + }, + Namespace: ZarfNamespaceName, + Name: pod.Name, + }, + } + waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) + defer waitCancel() + err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + if err != nil { + return err } + spinner.Success() + return nil - // All images were exhausted and still no happiness - spinner.Fatalf(nil, "Unable to perform the injection") } // StopInjectionMadness handles cleanup once the seed registry is up. func (c *Cluster) StopInjectionMadness(ctx context.Context) error { // Try to kill the injector pod now - err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Delete(ctx, "injector", metav1.DeleteOptions{}) + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "zarf-injector", + }, + }) + if err != nil { + return err + } + listOpts := metav1.ListOptions{ + LabelSelector: selector.String(), + } + err = c.Clientset.CoreV1().Pods(ZarfNamespaceName).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts) if err != nil { return err } // Remove the configmaps - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + selector, err = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ "zarf-injector": "payload", }, @@ -167,7 +190,7 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { if err != nil { return err } - listOpts := metav1.ListOptions{ + listOpts = metav1.ListOptions{ LabelSelector: selector.String(), } err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts) @@ -183,13 +206,12 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { return nil } -func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string, spinner *message.Spinner) ([]transform.Image, error) { +func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string) ([]transform.Image, error) { seedImages := []transform.Image{} localReferenceToDigest := make(map[string]string) // Load the injector-specific images and save them as seed-images for _, src := range injectorSeedSrcs { - spinner.Updatef("Loading the seed image '%s' from the package", src) ref, err := transform.ParseImageRef(src) if err != nil { return nil, fmt.Errorf("failed to create ref for image %s: %w", src, err) @@ -230,7 +252,6 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta return configMaps, "", err } - spinner.Updatef("Creating the seed registry archive to send to the cluster") // Create a tar archive of the injector payload if err := archiver.Archive(tarFileList, tarPath); err != nil { return configMaps, "", err @@ -241,8 +262,6 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta return configMaps, "", err } - spinner.Updatef("Splitting the archive into binary configmaps") - chunkCount := len(chunks) // Loop over all chunks and generate configmaps @@ -282,43 +301,6 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta return configMaps, sha256sum, nil } -// Test for pod readiness and seed image presence. -func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Image, spinner *message.Spinner) bool { - tunnel, err := c.NewTunnel(ZarfNamespaceName, SvcResource, ZarfInjectorName, "", 0, ZarfInjectorPort) - if err != nil { - return false - } - - _, err = tunnel.Connect(ctx) - if err != nil { - return false - } - defer tunnel.Close() - - spinner.Updatef("Testing the injector for seed image availability") - - for _, seedImage := range seedImages { - seedRegistry := fmt.Sprintf("%s/v2/%s/manifests/%s", tunnel.HTTPEndpoint(), seedImage.Path, seedImage.Tag) - - var resp *http.Response - var err error - err = tunnel.Wrap(func() error { - message.Debug("getting seed registry %v", seedRegistry) - resp, err = http.Get(seedRegistry) - return err - }) - - if err != nil || resp.StatusCode != 200 { - // Just debug log the output because failures just result in trying the next image - message.Debug(resp, err) - return false - } - } - - spinner.Updatef("Seed image found, injector is ready") - return true -} - func (c *Cluster) createInjectorConfigMap(ctx context.Context, binaryPath string) error { name := "rust-binary" // TODO: Replace with a create or update. @@ -384,13 +366,18 @@ func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { // buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []string, payloadShasum string) (*corev1.Pod, error) { executeMode := int32(0777) + + // Generate a UUID to append to the pod name. + // This prevents collisions where `zarf init` is ran back to back and a previous injector pod still exists. + uuid := uuid.New().String()[:16] + pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ - Name: "injector", + Name: fmt.Sprintf("injector-%s", uuid), Namespace: ZarfNamespaceName, Labels: map[string]string{ "app": "zarf-injector", diff --git a/src/pkg/cluster/injector_test.go b/src/pkg/cluster/injector_test.go index 3d9ccb1a36..16005126f0 100644 --- a/src/pkg/cluster/injector_test.go +++ b/src/pkg/cluster/injector_test.go @@ -77,6 +77,9 @@ func TestBuildInjectionPod(t *testing.T) { c := &Cluster{} pod, err := c.buildInjectionPod("injection-node", "docker.io/library/ubuntu:latest", []string{"foo", "bar"}, "shasum") require.NoError(t, err) + require.Contains(t, pod.Name, "injector-") + // Replace the random UUID in the pod name with a fixed placeholder for consistent comparison. + pod.ObjectMeta.Name = "injector-UUID" b, err := json.Marshal(pod) require.NoError(t, err) expected, err := os.ReadFile("./testdata/expected-injection-pod.json") diff --git a/src/pkg/cluster/testdata/expected-injection-pod.json b/src/pkg/cluster/testdata/expected-injection-pod.json index 30f2e5b1f1..a0c41072d3 100644 --- a/src/pkg/cluster/testdata/expected-injection-pod.json +++ b/src/pkg/cluster/testdata/expected-injection-pod.json @@ -1 +1 @@ -{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} +{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector-UUID","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 4fe2b770e6..7c63173b42 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -237,13 +237,13 @@ func (p *Packager) deployInitComponent(ctx context.Context, component types.Zarf if component.RequiresCluster() && p.state == nil { err = p.cluster.InitZarfState(ctx, p.cfg.InitOpts) if err != nil { - return charts, fmt.Errorf("unable to initialize Zarf state: %w", err) + return nil, fmt.Errorf("unable to initialize Zarf state: %w", err) } } if hasExternalRegistry && (isSeedRegistry || isInjector || isRegistry) { message.Notef("Not deploying the component (%s) since external registry information was provided during `zarf init`", component.Name) - return charts, nil + return nil, nil } if isRegistry { @@ -253,18 +253,21 @@ func (p *Packager) deployInitComponent(ctx context.Context, component types.Zarf // Before deploying the seed registry, start the injector if isSeedRegistry { - p.cluster.StartInjectionMadness(ctx, p.layout.Base, p.layout.Images.Base, component.Images) + err := p.cluster.StartInjectionMadness(ctx, p.layout.Base, p.layout.Images.Base, component.Images) + if err != nil { + return nil, err + } } charts, err = p.deployComponent(ctx, component, isAgent /* skip img checksum if isAgent */, isSeedRegistry /* skip image push if isSeedRegistry */) if err != nil { - return charts, err + return nil, err } // Do cleanup for when we inject the seed registry during initialization if isSeedRegistry { if err := p.cluster.StopInjectionMadness(ctx); err != nil { - return charts, fmt.Errorf("unable to seed the Zarf Registry: %w", err) + return nil, fmt.Errorf("unable to seed the Zarf Registry: %w", err) } } From 7415967772410ceeb77aae7b61367dad85b1516f Mon Sep 17 00:00:00 2001 From: Wayne Starr Date: Thu, 13 Jun 2024 14:41:59 -0600 Subject: [PATCH 064/132] fix: no longer remove the agent ignore label from namespaces (#2623) ## Description Fixes namespaces being able to be excluded from Zarf's mutation (deleting the label forces everything through the Zarf agent). ## Related Issue Fixes #N/A ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [X] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --------- Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/pkg/cluster/namespace.go | 1 - src/test/e2e/26_simple_packages_test.go | 16 +++++++++++++ .../26-agent-ignore/manifests/deployment.yaml | 20 ++++++++++++++++ .../26-agent-ignore/manifests/namespace.yaml | 6 +++++ src/test/packages/26-agent-ignore/zarf.yaml | 23 +++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 src/test/packages/26-agent-ignore/manifests/deployment.yaml create mode 100644 src/test/packages/26-agent-ignore/manifests/namespace.yaml create mode 100644 src/test/packages/26-agent-ignore/zarf.yaml diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index f7e776146d..6a35ee6a56 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -67,6 +67,5 @@ func AdoptZarfManagedLabels(labels map[string]string) map[string]string { labels = make(map[string]string) } labels[k8s.ZarfManagedByLabel] = "zarf" - delete(labels, k8s.AgentLabel) return labels } diff --git a/src/test/e2e/26_simple_packages_test.go b/src/test/e2e/26_simple_packages_test.go index c7a96e80d6..347aad89fd 100644 --- a/src/test/e2e/26_simple_packages_test.go +++ b/src/test/e2e/26_simple_packages_test.go @@ -65,3 +65,19 @@ func TestManifests(t *testing.T) { stdOut, stdErr, err = e2e.Zarf("package", "remove", "manifests", "--confirm") require.NoError(t, err, stdOut, stdErr) } + +func TestAgentIgnore(t *testing.T) { + t.Log("E2E: Test Manifests that are Agent Ignored") + e2e.SetupWithCluster(t) + + testCreate := filepath.Join("src", "test", "packages", "26-agent-ignore") + testDeploy := filepath.Join("build", fmt.Sprintf("zarf-package-agent-ignore-namespace-%s.tar.zst", e2e.Arch)) + + // Create the agent ignore test package + stdOut, stdErr, err := e2e.Zarf("package", "create", testCreate, "-o", "build", "--confirm") + require.NoError(t, err, stdOut, stdErr) + + // Deploy the agent ignore test package + stdOut, stdErr, err = e2e.Zarf("package", "deploy", testDeploy, "--confirm") + require.NoError(t, err, stdOut, stdErr) +} diff --git a/src/test/packages/26-agent-ignore/manifests/deployment.yaml b/src/test/packages/26-agent-ignore/manifests/deployment.yaml new file mode 100644 index 0000000000..46bbab17c1 --- /dev/null +++ b/src/test/packages/26-agent-ignore/manifests/deployment.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: httpd-deployment +spec: + selector: + matchLabels: + app: httpd + replicas: 2 # tells deployment to run 2 pods matching the template + template: + metadata: + labels: + app: httpd + spec: + containers: + - name: httpd + # This is explicitly a different tag than examples/manifests to ensure it has to pull the image from outside the cluster + image: "httpd:alpine" + ports: + - containerPort: 80 diff --git a/src/test/packages/26-agent-ignore/manifests/namespace.yaml b/src/test/packages/26-agent-ignore/manifests/namespace.yaml new file mode 100644 index 0000000000..53e30073c4 --- /dev/null +++ b/src/test/packages/26-agent-ignore/manifests/namespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: httpd-ignored + labels: + zarf.dev/agent: ignore diff --git a/src/test/packages/26-agent-ignore/zarf.yaml b/src/test/packages/26-agent-ignore/zarf.yaml new file mode 100644 index 0000000000..d945eef803 --- /dev/null +++ b/src/test/packages/26-agent-ignore/zarf.yaml @@ -0,0 +1,23 @@ +kind: ZarfPackageConfig +metadata: + name: agent-ignore-namespace + description: Simple test to check that Zarf respects ignored namespaces. + +components: + - name: httpd-deployment + required: true + manifests: + - name: agent-ignore-httpd + namespace: httpd-ignored + files: + - manifests/deployment.yaml + - manifests/namespace.yaml + actions: + onDeploy: + after: + - wait: + cluster: + kind: deployment + name: httpd-deployment + namespace: httpd-ignored + condition: "{.status.readyReplicas}=2" From f744036c71cf0e4fe8fc32063c3a8c6c98fd402a Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 14 Jun 2024 15:16:55 +0200 Subject: [PATCH 065/132] refactor: remove use of k8s secret (#2565) ## Description Removes use of k8s secret functions. ## Related Issue Relates to #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/internal/packager/helm/post-render.go | 45 +++++++--- src/pkg/cluster/secrets.go | 58 +++++++++---- src/pkg/cluster/state.go | 27 +++--- src/pkg/cluster/zarf.go | 56 ++++++++---- src/pkg/k8s/secrets.go | 100 ---------------------- src/pkg/packager/remove.go | 73 ++++++++++++---- 6 files changed, 190 insertions(+), 169 deletions(-) delete mode 100644 src/pkg/k8s/secrets.go diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 0a44acd06b..92b912ae63 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -159,24 +159,49 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { continue } - // Create the secret - validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) - // Try to get a valid existing secret - currentRegistrySecret, _ := c.GetSecret(ctx, name, config.ZarfImagePullSecretName) + validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) + // TODO: Refactor as error is not checked instead of checking for not found error. + currentRegistrySecret, _ := c.Clientset.CoreV1().Secrets(name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) if currentRegistrySecret.Name != config.ZarfImagePullSecretName || !reflect.DeepEqual(currentRegistrySecret.Data, validRegistrySecret.Data) { - // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(ctx, validRegistrySecret); err != nil { + err := func() error { + _, err := c.Clientset.CoreV1().Secrets(validRegistrySecret.Namespace).Create(ctx, validRegistrySecret, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return err + } + if err == nil { + return nil + } + _, err = c.Clientset.CoreV1().Secrets(validRegistrySecret.Namespace).Update(ctx, validRegistrySecret, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }() + if err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", name) } - // Generate the git server secret - gitServerSecret := c.GenerateGitPullCreds(name, config.ZarfGitServerSecretName, r.state.GitServer) - // Create or update the zarf git server secret - if _, err := c.CreateOrUpdateSecret(ctx, gitServerSecret); err != nil { + gitServerSecret := c.GenerateGitPullCreds(name, config.ZarfGitServerSecretName, r.state.GitServer) + err = func() error { + _, err := c.Clientset.CoreV1().Secrets(gitServerSecret.Namespace).Create(ctx, gitServerSecret, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return err + } + if err == nil { + return nil + } + _, err = c.Clientset.CoreV1().Secrets(gitServerSecret.Namespace).Update(ctx, gitServerSecret, metav1.UpdateOptions{}) + if err != nil { + return err + } + return nil + }() + if err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", name) } + } } return nil diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index a7190c8037..cfbb90953e 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -34,8 +34,6 @@ type DockerConfigEntryWithAuth struct { // GenerateRegistryPullCreds generates a secret containing the registry credentials. func (c *Cluster) GenerateRegistryPullCreds(namespace, name string, registryInfo types.RegistryInfo) *corev1.Secret { - secretDockerConfig := c.GenerateSecret(namespace, name, corev1.SecretTypeDockerConfigJson) - // Auth field must be username:password and base64 encoded fieldValue := registryInfo.PullUsername + ":" + registryInfo.PullPassword authEncodedValue := base64.StdEncoding.EncodeToString([]byte(fieldValue)) @@ -56,9 +54,23 @@ func (c *Cluster) GenerateRegistryPullCreds(namespace, name string, registryInfo message.WarnErrf(err, "Unable to marshal the .dockerconfigjson secret data for the image pull secret") } - // Add to the secret data - secretDockerConfig.Data[".dockerconfigjson"] = dockerConfigData - + secretDockerConfig := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": dockerConfigData, + }, + } return secretDockerConfig } @@ -66,12 +78,25 @@ func (c *Cluster) GenerateRegistryPullCreds(namespace, name string, registryInfo func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo types.GitServerInfo) *corev1.Secret { message.Debugf("k8s.GenerateGitPullCreds(%s, %s, gitServerInfo)", namespace, name) - gitServerSecret := c.GenerateSecret(namespace, name, corev1.SecretTypeOpaque) - gitServerSecret.StringData = map[string]string{ - "username": gitServerInfo.PullUsername, - "password": gitServerInfo.PullPassword, + gitServerSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{}, + StringData: map[string]string{ + "username": gitServerInfo.PullUsername, + "password": gitServerInfo.PullPassword, + }, } - return gitServerSecret } @@ -87,7 +112,7 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type } else { // Update all image pull secrets for _, namespace := range namespaceList.Items { - currentRegistrySecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfImagePullSecretName) + currentRegistrySecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) if err != nil { continue } @@ -97,11 +122,10 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) - // Create the secret newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) if !reflect.DeepEqual(currentRegistrySecret.Data, newRegistrySecret.Data) { - // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(ctx, newRegistrySecret); err != nil { + _, err := c.Clientset.CoreV1().Secrets(newRegistrySecret.Namespace).Update(ctx, newRegistrySecret, metav1.UpdateOptions{}) + if err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", namespace.Name) } } @@ -123,7 +147,7 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. } else { // Update all git pull secrets for _, namespace := range namespaceList.Items { - currentGitSecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfGitServerSecretName) + currentGitSecret, err := c.Clientset.CoreV1().Secrets(namespace.Name).Get(ctx, config.ZarfGitServerSecretName, metav1.GetOptions{}) if err != nil { continue } @@ -136,8 +160,8 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. // Create the secret newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) if !reflect.DeepEqual(currentGitSecret.StringData, newGitSecret.StringData) { - // Create or update the zarf git secret - if _, err := c.CreateOrUpdateSecret(ctx, newGitSecret); err != nil { + _, err := c.Clientset.CoreV1().Secrets(newGitSecret.Namespace).Update(ctx, newGitSecret, metav1.UpdateOptions{}) + if err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", namespace.Name) } } diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index ba0cf0ee95..64b8162c21 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -27,6 +27,7 @@ import ( // Zarf Cluster Constants. const ( + ZarfManagedByLabel = "app.kubernetes.io/managed-by" ZarfNamespaceName = "zarf" ZarfStateSecretName = "zarf-state" ZarfStateDataKey = "state" @@ -214,7 +215,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // LoadZarfState returns the current zarf/zarf-state secret data or an empty ZarfState. func (c *Cluster) LoadZarfState(ctx context.Context) (state *types.ZarfState, err error) { // Set up the API connection - secret, err := c.GetSecret(ctx, ZarfNamespaceName, ZarfStateSecretName) + secret, err := c.Clientset.CoreV1().Secrets(ZarfNamespaceName).Get(ctx, ZarfStateSecretName, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("%w. %s", err, message.ColorWrap("Did you remember to zarf init?", color.Bold)) } @@ -267,17 +268,10 @@ func (c *Cluster) debugPrintZarfState(state *types.ZarfState) { func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) error { c.debugPrintZarfState(state) - // Convert the data back to JSON. data, err := json.Marshal(&state) if err != nil { return err } - - // Set up the data wrapper. - dataWrapper := make(map[string][]byte) - dataWrapper[ZarfStateDataKey] = data - - // The secret object. secret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), @@ -291,14 +285,23 @@ func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) err }, }, Type: corev1.SecretTypeOpaque, - Data: dataWrapper, + Data: map[string][]byte{ + ZarfStateDataKey: data, + }, } // Attempt to create or update the secret and return. - if _, err := c.CreateOrUpdateSecret(ctx, secret); err != nil { - return fmt.Errorf("unable to create the zarf state secret") + _, err = c.Clientset.CoreV1().Secrets(secret.Namespace).Create(ctx, secret, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create the zarf state secret: %w", err) + } + if err == nil { + return nil + } + _, err = c.Clientset.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("unable to update the zarf state secret: %w", err) } - return nil } diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 921268e6a6..787852b761 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -12,13 +12,15 @@ import ( "strings" "time" + autoscalingV2 "k8s.io/api/autoscaling/v2" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" - autoscalingV2 "k8s.io/api/autoscaling/v2" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // GetDeployedZarfPackages gets metadata information about packages that have been deployed to the cluster. @@ -26,7 +28,8 @@ import ( // Returns a list of DeployedPackage structs and a list of errors. func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.DeployedPackage, error) { // Get the secrets that describe the deployed packages - secrets, err := c.GetSecretsWithLabel(ctx, ZarfNamespaceName, ZarfPackageInfoLabel) + listOpts := metav1.ListOptions{LabelSelector: ZarfPackageInfoLabel} + secrets, err := c.Clientset.CoreV1().Secrets(ZarfNamespaceName).List(ctx, listOpts) if err != nil { return nil, err } @@ -54,7 +57,7 @@ func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.Deployed // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. func (c *Cluster) GetDeployedPackage(ctx context.Context, packageName string) (deployedPackage *types.DeployedPackage, err error) { // Get the secret that describes the deployed package - secret, err := c.GetSecret(ctx, ZarfNamespaceName, config.ZarfPackagePrefix+packageName) + secret, err := c.Clientset.CoreV1().Secrets(ZarfNamespaceName).Get(ctx, config.ZarfPackagePrefix+packageName, metav1.GetOptions{}) if err != nil { return deployedPackage, err } @@ -178,11 +181,6 @@ func (c *Cluster) RecordPackageDeploymentAndWait(ctx context.Context, pkg types. func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int) (deployedPackage *types.DeployedPackage, err error) { packageName := pkg.Metadata.Name - // Generate a secret that describes the package that is being deployed - secretName := config.ZarfPackagePrefix + packageName - deployedPackageSecret := c.GenerateSecret(ZarfNamespaceName, secretName, corev1.SecretTypeOpaque) - deployedPackageSecret.Labels[ZarfPackageInfoLabel] = packageName - // Attempt to load information about webhooks for the package var componentWebhooks map[string]map[string]types.Webhook existingPackageSecret, err := c.GetDeployedPackage(ctx, packageName) @@ -209,16 +207,44 @@ func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg types.ZarfPac } // Update the package secret - deployedPackageSecret.Data = map[string][]byte{"data": packageData} - var updatedSecret *corev1.Secret - if updatedSecret, err = c.CreateOrUpdateSecret(ctx, deployedPackageSecret); err != nil { + deployedPackageSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: config.ZarfPackagePrefix + packageName, + Namespace: ZarfNamespaceName, + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + ZarfPackageInfoLabel: packageName, + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "data": packageData, + }, + } + updatedSecret, err := func() (*corev1.Secret, error) { + secret, err := c.Clientset.CoreV1().Secrets(deployedPackageSecret.Namespace).Create(ctx, deployedPackageSecret, metav1.CreateOptions{}) + if err != nil && !kerrors.IsAlreadyExists(err) { + return nil, err + } + if err == nil { + return secret, nil + } + secret, err = c.Clientset.CoreV1().Secrets(deployedPackageSecret.Namespace).Update(ctx, deployedPackageSecret, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + return secret, nil + }() + if err != nil { return nil, fmt.Errorf("failed to record package deployment in secret '%s'", deployedPackageSecret.Name) } - if err := json.Unmarshal(updatedSecret.Data["data"], &deployedPackage); err != nil { return nil, err } - return deployedPackage, nil } diff --git a/src/pkg/k8s/secrets.go b/src/pkg/k8s/secrets.go deleted file mode 100644 index 82b9ef52de..0000000000 --- a/src/pkg/k8s/secrets.go +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "crypto/tls" - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// GetSecret returns a Kubernetes secret. -func (k *K8s) GetSecret(ctx context.Context, namespace, name string) (*corev1.Secret, error) { - return k.Clientset.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) -} - -// GetSecretsWithLabel returns a list of Kubernetes secrets with the given label. -func (k *K8s) GetSecretsWithLabel(ctx context.Context, namespace, labelSelector string) (*corev1.SecretList, error) { - listOptions := metav1.ListOptions{LabelSelector: labelSelector} - return k.Clientset.CoreV1().Secrets(namespace).List(ctx, listOptions) -} - -// GenerateSecret returns a Kubernetes secret object without applying it to the cluster. -func (k *K8s) GenerateSecret(namespace, name string, secretType corev1.SecretType) *corev1.Secret { - return &corev1.Secret{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Secret", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - Labels: map[string]string{ - ZarfManagedByLabel: "zarf", - }, - }, - Type: secretType, - Data: map[string][]byte{}, - } -} - -// GenerateTLSSecret returns a Kubernetes secret object without applying it to the cluster. -func (k *K8s) GenerateTLSSecret(namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { - if _, err := tls.X509KeyPair(conf.Cert, conf.Key); err != nil { - return nil, err - } - - secretTLS := k.GenerateSecret(namespace, name, corev1.SecretTypeTLS) - secretTLS.Data[corev1.TLSCertKey] = conf.Cert - secretTLS.Data[corev1.TLSPrivateKeyKey] = conf.Key - - return secretTLS, nil -} - -// CreateOrUpdateTLSSecret creates or updates a Kubernetes secret with a new TLS secret. -func (k *K8s) CreateOrUpdateTLSSecret(ctx context.Context, namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { - secret, err := k.GenerateTLSSecret(namespace, name, conf) - if err != nil { - return secret, err - } - - return k.CreateOrUpdateSecret(ctx, secret) -} - -// DeleteSecret deletes a Kubernetes secret. -func (k *K8s) DeleteSecret(ctx context.Context, secret *corev1.Secret) error { - namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - - err := namespaceSecrets.Delete(ctx, secret.Name, metav1.DeleteOptions{}) - if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("error deleting the secret: %w", err) - } - - return nil -} - -// CreateOrUpdateSecret creates or updates a Kubernetes secret. -func (k *K8s) CreateOrUpdateSecret(ctx context.Context, secret *corev1.Secret) (createdSecret *corev1.Secret, err error) { - - namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - - if _, err = k.GetSecret(ctx, secret.Namespace, secret.Name); err != nil { - // create the given secret - if createdSecret, err = namespaceSecrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil { - return createdSecret, fmt.Errorf("unable to create the secret: %w", err) - } - } else { - // update the given secret - if createdSecret, err = namespaceSecrets.Update(ctx, secret, metav1.UpdateOptions{}); err != nil { - return createdSecret, fmt.Errorf("unable to update the secret: %w", err) - } - } - - return createdSecret, nil -} diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index 5169e53cbf..091f446ae2 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -10,10 +10,14 @@ import ( "errors" "fmt" "runtime" - "slices" "github.com/defenseunicorns/pkg/helpers/v2" + "helm.sh/helm/v3/pkg/storage/driver" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/pkg/cluster" @@ -22,8 +26,6 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/packager/filters" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/types" - "helm.sh/helm/v3/pkg/storage/driver" - corev1 "k8s.io/api/core/v1" ) // Remove removes a package that was already deployed onto a cluster, uninstalling all installed helm charts. @@ -104,25 +106,60 @@ func (p *Packager) Remove(ctx context.Context) (err error) { return nil } -func (p *Packager) updatePackageSecret(ctx context.Context, deployedPackage types.DeployedPackage) { +func (p *Packager) updatePackageSecret(ctx context.Context, deployedPackage types.DeployedPackage) error { // Only attempt to update the package secret if we are actually connected to a cluster if p.cluster != nil { + newPackageSecretData, err := json.Marshal(deployedPackage) + if err != nil { + return err + } + secretName := config.ZarfPackagePrefix + deployedPackage.Name // Save the new secret with the removed components removed from the secret - newPackageSecret := p.cluster.GenerateSecret(cluster.ZarfNamespaceName, secretName, corev1.SecretTypeOpaque) - newPackageSecret.Labels[cluster.ZarfPackageInfoLabel] = deployedPackage.Name - - newPackageSecretData, _ := json.Marshal(deployedPackage) - newPackageSecret.Data["data"] = newPackageSecretData - - _, err := p.cluster.CreateOrUpdateSecret(ctx, newPackageSecret) + newPackageSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: cluster.ZarfNamespaceName, + Labels: map[string]string{ + cluster.ZarfManagedByLabel: "zarf", + cluster.ZarfPackageInfoLabel: deployedPackage.Name, + }, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{ + "data": newPackageSecretData, + }, + } + err = func() error { + _, err := p.cluster.Clientset.CoreV1().Secrets(newPackageSecret.Namespace).Get(ctx, newPackageSecret.Name, metav1.GetOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return err + } + if kerrors.IsNotFound(err) { + _, err = p.cluster.Clientset.CoreV1().Secrets(newPackageSecret.Namespace).Create(ctx, newPackageSecret, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("unable to create the zarf state secret: %w", err) + } + return nil + } + _, err = p.cluster.Clientset.CoreV1().Secrets(newPackageSecret.Namespace).Update(ctx, newPackageSecret, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("unable to update the zarf state secret: %w", err) + } + return nil + }() // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { message.Warnf("Unable to update the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } } + return nil } func (p *Packager) removeComponent(ctx context.Context, deployedPackage *types.DeployedPackage, deployedComponent types.DeployedComponent, spinner *message.Spinner) (*types.DeployedPackage, error) { @@ -165,7 +202,10 @@ func (p *Packager) removeComponent(ctx context.Context, deployedPackage *types.D deployedComponent.InstalledCharts = helpers.RemoveMatches(deployedComponent.InstalledCharts, func(t types.InstalledChart) bool { return t.ChartName == chart.ChartName }) - p.updatePackageSecret(ctx, *deployedPackage) + err := p.updatePackageSecret(ctx, *deployedPackage) + if err != nil { + return nil, err + } } if err := actions.Run(onRemove.Defaults, onRemove.After, nil); err != nil { @@ -187,19 +227,22 @@ func (p *Packager) removeComponent(ctx context.Context, deployedPackage *types.D secretName := config.ZarfPackagePrefix + deployedPackage.Name // All the installed components were deleted, therefore this package is no longer actually deployed - packageSecret, err := p.cluster.GetSecret(ctx, cluster.ZarfNamespaceName, secretName) + packageSecret, err := p.cluster.Clientset.CoreV1().Secrets(cluster.ZarfNamespaceName).Get(ctx, secretName, metav1.GetOptions{}) // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } else { - err = p.cluster.DeleteSecret(ctx, packageSecret) + err = p.cluster.Clientset.CoreV1().Secrets(packageSecret.Namespace).Delete(ctx, packageSecret.Name, metav1.DeleteOptions{}) if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } } } else { - p.updatePackageSecret(ctx, *deployedPackage) + err := p.updatePackageSecret(ctx, *deployedPackage) + if err != nil { + return nil, err + } } return deployedPackage, nil From 4c4ac5b228a97d024e6cf90dccfa6d84d29568f2 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:47:48 -0400 Subject: [PATCH 066/132] fix: using a new s3 backend for test data (#2630) ## Description s3 we were using for examples was broken ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- examples/kiwix/zarf.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/kiwix/zarf.yaml b/examples/kiwix/zarf.yaml index 0ddf467086..bb91def028 100644 --- a/examples/kiwix/zarf.yaml +++ b/examples/kiwix/zarf.yaml @@ -33,7 +33,7 @@ components: onCreate: before: # Download a .zim file of a DevOps Stack Exchange snapshot into the data directory for use with Kiwix - - cmd: curl https://zarf-examples.s3.amazonaws.com/devops.stackexchange.com_en_all_2023-05.zim -o zim-data/devops.stackexchange.com_en_all_2023-05.zim + - cmd: curl https://zarf-remote.s3.us-east-2.amazonaws.com/testdata/devops.stackexchange.com_en_all_2023-05.zim -o zim-data/devops.stackexchange.com_en_all_2023-05.zim # Below are some more examples of *.zim files of available content: # https://library.kiwix.org/?lang=eng # NOTE: If `zarf package create`ing regularly you should mirror content to a web host you control to be a friendly neighbor From c8abe31a5c4bbed933d692fcdeeefdc4b31701a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 07:53:14 -0400 Subject: [PATCH 067/132] chore(deps): update goreleaser/goreleaser-action action to v6 (#2596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [goreleaser/goreleaser-action](https://togithub.com/goreleaser/goreleaser-action) | action | major | `v5.0.0` -> `v6.0.0` | --- > [!WARNING] > Some dependencies could not be looked up. Check the Dependency Dashboard for more information. --- ### Release Notes
goreleaser/goreleaser-action (goreleaser/goreleaser-action) ### [`v6.0.0`](https://togithub.com/goreleaser/goreleaser-action/releases/tag/v6.0.0) [Compare Source](https://togithub.com/goreleaser/goreleaser-action/compare/v5.1.0...v6.0.0) > \[!WARNING] > **This is a breaking change!** > > Follow the instructions [here](https://goreleaser.com/blog/goreleaser-v2/#upgrading) to upgrade! ##### What's Changed - feat!: use "~> v2" as default by [@​caarlos0](https://togithub.com/caarlos0) in [https://github.com/goreleaser/goreleaser-action/pull/463](https://togithub.com/goreleaser/goreleaser-action/pull/463) **Full Changelog**: https://github.com/goreleaser/goreleaser-action/compare/v5...v6.0.0 ### [`v5.1.0`](https://togithub.com/goreleaser/goreleaser-action/releases/tag/v5.1.0) [Compare Source](https://togithub.com/goreleaser/goreleaser-action/compare/v5.0.0...v5.1.0) #### Important This version changes the default behavior of `latest` to `~> v1`. The next major of this action (v6), will change this to `~> v2`, and will be launched together with GoReleaser v2. #### What's Changed - docs: bump actions to latest major by [@​crazy-max](https://togithub.com/crazy-max) in [https://github.com/goreleaser/goreleaser-action/pull/435](https://togithub.com/goreleaser/goreleaser-action/pull/435) - chore(deps): bump docker/bake-action from 3 to 4 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/436](https://togithub.com/goreleaser/goreleaser-action/pull/436) - chore(deps): bump codecov/codecov-action from 3 to 4 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/437](https://togithub.com/goreleaser/goreleaser-action/pull/437) - chore(deps): bump actions/setup-go from 4 to 5 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/443](https://togithub.com/goreleaser/goreleaser-action/pull/443) - chore(deps): bump actions/upload-artifact from 3 to 4 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/444](https://togithub.com/goreleaser/goreleaser-action/pull/444) - Delete .kodiak.toml by [@​vedantmgoyal9](https://togithub.com/vedantmgoyal9) in [https://github.com/goreleaser/goreleaser-action/pull/446](https://togithub.com/goreleaser/goreleaser-action/pull/446) - chore(deps): bump codecov/codecov-action from 3 to 4 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/448](https://togithub.com/goreleaser/goreleaser-action/pull/448) - chore(deps): bump ip from 2.0.0 to 2.0.1 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/450](https://togithub.com/goreleaser/goreleaser-action/pull/450) - Upgrade setup-go action version in README by [@​kishaningithub](https://togithub.com/kishaningithub) in [https://github.com/goreleaser/goreleaser-action/pull/455](https://togithub.com/goreleaser/goreleaser-action/pull/455) - chore(deps): bump tar from 6.1.14 to 6.2.1 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/456](https://togithub.com/goreleaser/goreleaser-action/pull/456) - chore: use corepack to install yarn by [@​crazy-max](https://togithub.com/crazy-max) in [https://github.com/goreleaser/goreleaser-action/pull/458](https://togithub.com/goreleaser/goreleaser-action/pull/458) - feat: lock this major version of the action to use '~> v1' as 'latest' by [@​caarlos0](https://togithub.com/caarlos0) in [https://github.com/goreleaser/goreleaser-action/pull/461](https://togithub.com/goreleaser/goreleaser-action/pull/461) - chore(deps): bump semver from 7.6.0 to 7.6.2 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/462](https://togithub.com/goreleaser/goreleaser-action/pull/462) - chore(deps): bump [@​actions/http-client](https://togithub.com/actions/http-client) from 2.2.0 to 2.2.1 by [@​dependabot](https://togithub.com/dependabot) in [https://github.com/goreleaser/goreleaser-action/pull/451](https://togithub.com/goreleaser/goreleaser-action/pull/451) #### New Contributors - [@​vedantmgoyal9](https://togithub.com/vedantmgoyal9) made their first contribution in [https://github.com/goreleaser/goreleaser-action/pull/446](https://togithub.com/goreleaser/goreleaser-action/pull/446) **Full Changelog**: https://github.com/goreleaser/goreleaser-action/compare/v5.0.0...v5.1.0
--- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/defenseunicorns/zarf). --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lucas Rodriguez Co-authored-by: Austin Abro --- .github/workflows/release.yml | 6 +++--- .goreleaser.yaml | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba6ecc10e3..2f23f8c9c3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -184,11 +184,11 @@ jobs: # Create the GitHub release notes, upload artifact backups to S3, publish homebrew recipe - name: Run GoReleaser - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 with: distribution: goreleaser - version: latest - args: release --rm-dist --debug + version: "~> v2" + args: release --clean --verbose env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}} HOMEBREW_TAP_GITHUB_TOKEN: ${{ steps.brew-tap-token.outputs.token }} diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 2699443597..8743d5d263 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -1,3 +1,5 @@ +version: 2 + before: hooks: - go mod tidy @@ -72,7 +74,7 @@ release: # Update the 'generic' brew formula and create a versioned brew formula for artifacts from this release brews: - name: "{{ .Env.BREW_NAME }}" - tap: + repository: owner: defenseunicorns name: homebrew-tap token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" @@ -91,7 +93,7 @@ brews: # NOTE: We are using .Version instead of .Tag because homebrew has weird semver parsing rules and won't be able to # install versioned releases that has a `v` character before the version number. - name: "zarf@{{ .Version }}" - tap: + repository: owner: defenseunicorns name: homebrew-tap token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" @@ -111,4 +113,4 @@ blobs: - provider: s3 region: us-gov-west-1 bucket: zarf-public - folder: "release/{{.Version}}" + directory: "release/{{.Version}}" From d02c51a192462040f9083119444c8e2c19ef0257 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Thu, 20 Jun 2024 13:37:50 +0200 Subject: [PATCH 068/132] refactor: remove unused constants and variables (#2633) ## Description Removes unused constants and variables. ## Related Issue N/A ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/config/config.go | 5 ----- src/config/lang/english.go | 15 --------------- src/pkg/cluster/state.go | 11 +++++------ src/pkg/layout/constants.go | 4 ---- 4 files changed, 5 insertions(+), 30 deletions(-) diff --git a/src/config/config.go b/src/config/config.go index f603e317a2..53e49d720b 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -5,7 +5,6 @@ package config import ( - "embed" "fmt" "os" "path/filepath" @@ -20,9 +19,6 @@ import ( const ( GithubProject = "defenseunicorns/zarf" - // ZarfMaxChartNameLength limits helm chart name size to account for K8s/helm limits and zarf prefix - ZarfMaxChartNameLength = 40 - ZarfAgentHost = "agent-hook.zarf.svc" ZarfConnectLabelName = "zarf.dev/connect-name" @@ -77,7 +73,6 @@ var ( NoColor bool CosignPublicKey string - ZarfSchema embed.FS // Timestamp of when the CLI was started operationStartTime = time.Now().Unix() diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 36112f99a9..f84563ed2d 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -19,9 +19,6 @@ import ( const ( ErrLoadState = "Failed to load the Zarf State from the cluster." ErrSaveState = "Failed to save the Zarf State to the cluster." - ErrLoadPackageSecret = "Failed to load %s's secret from the cluster" - ErrNoClusterConnection = "Failed to connect to the cluster." - ErrTunnelFailed = "Failed to create a tunnel to the cluster." ErrUnmarshal = "failed to unmarshal file: %w" ErrWritingFile = "failed to write file %s: %s" ErrDownloading = "failed to download %s: %s" @@ -316,8 +313,6 @@ $ zarf package mirror-resources \ CmdPackageRemoveShort = "Removes a Zarf package that has been deployed already (runs offline)" CmdPackageRemoveFlagConfirm = "REQUIRED. Confirm the removal action to prevent accidental deletions" CmdPackageRemoveFlagComponents = "Comma-separated list of components to remove. This list will be respected regardless of a component's 'required' or 'default' status. Globbing component names with '*' and deselecting components with a leading '-' are also supported." - CmdPackageRemoveTarballErr = "Invalid tarball path provided" - CmdPackageRemoveExtractErr = "Unable to extract the package contents" CmdPackageRemoveErr = "Unable to remove the package with an error of: %s" CmdPackageRegistryPrefixErr = "Registry must be prefixed with 'oci://'" @@ -397,7 +392,6 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a sk CmdDevLintShort = "Lints the given package for valid schema and recommended practices" CmdDevLintLong = "Verifies the package schema, checks if any variables won't be evaluated, and checks for unpinned images/repos/files" - CmdDevLintErr = "Unable to lint package: %s" // zarf tools CmdToolsShort = "Collection of additional tools to make airgap easier" @@ -556,7 +550,6 @@ zarf tools yq e '.a.b = "cool"' -i file.yaml CmdToolsGenKeySuccess = "Generated key pair and written to %s and %s" CmdToolsSbomShort = "Generates a Software Bill of Materials (SBOM) for the given package" - CmdToolsSbomErr = "Unable to create SBOM (Syft) CLI" CmdToolsWaitForShort = "Waits for a given Kubernetes resource to be ready" CmdToolsWaitForLong = "By default Zarf will wait for all Kubernetes resources to be ready before completion of a component during a deployment.\n" + @@ -658,7 +651,6 @@ $ zarf tools update-creds artifact --artifact-push-username={USERNAME} --artifac // These are only seen in the Kubernetes logs. const ( AgentInfoWebhookAllowed = "Webhook [%s - %s] - Allowed: %t" - AgentInfoShutdown = "Shutdown gracefully..." AgentInfoPort = "Server running in port: %s" AgentErrBadRequest = "could not read request body: %s" @@ -674,8 +666,6 @@ const ( AgentErrMarshallJSONPatch = "unable to marshall the json patch" AgentErrMarshalResponse = "unable to marshal the response" AgentErrNilReq = "malformed admission review: request is nil" - AgentErrShutdown = "unable to properly shutdown the web server" - AgentErrStart = "Failed to start the web server" AgentErrUnableTransform = "unable to transform the provided request; see zarf http proxy logs for more details" ) @@ -690,7 +680,6 @@ const ( PkgValidateTemplateDeprecation = "Package template %q is using the deprecated syntax ###ZARF_PKG_VAR_%s###. This will be removed in Zarf v1.0.0. Please update to ###ZARF_PKG_TMPL_%s###." PkgValidateMustBeUppercase = "variable name %q must be all uppercase and contain no special characters except _" PkgValidateErrAction = "invalid action: %w" - PkgValidateErrActionVariables = "component %q cannot contain setVariables outside of onDeploy in actions" PkgValidateErrActionCmdWait = "action %q cannot be both a command and wait action" PkgValidateErrActionClusterNetwork = "a single wait action must contain only one of cluster or network" PkgValidateErrChart = "invalid chart definition: %w" @@ -703,10 +692,8 @@ const ( PkgValidateErrComponentName = "component name %q must be all lowercase and contain no special characters except '-' and cannot start with a '-'" PkgValidateErrComponentLocalOS = "component %q contains a localOS value that is not supported: %s (supported: %s)" PkgValidateErrComponentNameNotUnique = "component name %q is not unique" - PkgValidateErrComponent = "invalid component %q: %w" PkgValidateErrComponentReqDefault = "component %q cannot be both required and default" PkgValidateErrComponentReqGrouped = "component %q cannot be both required and grouped" - PkgValidateErrComponentYOLO = "component %q incompatible with the online-only package flag (metadata.yolo): %w" PkgValidateErrGroupMultipleDefaults = "group %q has multiple defaults (%q, %q)" PkgValidateErrGroupOneComponent = "group %q only has one component (%q)" PkgValidateErrConstant = "invalid package constant: %w" @@ -717,7 +704,6 @@ const ( PkgValidateErrManifestNameLength = "manifest %q exceed the maximum length of %d characters" PkgValidateErrManifestNameMissing = "manifest must include a name" PkgValidateErrManifestNameNotUnique = "manifest name %q is not unique" - PkgValidateErrName = "invalid package name: %w" PkgValidateErrPkgConstantName = "constant name %q must be all uppercase and contain no special characters except _" PkgValidateErrPkgConstantPattern = "provided value for constant %q does not match pattern %q" PkgValidateErrPkgName = "package name %q must be all lowercase and contain no special characters except '-' and cannot start with a '-'" @@ -732,7 +718,6 @@ const ( var ( ErrInitNotFound = errors.New("this command requires a zarf-init package, but one was not found on the local system. Re-run the last command again without '--confirm' to download the package") ErrUnableToCheckArch = errors.New("unable to get the configured cluster's architecture") - ErrInterrupt = errors.New("execution cancelled due to an interrupt") ErrUnableToGetPackages = errors.New("unable to load the Zarf Package data from the cluster") ) diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 64b8162c21..5f5541250a 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -27,12 +27,11 @@ import ( // Zarf Cluster Constants. const ( - ZarfManagedByLabel = "app.kubernetes.io/managed-by" - ZarfNamespaceName = "zarf" - ZarfStateSecretName = "zarf-state" - ZarfStateDataKey = "state" - ZarfPackageInfoLabel = "package-deploy-info" - ZarfInitPackageInfoName = "zarf-package-init" + ZarfManagedByLabel = "app.kubernetes.io/managed-by" + ZarfNamespaceName = "zarf" + ZarfStateSecretName = "zarf-state" + ZarfStateDataKey = "state" + ZarfPackageInfoLabel = "package-deploy-info" ) // InitZarfState initializes the Zarf state with the given temporary directory and init configs. diff --git a/src/pkg/layout/constants.go b/src/pkg/layout/constants.go index bb260f6a76..1df3b3db36 100644 --- a/src/pkg/layout/constants.go +++ b/src/pkg/layout/constants.go @@ -28,10 +28,6 @@ const ( IndexJSON = "index.json" OCILayout = "oci-layout" - - SeedImagesDir = "seed-images" - InjectorBinary = "zarf-injector" - InjectorPayloadTarGz = "payload.tgz" ) var ( From abd4855b3e081a5742e8032e48ad25254b8e1bcd Mon Sep 17 00:00:00 2001 From: Joel McCoy Date: Thu, 20 Jun 2024 11:24:51 -0500 Subject: [PATCH 069/132] docs: fixed wrong link in zarf site nerd notes page (#2639) ## Description Link in nerd notes points to the wrong issue. 375 currently points to 376. Fixed it so it points to the right issue that has the discussion mentioned in the bullet. ... ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- site/src/content/docs/contribute/nerd-notes.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/content/docs/contribute/nerd-notes.mdx b/site/src/content/docs/contribute/nerd-notes.mdx index d7fd44d15c..6264dd5266 100644 --- a/site/src/content/docs/contribute/nerd-notes.mdx +++ b/site/src/content/docs/contribute/nerd-notes.mdx @@ -10,7 +10,7 @@ Zarf is written entirely in [go](https://go.dev/), except for a single 868Kb bin - All workloads are installed in the cluster via the [Helm SDK](https://helm.sh/docs/topics/advanced/#go-sdk) - The OCI Registries used are both from [Docker](https://github.com/distribution/distribution) -- Currently, the Registry and Git servers _are not HA_, see [#375](https://github.com/defenseunicorns/zarf/issues/376) and [#376](https://github.com/defenseunicorns/zarf/issues/376) for discussion on this +- Currently, the Registry and Git servers _are not HA_, see [#375](https://github.com/defenseunicorns/zarf/issues/375) and [#376](https://github.com/defenseunicorns/zarf/issues/376) for discussion on this - To avoid TLS issues, Zarf binds to `127.0.0.1:31999` on each node as a [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport) to allow all nodes to access the pod(s) in the cluster - Zarf utilizes a [mutating admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) called the [`zarf-agent`](https://github.com/defenseunicorns/zarf/tree/main/src/internal/agent) to modify the image property within the `PodSpec`. The purpose is to redirect it to Zarf's configured registry instead of the the original registry (such as DockerHub, GCR, or Quay). Additionally, the webhook attaches the appropriate [ImagePullSecret](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) for the seed registry to the pod. This configuration allows the pod to successfully retrieve the image from the seed registry, even when operating in an air-gapped environment. - Zarf uses a custom injector system to bootstrap a new cluster. See the PR [#329](https://github.com/defenseunicorns/zarf/pull/329) and [ADR](https://github.com/defenseunicorns/zarf/blob/main/adr/0003-image-injection-into-remote-clusters-without-native-support.md) for more details on how we came to this solution. The general steps are listed below: From 9f4fa9cd630c8537e443bb3e9632c7960ff1ab87 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 21 Jun 2024 07:35:50 -0400 Subject: [PATCH 070/132] chore: s3 cleanup (#2632) ## Description removes one dependency on s3 and cleanups up an example ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- src/pkg/packager/sources/new_test.go | 28 ++++++++++++++++++++-------- src/test/e2e/00_use_cli_test.go | 7 ------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go index d8d9c8ecd3..9c7629ba42 100644 --- a/src/pkg/packager/sources/new_test.go +++ b/src/pkg/packager/sources/new_test.go @@ -135,18 +135,26 @@ func TestPackageSource(t *testing.T) { t.Cleanup(func() { ts.Close() }) tests := []struct { - name string - src string - shasum string + name string + src string + shasum string + expectedErr string }{ { - name: "local", - src: tarPath, + name: "local", + src: tarPath, + expectedErr: "", }, { - name: "http", - src: fmt.Sprintf("%s/zarf-package-wordpress-amd64-16.0.4.tar.zst", ts.URL), - shasum: "835b06fc509e639497fb45f45d432e5c4cbd5d84212db5357b16bc69724b0e26", + name: "http", + src: fmt.Sprintf("%s/zarf-package-wordpress-amd64-16.0.4.tar.zst", ts.URL), + shasum: "835b06fc509e639497fb45f45d432e5c4cbd5d84212db5357b16bc69724b0e26", + expectedErr: "", + }, + { + name: "http-insecure", + src: fmt.Sprintf("%s/zarf-package-wordpress-amd64-16.0.4.tar.zst", ts.URL), + expectedErr: "remote package provided without a shasum, use --insecure to ignore, or provide one w/ --shasum", }, } for _, tt := range tests { @@ -163,6 +171,10 @@ func TestPackageSource(t *testing.T) { packageDir := t.TempDir() pkgLayout := layout.New(packageDir) pkg, warnings, err := ps.LoadPackage(context.Background(), pkgLayout, filters.Empty(), false) + if tt.expectedErr != "" { + require.EqualError(t, err, tt.expectedErr) + return + } require.NoError(t, err) require.Empty(t, warnings) require.Equal(t, expectedPkg, pkg) diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index c59fdf9d55..be3afb6b4f 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -104,13 +104,6 @@ func TestUseCLI(t *testing.T) { require.Contains(t, stdErr, expectedOutString, "The log level should be changed to 'debug'") }) - t.Run("bad zarf package deploy w/o --insecure or --shasum", func(t *testing.T) { - t.Parallel() - // Test that `zarf package deploy` gives an error if deploying a remote package without the --insecure or --shasum flags - stdOut, stdErr, err := e2e.Zarf("package", "deploy", "https://zarf-examples.s3.amazonaws.com/zarf-package-appliance-demo-doom-20210125.tar.zst", "--confirm") - require.Error(t, err, stdOut, stdErr) - }) - t.Run("zarf package to test bad remote images", func(t *testing.T) { _, stdErr, err := e2e.Zarf("package", "create", "src/test/packages/00-remote-pull-fail", "--confirm") // expecting zarf to have an error and output to stderr From 8897eb4db3f908b9672ecc29d631b57d390e8c9d Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Fri, 21 Jun 2024 10:26:42 -0500 Subject: [PATCH 071/132] refactor: change UpdateZarfAgentValues to rolling restart (#2644) ## Description This PR splits out a refactor that was done in #2625 https://github.com/defenseunicorns/zarf/pull/2625#discussion_r1644994882 Here is some context for why I refactored `UpdateZarfAgentValues`. In our e2e test suite, `TestConnectAndCreds` runs `zarf tools update-creds --confirm`. `update-creds` calls `UpdateZarfAgentValues` which used to delete all agent pods manually based on label selector and immediately return without waiting for the pods to be ready. `TestMetrics` is ran immediately after `TestConnectAndCreds`, and it attempts to establish a tunnel connection to the Zarf agent to test the /metrics endpoint. This test was failing because there were no running agent pods yet after deleting them. This is not a problem in our tests right now because we run assertions against the logging stack in the cluster in `TestConnectAndCreds`, which buys us enough time for the agent pods to come back up and running. I refactored `UpdateZarfAgentValues` to perform a rolling update of the agent deployment by modifying the Pod template rather than manually deleting pods, and added a wait to ensure the agent deployment is ready before returning. This approach ensures a controlled and graceful update process managed by Kubernetes and simplifies the code. Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/internal/packager/helm/zarf.go | 71 ++++++++++++++---------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index 90c314fae0..91d3fe917f 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -74,42 +74,26 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { spinner := message.NewProgressSpinner("Gathering information to update Zarf Agent TLS") defer spinner.Stop() - err := h.createActionConfig(cluster.ZarfNamespaceName, spinner) - if err != nil { - return fmt.Errorf("unable to initialize the K8s client: %w", err) - } - - // Get the current agent image from one of its pods. - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"app": "agent-hook"}}) + deployment, err := h.cluster.Clientset.AppsV1().Deployments(cluster.ZarfNamespaceName).Get(ctx, "agent-hook", metav1.GetOptions{}) if err != nil { return err } - listOpts := metav1.ListOptions{ - LabelSelector: selector.String(), - } - podList, err := h.cluster.Clientset.CoreV1().Pods(cluster.ZarfNamespaceName).List(ctx, listOpts) + agentImage, err := transform.ParseImageRef(deployment.Spec.Template.Spec.Containers[0].Image) if err != nil { return err } - var currentAgentImage transform.Image - if len(podList.Items) > 0 && len(podList.Items[0].Spec.Containers) > 0 { - currentAgentImage, err = transform.ParseImageRef(podList.Items[0].Spec.Containers[0].Image) - if err != nil { - return fmt.Errorf("unable to parse current agent image reference: %w", err) - } - } else { - return fmt.Errorf("unable to get current agent pod") + err = h.createActionConfig(cluster.ZarfNamespaceName, spinner) + if err != nil { + return err } // List the releases to find the current agent release name. listClient := action.NewList(h.actionConfig) - releases, err := listClient.Run() if err != nil { return fmt.Errorf("unable to list helm releases: %w", err) } - spinner.Success() for _, release := range releases { @@ -122,11 +106,11 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { h.variableConfig.SetConstants([]variables.Constant{ { Name: "AGENT_IMAGE", - Value: currentAgentImage.Path, + Value: agentImage.Path, }, { Name: "AGENT_IMAGE_TAG", - Value: currentAgentImage.Tag, + Value: agentImage.Tag, }, }) applicationTemplates, err := template.GetZarfTemplates("zarf-agent", h.state) @@ -142,30 +126,43 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { } } - spinner = message.NewProgressSpinner("Cleaning up Zarf Agent pods after update") + // Trigger a rolling update for the TLS secret update to take effect. + // https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#updating-a-deployment + spinner = message.NewProgressSpinner("Performing a rolling update for the Zarf Agent deployment") defer spinner.Stop() - // Force pods to be recreated to get the updated secret. - // TODO: Explain why no grace period is given. - deleteGracePeriod := int64(0) - deletePolicy := metav1.DeletePropagationForeground - deleteOpts := metav1.DeleteOptions{ - GracePeriodSeconds: &deleteGracePeriod, - PropagationPolicy: &deletePolicy, + // Re-fetch the agent deployment before we update since the resourceVersion has changed after updating the Helm release values. + // Avoids this error: https://github.com/kubernetes/kubernetes/issues/28149 + deployment, err = h.cluster.Clientset.AppsV1().Deployments(cluster.ZarfNamespaceName).Get(ctx, "agent-hook", metav1.GetOptions{}) + if err != nil { + return err + } + if deployment.Spec.Template.Annotations == nil { + deployment.Spec.Template.Annotations = map[string]string{} } - selector, err = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{MatchLabels: map[string]string{"app": "agent-hook"}}) + deployment.Spec.Template.Annotations["zarf.dev/restartedAt"] = time.Now().UTC().Format(time.RFC3339) + _, err = h.cluster.Clientset.AppsV1().Deployments(cluster.ZarfNamespaceName).Update(ctx, deployment, metav1.UpdateOptions{}) if err != nil { return err } - listOpts = metav1.ListOptions{ - LabelSelector: selector.String(), + + objs := []object.ObjMetadata{ + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: cluster.ZarfNamespaceName, + Name: "agent-hook", + }, } - err = h.cluster.Clientset.CoreV1().Pods(cluster.ZarfNamespaceName).DeleteCollection(ctx, deleteOpts, listOpts) + waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) + defer waitCancel() + err = pkgkubernetes.WaitForReady(waitCtx, h.cluster.Watcher, objs) if err != nil { - return fmt.Errorf("error recycling pods for the Zarf Agent: %w", err) + return err } spinner.Success() - return nil } From 728e8c0bc61b06c2eda313ac90f5fe2da956632f Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:02:51 -0400 Subject: [PATCH 072/132] chore: make less (#2648) ## Description Many actions in the make file don't need every cli, just the regular build is enough ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 3ec06565a3..9a4fa4e58e 100644 --- a/Makefile +++ b/Makefile @@ -107,6 +107,8 @@ docs-and-schema: ## Generate the Zarf Documentation and Schema ZARF_CONFIG=hack/empty-config.toml go run main.go internal gen-cli-docs ZARF_CONFIG=hack/empty-config.toml hack/create-zarf-schema.sh +init-package-with-agent: build build-local-agent-image init-package + lint-packages-and-examples: build ## Recursively lint all zarf.yaml files in the repo except for those dedicated to tests hack/lint-all-zarf-packages.sh $(ZARF_BIN) false @@ -133,7 +135,7 @@ release-init-package: # INTERNAL: used to build an iron bank version of the init package with an ib version of the registry image ib-init-package: - @test -s $(ZARF_BIN) || $(MAKE) build-cli + @test -s $(ZARF_BIN) || $(MAKE) $(ZARF_BIN) package create -o build -a $(ARCH) --confirm . \ --set REGISTRY_IMAGE_DOMAIN="registry1.dso.mil/" \ --set REGISTRY_IMAGE="ironbank/opensource/docker/registry-v2" \ @@ -191,7 +193,7 @@ test-e2e-without-cluster: build-examples ## Run all of the core Zarf CLI E2E tes ## NOTE: Requires an existing cluster .PHONY: test-external test-external: ## Run the Zarf CLI E2E tests for an external registry and cluster - @test -s $(ZARF_BIN) || $(MAKE) build-cli + @test -s $(ZARF_BIN) || $(MAKE) @test -s ./build/zarf-init-$(ARCH)-$(CLI_VERSION).tar.zst || $(MAKE) init-package @test -s ./build/zarf-package-podinfo-flux-$(ARCH).tar.zst || $(ZARF_BIN) package create examples/podinfo-flux -o build -a $(ARCH) --confirm @test -s ./build/zarf-package-argocd-$(ARCH).tar.zst || $(ZARF_BIN) package create examples/argocd -o build -a $(ARCH) --confirm @@ -200,7 +202,7 @@ test-external: ## Run the Zarf CLI E2E tests for an external registry and cluste ## NOTE: Requires an existing cluster and .PHONY: test-upgrade test-upgrade: ## Run the Zarf CLI E2E tests for an external registry and cluster - @test -s $(ZARF_BIN) || $(MAKE) build-cli + @test -s $(ZARF_BIN) || $(MAKE) [ -n "$(shell zarf version)" ] || (echo "Zarf must be installed prior to the upgrade test" && exit 1) [ -n "$(shell zarf package list 2>&1 | grep test-upgrade-package)" ] || (echo "Zarf must be initialized and have the 6.3.3 upgrade-test package installed prior to the upgrade test" && exit 1) @test -s "zarf-package-test-upgrade-package-amd64-6.3.4.tar.zst" || zarf package create src/test/upgrade/ --set PODINFO_VERSION=6.3.4 --confirm From eab8a980a8fe9df1cb49a1b260e806f97101f6ba Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:40:01 -0400 Subject: [PATCH 073/132] fix: docs links (#2650) ## Description Fixes all the links checked by linkinator. Not checking github since it gives bad error codes even if the site link works ## Related Issue Fixes #2642 and #2643 ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed --- site/astro.config.ts | 2 +- site/linkinator.config.json | 4 + site/package-lock.json | 651 ++++++++++++++++++ site/package.json | 6 +- site/src/content/docs/ref/init-package.mdx | 2 +- site/src/content/docs/ref/tools.mdx | 6 +- .../tutorials/0-creating-a-zarf-package.mdx | 4 +- .../docs/tutorials/7-publish-and-deploy.mdx | 4 +- 8 files changed, 668 insertions(+), 11 deletions(-) create mode 100644 site/linkinator.config.json diff --git a/site/astro.config.ts b/site/astro.config.ts index ac3b8dec24..d787bd08c6 100644 --- a/site/astro.config.ts +++ b/site/astro.config.ts @@ -44,7 +44,7 @@ export default defineConfig({ }, favicon: "/favicon.svg", editLink: { - baseUrl: "https://github.com/defenseunicorns/zarf/edit/main/", + baseUrl: "https://github.com/defenseunicorns/zarf/edit/main/site", }, logo: { src: "./src/assets/zarf-logo-header.svg", diff --git a/site/linkinator.config.json b/site/linkinator.config.json new file mode 100644 index 0000000000..dcf747e9a4 --- /dev/null +++ b/site/linkinator.config.json @@ -0,0 +1,4 @@ +{ + "skip": "^(https://.*github.com)", + "verbosity": "error" +} diff --git a/site/package-lock.json b/site/package-lock.json index faa4544f51..2fdfd8e985 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -18,6 +18,7 @@ "typescript": "^5.4.3" }, "devDependencies": { + "linkinator": "^6.0.5", "markdownlint-cli2": "^0.12.1", "remark-gemoji": "^8.0.0", "yaml": "^2.4.1" @@ -1791,6 +1792,49 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -1965,6 +2009,17 @@ "win32" ] }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.1.tgz", @@ -2423,6 +2478,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-align": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", @@ -2921,6 +2989,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/bare-events": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", @@ -3080,6 +3155,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -4128,11 +4213,70 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, "node_modules/dompurify": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.11.tgz", "integrity": "sha512-Fan4uMuyB26gFV3ovPoEoQbxRRPfTu3CvImyZnhGq5fsIEO+gEFLp45ISFt+kQBWsK5ulDdT0oV28jS1UrwQLg==" }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dset": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.3.tgz", @@ -4240,6 +4384,13 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -4466,6 +4617,23 @@ "node": ">=8" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -4493,6 +4661,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gaxios": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.6.0.tgz", + "integrity": "sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gemoji": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/gemoji/-/gemoji-8.1.0.tgz", @@ -4552,6 +4750,30 @@ "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" }, + "node_modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -4965,11 +5187,45 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", @@ -5284,6 +5540,25 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5385,6 +5660,60 @@ "uc.micro": "^2.0.0" } }, + "node_modules/linkinator": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/linkinator/-/linkinator-6.0.5.tgz", + "integrity": "sha512-LRMHgO/29gk2WQzdj4cFcFHGKPhYPGBWVZOayATP6j3159ubonGJizObNRvgA5qDnrkqsRwJT7p4Tq97pC9GeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.0.0", + "escape-html": "^1.0.3", + "gaxios": "^6.0.0", + "glob": "^10.3.10", + "htmlparser2": "^9.0.0", + "marked": "^12.0.1", + "meow": "^13.0.0", + "mime": "^4.0.0", + "server-destroy": "^1.0.1", + "srcset": "^5.0.0" + }, + "bin": { + "linkinator": "build/src/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/linkinator/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/linkinator/node_modules/mime": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.3.tgz", + "integrity": "sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa" + ], + "license": "MIT", + "bin": { + "mime": "bin/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/load-yaml-file": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", @@ -5609,6 +5938,19 @@ "url": "https://github.com/sponsors/DavidAnson" } }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mdast-util-definitions": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-6.0.0.tgz", @@ -5930,6 +6272,19 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "dev": true }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -7197,6 +7552,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -7206,6 +7577,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -7283,6 +7664,27 @@ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", "optional": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -7501,6 +7903,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pagefind": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pagefind/-/pagefind-1.0.4.tgz", @@ -7591,6 +8000,33 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "14 || >=16.14" + } + }, "node_modules/path-to-regexp": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", @@ -9018,6 +9454,13 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true, + "license": "ISC" + }, "node_modules/sharp": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.3.tgz", @@ -9218,6 +9661,19 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/srcset": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz", + "integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -9269,6 +9725,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.3.tgz", @@ -9296,6 +9798,30 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -9411,6 +9937,13 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -10462,6 +10995,24 @@ "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.3.0.tgz", "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -10547,6 +11098,106 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", diff --git a/site/package.json b/site/package.json index 22dafe180d..51daacaa59 100644 --- a/site/package.json +++ b/site/package.json @@ -10,9 +10,10 @@ "dev": "astro dev", "start": "astro dev", "prebuild": "cp ../zarf.schema.json ./src/assets/zarf.schema.json && node hack/copy-examples.js", - "build": "astro check && astro build", + "build": "rm -rf dist && astro check && astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "link-check": "linkinator --recurse dist/ --config linkinator.config.json" }, "dependencies": { "@astrojs/check": "^0.5.10", @@ -25,6 +26,7 @@ "typescript": "^5.4.3" }, "devDependencies": { + "linkinator": "^6.0.5", "markdownlint-cli2": "^0.12.1", "remark-gemoji": "^8.0.0", "yaml": "^2.4.1" diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx index c2f98e1e6e..f1669ae471 100644 --- a/site/src/content/docs/ref/init-package.mdx +++ b/site/src/content/docs/ref/init-package.mdx @@ -88,7 +88,7 @@ It leverages the same `docker-registry` chart used in `zarf-seed-registry` but w 1. The `image.repository` is set to the value of the built-in variable `###ZARF_REGISTRY###` which is set at runtime to the registry hosted by `zarf-seed-registry`. {/* you know, why DO we do this? if we kept the repository the same, and kept running the injector, couldnt this handle cluster full-deaths??? (ofc would need to tweak the zarf-agent to not mutate the image.repository for the docker-registry chart under the zarf namespace, but i think it might be doable) */} -2. A `connect` manifest for running [`zarf connect registry`](/commands/zarf_connect_registry/) to tunnel to the Zarf Registry. +2. A `connect` manifest for running [`zarf connect registry`](/commands/zarf_connect/) to tunnel to the Zarf Registry. 3. A configmap to satisfy [KEP-1755](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry) :::tip diff --git a/site/src/content/docs/ref/tools.mdx b/site/src/content/docs/ref/tools.mdx index e9a0eb5246..7c1c3a0316 100644 --- a/site/src/content/docs/ref/tools.mdx +++ b/site/src/content/docs/ref/tools.mdx @@ -17,7 +17,7 @@ TODO: finish this page for all embedded CLIs > > aliases: `yq` -Zarf vendors in [`yq`](https://github.com/mikefarah/yq/) to provide a way to interact with YAML files. +Zarf vendors in [`yq`](https://github.com/mikefarah/yq/) to provide a way to interact with YAML files. `yq` is a lightweight and portable command-line YAML processor. It can be used to extract specific parts of a YAML file, replace or delete values, and more. ## archiver @@ -30,7 +30,7 @@ Zarf uses the great [mholt/archiver](https://github.com/mholt/archiver) library ## syft -> command: [`zarf tools sbom`](/commands/zarf_tools_syft) +> command: [`zarf tools sbom`](/commands/zarf_tools_sbom) > > aliases: `syft`, `sbom`, `s` @@ -38,7 +38,7 @@ Zarf vendors in [Syft](https://github.com/anchore/syft), a "CLI tool and library ## k9s -> command: [`zarf tools k9s`](/commands/zarf_tools_k9s) +> command: [`zarf tools k9s`](/commands/zarf_tools_monitor) > > aliases: `k9s`, `monitor`, `m` diff --git a/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx b/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx index 859c479056..84634b4386 100644 --- a/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx +++ b/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx @@ -92,7 +92,7 @@ We create any `values.yaml` file(s) at this stage because the `zarf dev find-ima :::caution -Note that we are explicitly defining the `wordpress` namespace for this deployment, this is strongly recommended to separate out the applications you deploy and to avoid issues with the Zarf Agent not being able to mutate your resources as it intentionally ignores resources in the `default` or `kube-system` namespaces. See [what happens to resources that exist before Zarf init](/faq#what-happens-to-resources-that-exist-in-the-cluster-before-zarf-init) for more information. +Note that we are explicitly defining the `wordpress` namespace for this deployment, this is strongly recommended to separate out the applications you deploy and to avoid issues with the [Zarf Agent](/ref/init-package/#zarf-agent) not being able to mutate your resources as it intentionally ignores resources in the `default` or `kube-system` namespaces. ::: @@ -120,7 +120,7 @@ Zarf has more `dev` commands you can learn about on the [dev CLI docs page](/ref We now have a deployable package definition, but it is currently not very configurable and might not fit every environment we want to deploy it to. If we deployed it as-is we would always have a Zarf Blog and a `zarf` user with an autogenerated password. -To resolve this, we can add configuration options with [Zarf Variables](/ref/examples/values/#variables-zarf_var_). For this package we will add a `variables` section to our `zarf.yaml` above `components` that will allow us to setup the user and the blog. +To resolve this, we can add configuration options with [Zarf Variables](/ref/values/#variables-zarf_var_). For this package we will add a `variables` section to our `zarf.yaml` above `components` that will allow us to setup the user and the blog. ```yaml variables: diff --git a/site/src/content/docs/tutorials/7-publish-and-deploy.mdx b/site/src/content/docs/tutorials/7-publish-and-deploy.mdx index 8d0d0fee4b..c50b8fee65 100644 --- a/site/src/content/docs/tutorials/7-publish-and-deploy.mdx +++ b/site/src/content/docs/tutorials/7-publish-and-deploy.mdx @@ -18,7 +18,7 @@ In this tutorial, we are going to run through how to publish a Zarf package to a For following along locally, please ensure the following prerequisites are met: - Zarf binary installed on your `$PATH`: ([Installing Zarf](/getting-started/install/)) -- Access to a [Registry supporting the OCI Distribution Spec](https://oras.land/implementors/#registries-supporting-oci-artifacts), this tutorial will be using Docker Hub +- Access to a [Registry supporting the OCI Distribution Spec](https://oras.land/docs/compatible_oci_registries#registries-supporting-oci-artifacts), this tutorial will be using Docker Hub - Access to a cluster that has been [initialized with zarf](/tutorials/1-initializing-a-k8s-cluster/). ## Working with OCI Packages @@ -128,7 +128,7 @@ You attempted to publish a package with no version metadata. 1. Open the zarf.yaml file. -2. Add a version attribute to the [package metadata](https://docs.zarf.dev/docs/user-guide/zarf-schema#metadata) +2. Add the version attribute to the package metadata. 3. Recreate the package with the `zarf package create` command. 4. Publish the package. The filename will now have the version as part of it. From 6cc9c57bed34ae1c0a3eafd0a1a453a80ff193d5 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 21 Jun 2024 19:21:50 +0200 Subject: [PATCH 074/132] refactor: remove use of reflections (#2634) ## Description This change removes any direct import of the reflection package. Use of reflections still exists but these are wrapped in the helpers package. Use of deep equals has been removed from tests as require equals uses deep equals as part of its test. ## Related Issue N/A ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/internal/packager/helm/post-render.go | 5 ++-- src/pkg/cluster/secrets.go | 7 ++--- src/pkg/packager/deploy_test.go | 5 +--- src/pkg/packager/filters/deploy_test.go | 31 +++++------------------ src/pkg/utils/sort_test.go | 5 +--- 5 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 92b912ae63..1dcdbc6912 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -8,9 +8,9 @@ import ( "bytes" "context" "fmt" + "maps" "os" "path/filepath" - "reflect" "slices" "github.com/defenseunicorns/pkg/helpers/v2" @@ -163,7 +163,8 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) // TODO: Refactor as error is not checked instead of checking for not found error. currentRegistrySecret, _ := c.Clientset.CoreV1().Secrets(name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) - if currentRegistrySecret.Name != config.ZarfImagePullSecretName || !reflect.DeepEqual(currentRegistrySecret.Data, validRegistrySecret.Data) { + sameSecretData := maps.EqualFunc(currentRegistrySecret.Data, validRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) + if currentRegistrySecret.Name != config.ZarfImagePullSecretName || !sameSecretData { err := func() error { _, err := c.Clientset.CoreV1().Secrets(validRegistrySecret.Namespace).Create(ctx, validRegistrySecret, metav1.CreateOptions{}) if err != nil && !kerrors.IsAlreadyExists(err) { diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index cfbb90953e..a7af5449f6 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -5,10 +5,11 @@ package cluster import ( + "bytes" "context" "encoding/base64" "encoding/json" - "reflect" + "maps" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -123,7 +124,7 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) - if !reflect.DeepEqual(currentRegistrySecret.Data, newRegistrySecret.Data) { + if !maps.EqualFunc(currentRegistrySecret.Data, newRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) { _, err := c.Clientset.CoreV1().Secrets(newRegistrySecret.Namespace).Update(ctx, newRegistrySecret, metav1.UpdateOptions{}) if err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", namespace.Name) @@ -159,7 +160,7 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. // Create the secret newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) - if !reflect.DeepEqual(currentGitSecret.StringData, newGitSecret.StringData) { + if !maps.Equal(currentGitSecret.StringData, newGitSecret.StringData) { _, err := c.Clientset.CoreV1().Secrets(newGitSecret.Namespace).Update(ctx, newGitSecret, metav1.UpdateOptions{}) if err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", namespace.Name) diff --git a/src/pkg/packager/deploy_test.go b/src/pkg/packager/deploy_test.go index b4b7cb9df2..8c69d1682f 100644 --- a/src/pkg/packager/deploy_test.go +++ b/src/pkg/packager/deploy_test.go @@ -4,7 +4,6 @@ package packager import ( - "reflect" "testing" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" @@ -222,9 +221,7 @@ func TestGenerateValuesOverrides(t *testing.T) { if err != nil { t.Errorf("%s: generateValuesOverrides() error = %v", tt.name, err) } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("%s: generateValuesOverrides() got = %v, want %v", tt.name, got, tt.want) - } + require.Equal(t, tt.want, got) }) } } diff --git a/src/pkg/packager/filters/deploy_test.go b/src/pkg/packager/filters/deploy_test.go index 029a2a9d53..1bb548f1cb 100644 --- a/src/pkg/packager/filters/deploy_test.go +++ b/src/pkg/packager/filters/deploy_test.go @@ -6,7 +6,6 @@ package filters import ( "fmt" - "reflect" "strings" "testing" @@ -115,7 +114,7 @@ func TestDeployFilter_Apply(t *testing.T) { possibilities := componentMatrix(t) - testCases := map[string]struct { + tests := map[string]struct { pkg types.ZarfPackage optionalComponents string want []types.ZarfComponent @@ -196,35 +195,19 @@ func TestDeployFilter_Apply(t *testing.T) { }, } - for name, tc := range testCases { + for name, tt := range tests { t.Run(name, func(t *testing.T) { // we do not currently support interactive mode in unit tests isInteractive := false - filter := ForDeploy(tc.optionalComponents, isInteractive) + filter := ForDeploy(tt.optionalComponents, isInteractive) - result, err := filter.Apply(tc.pkg) - if tc.expectedErr != nil { - require.ErrorIs(t, err, tc.expectedErr) + result, err := filter.Apply(tt.pkg) + if tt.expectedErr != nil { + require.ErrorIs(t, err, tt.expectedErr) } else { require.NoError(t, err) } - equal := reflect.DeepEqual(tc.want, result) - if !equal { - left := []string{} - right := []string{} - - for _, c := range tc.want { - left = append(left, c.Name) - } - - for _, c := range result { - right = append(right, c.Name) - fmt.Printf("componentFromQuery(t, %q),\n", strings.TrimSpace(c.Name)) - } - - // cause the test to fail - require.FailNow(t, "expected and actual are not equal", "\n\nexpected: %#v\n\nactual: %#v", left, right) - } + require.Equal(t, tt.want, result) }) } } diff --git a/src/pkg/utils/sort_test.go b/src/pkg/utils/sort_test.go index 2cd2a29c42..beee3e160b 100644 --- a/src/pkg/utils/sort_test.go +++ b/src/pkg/utils/sort_test.go @@ -5,7 +5,6 @@ package utils import ( - "reflect" "testing" "github.com/stretchr/testify/require" @@ -169,9 +168,7 @@ func TestSortDependencies(t *testing.T) { } else { require.Error(t, err) } - if !reflect.DeepEqual(result, tt.expected) { - t.Errorf("expected %v but got %v", tt.expected, result) - } + require.Equal(t, tt.expected, result) }) } } From 27f280d2dbfccf016da11feb4ad4fba72f4a0a30 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 21 Jun 2024 20:20:47 +0200 Subject: [PATCH 075/132] refactor: remove use of message.Fatal in tools (#2602) ## Description This change removes the use of message.Fatal in the tools. ## Related Issue ## Checklist before merging - [ ] Test, docs, adr added or updated as needed - [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/root.go | 4 +- src/cmd/tools/archiver.go | 60 +++++++++++++++--------------- src/cmd/tools/crane.go | 10 +++-- src/cmd/tools/helm/load_plugins.go | 11 +----- src/cmd/tools/helm/repo_add.go | 7 +--- src/cmd/tools/helm/repo_index.go | 3 +- src/cmd/tools/wait.go | 8 ++-- src/config/lang/english.go | 8 +--- src/pkg/utils/wait.go | 22 ++++++----- 9 files changed, 61 insertions(+), 72 deletions(-) diff --git a/src/cmd/root.go b/src/cmd/root.go index 447a9d76d2..ee40254fe0 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -8,6 +8,7 @@ import ( "context" "fmt" "os" + "slices" "strings" "github.com/pterm/pterm" @@ -67,8 +68,9 @@ func Execute(ctx context.Context) { if err == nil { return } + defaultPrintCmds := []string{"helm", "yq", "kubectl"} comps := strings.Split(cmd.CommandPath(), " ") - if len(comps) > 1 && comps[1] == "tools" { + if len(comps) > 1 && comps[1] == "tools" && slices.Contains(defaultPrintCmds, comps[2]) { cmd.PrintErrln(cmd.ErrPrefix(), err.Error()) } else { pterm.Error.Println(err.Error()) diff --git a/src/cmd/tools/archiver.go b/src/cmd/tools/archiver.go index 344cc7398c..d62ce32785 100644 --- a/src/cmd/tools/archiver.go +++ b/src/cmd/tools/archiver.go @@ -12,7 +12,6 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/layout" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/mholt/archiver/v3" "github.com/spf13/cobra" ) @@ -32,12 +31,13 @@ var archiverCompressCmd = &cobra.Command{ Aliases: []string{"c"}, Short: lang.CmdToolsArchiverCompressShort, Args: cobra.MinimumNArgs(2), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { sourceFiles, destinationArchive := args[:len(args)-1], args[len(args)-1] err := archiver.Archive(sourceFiles, destinationArchive) if err != nil { - message.Fatalf(err, lang.CmdToolsArchiverCompressErr, err.Error()) + return fmt.Errorf("unable to perform compression: %w", err) } + return err }, } @@ -48,39 +48,40 @@ var archiverDecompressCmd = &cobra.Command{ Aliases: []string{"d"}, Short: lang.CmdToolsArchiverDecompressShort, Args: cobra.ExactArgs(2), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { sourceArchive, destinationPath := args[0], args[1] err := archiver.Unarchive(sourceArchive, destinationPath) if err != nil { - message.Fatalf(err, lang.CmdToolsArchiverDecompressErr, err.Error()) + return fmt.Errorf("unable to perform decompression: %w", err) } - - if unarchiveAll { - err := filepath.Walk(destinationPath, func(path string, info os.FileInfo, err error) error { + if !unarchiveAll { + return nil + } + err = filepath.Walk(destinationPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, ".tar") { + dst := filepath.Join(strings.TrimSuffix(path, ".tar"), "..") + // Unpack sboms.tar differently since it has a different folder structure than components + if info.Name() == layout.SBOMTar { + dst = strings.TrimSuffix(path, ".tar") + } + err := archiver.Unarchive(path, dst) if err != nil { - return err + return fmt.Errorf(lang.ErrUnarchive, path, err.Error()) } - if strings.HasSuffix(path, ".tar") { - dst := filepath.Join(strings.TrimSuffix(path, ".tar"), "..") - // Unpack sboms.tar differently since it has a different folder structure than components - if info.Name() == layout.SBOMTar { - dst = strings.TrimSuffix(path, ".tar") - } - err := archiver.Unarchive(path, dst) - if err != nil { - return fmt.Errorf(lang.ErrUnarchive, path, err.Error()) - } - err = os.Remove(path) - if err != nil { - return fmt.Errorf(lang.ErrRemoveFile, path, err.Error()) - } + err = os.Remove(path) + if err != nil { + return fmt.Errorf(lang.ErrRemoveFile, path, err.Error()) } - return nil - }) - if err != nil { - message.Fatalf(err, lang.CmdToolsArchiverUnarchiveAllErr, err.Error()) } + return nil + }) + if err != nil { + return fmt.Errorf("unable to unarchive all nested tarballs: %w", err) } + return nil }, } @@ -93,8 +94,5 @@ func init() { archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "decompress-all", false, "Decompress all tarballs in the archive") archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "unarchive-all", false, "Unarchive all tarballs in the archive") archiverDecompressCmd.MarkFlagsMutuallyExclusive("decompress-all", "unarchive-all") - err := archiverDecompressCmd.Flags().MarkHidden("decompress-all") - if err != nil { - message.Fatal(err, err.Error()) - } + archiverDecompressCmd.Flags().MarkHidden("decompress-all") } diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index ad0b98ab0e..ea8924be61 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -5,6 +5,7 @@ package tools import ( + "errors" "fmt" "os" "strings" @@ -37,7 +38,7 @@ func init() { Use: "registry", Aliases: []string{"r", "crane"}, Short: lang.CmdToolsRegistryShort, - PersistentPreRun: func(cmd *cobra.Command, _ []string) { + PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { // The crane options loading here comes from the rootCmd of crane craneOptions = append(craneOptions, crane.WithContext(cmd.Context())) // TODO(jonjohnsonjr): crane.Verbose option? @@ -56,11 +57,12 @@ func init() { if platform != "all" { v1Platform, err = v1.ParsePlatform(platform) if err != nil { - message.Fatalf(err, lang.CmdToolsRegistryInvalidPlatformErr, platform, err.Error()) + return fmt.Errorf("invalid platform %s: %w", platform, err) } } craneOptions = append(craneOptions, crane.WithPlatform(v1Platform)) + return nil }, } @@ -159,7 +161,7 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command wrappedCommand.RunE = func(cmd *cobra.Command, args []string) error { if len(args) < imageNameArgumentIndex+1 { - message.Fatal(nil, lang.CmdToolsCraneNotEnoughArgumentsErr) + return errors.New("not have enough arguments specified for this command") } // Try to connect to a Zarf initialized cluster otherwise then pass it down to crane. @@ -333,7 +335,7 @@ func doPruneImagesForPackages(zarfState *types.ZarfState, zarfPackages []types.D Message: lang.CmdConfirmContinue, } if err := survey.AskOne(prompt, &confirm); err != nil { - message.Fatalf(nil, lang.ErrConfirmCancel, err) + return fmt.Errorf("confirm selection canceled: %w", err) } } if confirm { diff --git a/src/cmd/tools/helm/load_plugins.go b/src/cmd/tools/helm/load_plugins.go index f4d2800137..28ea155030 100644 --- a/src/cmd/tools/helm/load_plugins.go +++ b/src/cmd/tools/helm/load_plugins.go @@ -32,7 +32,6 @@ import ( "strings" "syscall" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/yaml" @@ -217,10 +216,7 @@ func loadCompletionForPlugin(pluginCmd *cobra.Command, plugin *plugin.Plugin) { if err != nil { // The file could be missing or invalid. No static completion for this plugin. if settings.Debug { - err := log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) - if err != nil { - message.Fatal(err, err.Error()) - } + log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) } // Continue to setup dynamic completion. cmds = &pluginCommand{} @@ -242,10 +238,7 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug if len(cmds.Name) == 0 { // Missing name for a command if settings.Debug { - err := log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) - if err != nil { - message.Fatal(err, err.Error()) - } + log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) } return } diff --git a/src/cmd/tools/helm/repo_add.go b/src/cmd/tools/helm/repo_add.go index 5bbca44356..de695bff5c 100644 --- a/src/cmd/tools/helm/repo_add.go +++ b/src/cmd/tools/helm/repo_add.go @@ -32,7 +32,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/gofrs/flock" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -134,11 +133,7 @@ func (o *repoAddOptions) run(ctx context.Context, out io.Writer) error { defer cancel() locked, err := fileLock.TryLockContext(lockCtx, time.Second) if err == nil && locked { - defer func() { - if err := fileLock.Unlock(); err != nil { - message.Fatal(err, err.Error()) - } - }() + defer fileLock.Unlock() } if err != nil { return err diff --git a/src/cmd/tools/helm/repo_index.go b/src/cmd/tools/helm/repo_index.go index ddccb7b46e..e326110aeb 100644 --- a/src/cmd/tools/helm/repo_index.go +++ b/src/cmd/tools/helm/repo_index.go @@ -27,7 +27,6 @@ import ( "path/filepath" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -104,7 +103,7 @@ func index(dir, url, mergeTo string) error { i2 = repo.NewIndexFile() err := i2.WriteFile(mergeTo, helpers.ReadAllWriteUser) if err != nil { - message.Fatal(err, err.Error()) + return err } } else { i2, err = repo.LoadIndexFile(mergeTo) diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go index 9977c58011..bb767ec972 100644 --- a/src/cmd/tools/wait.go +++ b/src/cmd/tools/wait.go @@ -5,6 +5,7 @@ package tools import ( + "fmt" "time" "github.com/defenseunicorns/zarf/src/config/lang" @@ -28,11 +29,11 @@ var waitForCmd = &cobra.Command{ Long: lang.CmdToolsWaitForLong, Example: lang.CmdToolsWaitForExample, Args: cobra.MinimumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { // Parse the timeout string timeout, err := time.ParseDuration(waitTimeout) if err != nil { - message.Fatalf(err, lang.CmdToolsWaitForErrTimeoutString, waitTimeout) + return fmt.Errorf("invalid timeout duration %s, use a valid duration string e.g. 1s, 2m, 3h: %w", waitTimeout, err) } kind := args[0] @@ -51,8 +52,9 @@ var waitForCmd = &cobra.Command{ // Execute the wait command. if err := utils.ExecuteWait(waitTimeout, waitNamespace, condition, kind, identifier, timeout); err != nil { - message.Fatal(err, err.Error()) + return err } + return err }, } diff --git a/src/config/lang/english.go b/src/config/lang/english.go index f84563ed2d..08dba19dd1 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -573,12 +573,8 @@ $ zarf tools wait-for https 1.1.1.1 200 # wait $ zarf tools wait-for http google.com # wait for any 2xx response from http://google.com $ zarf tools wait-for http google.com success # wait for any 2xx response from http://google.com ` - CmdToolsWaitForFlagTimeout = "Specify the timeout duration for the wait command." - CmdToolsWaitForErrTimeoutString = "Invalid timeout duration '%s'. Please use a valid duration string (e.g. 1s, 2m, 3h)." - CmdToolsWaitForErrTimeout = "Wait timed out." - CmdToolsWaitForErrConditionString = "Invalid HTTP status code. Please use a valid HTTP status code (e.g. 200, 404, 500)." - CmdToolsWaitForErrZarfPath = "Could not locate the current Zarf binary path." - CmdToolsWaitForFlagNamespace = "Specify the namespace of the resources to wait for." + CmdToolsWaitForFlagTimeout = "Specify the timeout duration for the wait command." + CmdToolsWaitForFlagNamespace = "Specify the namespace of the resources to wait for." CmdToolsKubectlDocs = "Kubectl command. See https://kubernetes.io/docs/reference/kubectl/overview/ for more information." diff --git a/src/pkg/utils/wait.go b/src/pkg/utils/wait.go index a4815e5ea5..4ccc3d2481 100644 --- a/src/pkg/utils/wait.go +++ b/src/pkg/utils/wait.go @@ -5,6 +5,7 @@ package utils import ( + "errors" "fmt" "net" "net/http" @@ -15,7 +16,6 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/utils/exec" - "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -33,8 +33,7 @@ func ExecuteWait(waitTimeout, waitNamespace, condition, kind, identifier string, // Handle network endpoints. switch kind { case "http", "https", "tcp": - waitForNetworkEndpoint(kind, identifier, condition, timeout) - return nil + return waitForNetworkEndpoint(kind, identifier, condition, timeout) } // Type of wait, condition or JSONPath @@ -50,7 +49,7 @@ func ExecuteWait(waitTimeout, waitNamespace, condition, kind, identifier string, // Get the Zarf command configuration. zarfCommand, err := GetFinalExecutableCommand() if err != nil { - message.Fatal(err, lang.CmdToolsWaitForErrZarfPath) + return fmt.Errorf("could not locate the current Zarf binary path: %w", err) } identifierMsg := identifier @@ -88,7 +87,7 @@ func ExecuteWait(waitTimeout, waitNamespace, condition, kind, identifier string, select { case <-expired: - message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) + return errors.New("wait timed out") default: spinner.Updatef(existMsg) @@ -132,7 +131,7 @@ func ExecuteWait(waitTimeout, waitNamespace, condition, kind, identifier string, } // waitForNetworkEndpoint waits for a network endpoint to respond. -func waitForNetworkEndpoint(resource, name, condition string, timeout time.Duration) { +func waitForNetworkEndpoint(resource, name, condition string, timeout time.Duration) error { // Set the timeout for the wait-for command. expired := time.After(timeout) @@ -153,7 +152,7 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat select { case <-expired: - message.Fatal(nil, lang.CmdToolsWaitForErrTimeout) + return errors.New("wait timed out") default: switch resource { @@ -179,8 +178,11 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat // Convert the condition to an int and check if it's a valid HTTP status code. code, err := strconv.Atoi(condition) - if err != nil || http.StatusText(code) == "" { - message.Fatal(err, lang.CmdToolsWaitForErrConditionString) + if err != nil { + return fmt.Errorf("http status code %s is not an integer: %w", condition, err) + } + if http.StatusText(code) == "" { + return errors.New("http status code %s is unknown") } // Try to get the URL and check the status code. @@ -202,7 +204,7 @@ func waitForNetworkEndpoint(resource, name, condition string, timeout time.Durat // Yay, we made it! spinner.Success() - return + return nil } } } From 1b104dc75177d2fee37e539da092ca4400b4af15 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 21 Jun 2024 20:44:36 +0200 Subject: [PATCH 076/132] refactor: remove k8s package (#2627) ## Description This is the final PR to completlty remove the k8s package. It refactors some of the cluster creation and moves constants that are still needed. ## Related Issue Fixes #2507 ## Checklist before merging - [x] Test, docs, adr added or updated as needed - [x] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/internal/agent/hooks/utils_test.go | 3 +- src/pkg/cluster/cluster.go | 118 +++++++++++++++++++++++ src/pkg/cluster/common.go | 65 ------------- src/pkg/cluster/injector.go | 23 ++--- src/pkg/cluster/injector_test.go | 15 +-- src/pkg/cluster/namespace.go | 6 +- src/pkg/cluster/secrets.go | 9 +- src/pkg/cluster/secrets_test.go | 5 +- src/pkg/cluster/state.go | 7 +- src/pkg/cluster/state_test.go | 8 +- src/pkg/cluster/zarf.go | 7 +- src/pkg/cluster/zarf_test.go | 9 +- src/pkg/k8s/common.go | 128 ------------------------- src/pkg/k8s/types.go | 29 ------ src/pkg/packager/common_test.go | 16 ++-- src/pkg/pki/pki.go | 6 +- src/types/k8s.go | 18 ++-- 17 files changed, 176 insertions(+), 296 deletions(-) create mode 100644 src/pkg/cluster/cluster.go delete mode 100644 src/pkg/cluster/common.go delete mode 100644 src/pkg/k8s/common.go delete mode 100644 src/pkg/k8s/types.go diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 7cc1c912a6..716a5b5245 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -13,7 +13,6 @@ import ( "github.com/defenseunicorns/zarf/src/internal/agent/operations" "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/admission/v1" @@ -32,7 +31,7 @@ type admissionTest struct { func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { t.Helper() - c := &cluster.Cluster{K8s: &k8s.K8s{Clientset: fake.NewSimpleClientset()}} + c := &cluster.Cluster{Clientset: fake.NewSimpleClientset()} stateData, err := json.Marshal(state) require.NoError(t, err) diff --git a/src/pkg/cluster/cluster.go b/src/pkg/cluster/cluster.go new file mode 100644 index 0000000000..0aad1ad848 --- /dev/null +++ b/src/pkg/cluster/cluster.go @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package cluster contains Zarf-specific cluster management functions. +package cluster + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" + + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" + + "github.com/defenseunicorns/zarf/src/pkg/message" +) + +const ( + // DefaultTimeout is the default time to wait for a cluster to be ready. + DefaultTimeout = 30 * time.Second + // AgentLabel is used to give instructions to the Zarf agent + AgentLabel = "zarf.dev/agent" +) + +// Cluster Zarf specific cluster management functions. +type Cluster struct { + Clientset kubernetes.Interface + RestConfig *rest.Config + Watcher watcher.StatusWatcher +} + +// NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. +func NewClusterWithWait(ctx context.Context) (*Cluster, error) { + spinner := message.NewProgressSpinner("Waiting for cluster connection") + defer spinner.Stop() + + c, err := NewCluster() + if err != nil { + return nil, err + } + err = waitForHealthyCluster(ctx, c.Clientset) + if err != nil { + return nil, err + } + + spinner.Success() + + return c, nil +} + +// NewCluster creates a new Cluster instance and validates connection to the cluster by fetching the Kubernetes version. +func NewCluster() (*Cluster, error) { + clientset, config, err := pkgkubernetes.ClientAndConfig() + if err != nil { + return nil, err + } + watcher, err := pkgkubernetes.WatcherForConfig(config) + if err != nil { + return nil, err + } + c := &Cluster{ + Clientset: clientset, + RestConfig: config, + Watcher: watcher, + } + // Dogsled the version output. We just want to ensure no errors were returned to validate cluster connection. + _, err = c.Clientset.Discovery().ServerVersion() + if err != nil { + return nil, err + } + return c, nil +} + +// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. +func waitForHealthyCluster(ctx context.Context, client kubernetes.Interface) error { + const waitDuration = 1 * time.Second + + timer := time.NewTimer(0) + defer timer.Stop() + + for { + select { + case <-ctx.Done(): + return fmt.Errorf("error waiting for cluster to report healthy: %w", ctx.Err()) + case <-timer.C: + // Make sure there is at least one running Node + nodeList, err := client.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) + if err != nil || len(nodeList.Items) < 1 { + message.Debug("No nodes reporting healthy yet: %v\n", err) + timer.Reset(waitDuration) + continue + } + + // Get the cluster pod list + pods, err := client.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{}) + if err != nil { + message.Debug("Could not get the pod list: %w", err) + timer.Reset(waitDuration) + continue + } + + // Check that at least one pod is in the 'succeeded' or 'running' state + for _, pod := range pods.Items { + if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodRunning { + return nil + } + } + + message.Debug("No pods reported 'succeeded' or 'running' state yet.") + timer.Reset(waitDuration) + } + } +} diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go deleted file mode 100644 index 02b6f1da78..0000000000 --- a/src/pkg/cluster/common.go +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package cluster contains Zarf-specific cluster management functions. -package cluster - -import ( - "context" - "time" - - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/pkg/message" -) - -// Cluster is a wrapper for the k8s package that provides Zarf-specific cluster management functions. -type Cluster struct { - *k8s.K8s -} - -const ( - // DefaultTimeout is the default time to wait for a cluster to be ready. - DefaultTimeout = 30 * time.Second -) - -// NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. -func NewClusterWithWait(ctx context.Context) (*Cluster, error) { - spinner := message.NewProgressSpinner("Waiting for cluster connection") - defer spinner.Stop() - - c := &Cluster{} - var err error - - c.K8s, err = k8s.New(message.Debugf) - if err != nil { - return nil, err - } - - err = c.WaitForHealthyCluster(ctx) - if err != nil { - return nil, err - } - - spinner.Success() - - return c, nil -} - -// NewCluster creates a new Cluster instance and validates connection to the cluster by fetching the Kubernetes version. -func NewCluster() (*Cluster, error) { - c := &Cluster{} - var err error - - c.K8s, err = k8s.New(message.Debugf) - if err != nil { - return nil, err - } - - // Dogsled the version output. We just want to ensure no errors were returned to validate cluster connection. - _, err = c.Clientset.Discovery().ServerVersion() - if err != nil { - return nil, err - } - - return c, nil -} diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 04cd80a580..10b2778d7a 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -12,14 +12,6 @@ import ( "regexp" "time" - "github.com/defenseunicorns/pkg/helpers/v2" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/pkg/layout" - "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/pkg/transform" - "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/google/go-containerregistry/pkg/crane" "github.com/google/uuid" "github.com/mholt/archiver/v3" @@ -30,6 +22,15 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" "sigs.k8s.io/cli-utils/pkg/object" + + "github.com/defenseunicorns/pkg/helpers/v2" + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/layout" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/defenseunicorns/zarf/src/pkg/utils" ) // The chunk size for the tarball chunks. @@ -380,8 +381,8 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri Name: fmt.Sprintf("injector-%s", uuid), Namespace: ZarfNamespaceName, Labels: map[string]string{ - "app": "zarf-injector", - k8s.AgentLabel: "ignore", + "app": "zarf-injector", + AgentLabel: "ignore", }, }, Spec: corev1.PodSpec{ @@ -544,7 +545,7 @@ func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeM return result, nil } - c.Log("No images found on any node. Retrying...") + message.Debug("No images found on any node. Retrying...") timer.Reset(2 * time.Second) } } diff --git a/src/pkg/cluster/injector_test.go b/src/pkg/cluster/injector_test.go index 16005126f0..291eb092ef 100644 --- a/src/pkg/cluster/injector_test.go +++ b/src/pkg/cluster/injector_test.go @@ -18,8 +18,6 @@ import ( "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - - "github.com/defenseunicorns/zarf/src/pkg/k8s" ) func TestCreateInjectorConfigMap(t *testing.T) { @@ -32,9 +30,7 @@ func TestCreateInjectorConfigMap(t *testing.T) { cs := fake.NewSimpleClientset() c := &Cluster{ - &k8s.K8s{ - Clientset: cs, - }, + Clientset: cs, } ctx := context.Background() @@ -52,9 +48,7 @@ func TestCreateService(t *testing.T) { cs := fake.NewSimpleClientset() c := &Cluster{ - &k8s.K8s{ - Clientset: cs, - }, + Clientset: cs, } expected, err := os.ReadFile("./testdata/expected-injection-service.json") @@ -94,10 +88,7 @@ func TestImagesAndNodesForInjection(t *testing.T) { cs := fake.NewSimpleClientset() c := &Cluster{ - &k8s.K8s{ - Clientset: cs, - Log: func(string, ...any) {}, - }, + Clientset: cs, } nodes := []corev1.Node{ diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 6a35ee6a56..c65c968d0e 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -8,11 +8,11 @@ import ( "context" "time" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/pkg/message" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/defenseunicorns/zarf/src/pkg/message" ) // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster. @@ -66,6 +66,6 @@ func AdoptZarfManagedLabels(labels map[string]string) map[string]string { if labels == nil { labels = make(map[string]string) } - labels[k8s.ZarfManagedByLabel] = "zarf" + labels[ZarfManagedByLabel] = "zarf" return labels } diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index a7af5449f6..2e5ff26ecc 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -15,7 +15,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -119,8 +118,8 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentRegistrySecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { + if currentRegistrySecret.Labels[ZarfManagedByLabel] == "zarf" || + (namespace.Labels[AgentLabel] != "skip" && namespace.Labels[AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) @@ -154,8 +153,8 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. } // Check if this is a Zarf managed secret or is in a namespace the Zarf agent will take action in - if currentGitSecret.Labels[k8s.ZarfManagedByLabel] == "zarf" || - (namespace.Labels[k8s.AgentLabel] != "skip" && namespace.Labels[k8s.AgentLabel] != "ignore") { + if currentGitSecret.Labels[ZarfManagedByLabel] == "zarf" || + (namespace.Labels[AgentLabel] != "skip" && namespace.Labels[AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed git secret for namespace: '%s'", namespace.Name) // Create the secret diff --git a/src/pkg/cluster/secrets_test.go b/src/pkg/cluster/secrets_test.go index a3c4a3a35a..dc9f6c9e22 100644 --- a/src/pkg/cluster/secrets_test.go +++ b/src/pkg/cluster/secrets_test.go @@ -10,7 +10,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" ) @@ -33,7 +32,7 @@ func TestGenerateRegistryPullCreds(t *testing.T) { Name: "bar", Namespace: "foo", Labels: map[string]string{ - k8s.ZarfManagedByLabel: "zarf", + ZarfManagedByLabel: "zarf", }, }, Type: corev1.SecretTypeDockerConfigJson, @@ -62,7 +61,7 @@ func TestGenerateGitPullCreds(t *testing.T) { Name: "bar", Namespace: "foo", Labels: map[string]string{ - k8s.ZarfManagedByLabel: "zarf", + ZarfManagedByLabel: "zarf", }, }, Type: corev1.SecretTypeOpaque, diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 5f5541250a..ca3abf75ea 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -19,7 +19,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/types" @@ -99,7 +98,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO namespace.Labels = make(map[string]string) } // This label will tell the Zarf Agent to ignore this namespace. - namespace.Labels[k8s.AgentLabel] = "ignore" + namespace.Labels[AgentLabel] = "ignore" namespaceCopy := namespace _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) if err != nil { @@ -147,7 +146,7 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO return err } if kerrors.IsNotFound(err) { - c.Log("Service account %s/%s not found, retrying...", ns, name) + message.Debug("Service account %s/%s not found, retrying...", ns, name) timer.Reset(1 * time.Second) continue } @@ -280,7 +279,7 @@ func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) err Name: ZarfStateSecretName, Namespace: ZarfNamespaceName, Labels: map[string]string{ - k8s.ZarfManagedByLabel: "zarf", + ZarfManagedByLabel: "zarf", }, }, Type: corev1.SecretTypeOpaque, diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index e8c42983b3..19301432f1 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -17,7 +17,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/pki" "github.com/defenseunicorns/zarf/src/types" @@ -130,10 +129,7 @@ func TestInitZarfState(t *testing.T) { require.NoError(t, err) } c := &Cluster{ - &k8s.K8s{ - Clientset: cs, - Log: func(string, ...any) {}, - }, + Clientset: cs, } // Create default service account in Zarf namespace @@ -173,7 +169,7 @@ func TestInitZarfState(t *testing.T) { } ns, err := cs.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{}) require.NoError(t, err) - require.Equal(t, map[string]string{k8s.AgentLabel: "ignore"}, ns.Labels) + require.Equal(t, map[string]string{AgentLabel: "ignore"}, ns.Labels) } }) } diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index 787852b761..93d8406ced 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -18,7 +18,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -72,7 +71,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { deleteOptions := metav1.DeleteOptions{} listOptions := metav1.ListOptions{ - LabelSelector: k8s.ZarfManagedByLabel + "=zarf", + LabelSelector: ZarfManagedByLabel + "=zarf", } namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) @@ -80,9 +79,9 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { spinner.Errorf(err, "Unable to get k8s namespaces") } else { for _, namespace := range namespaceList.Items { - if _, ok := namespace.Labels[k8s.AgentLabel]; ok { + if _, ok := namespace.Labels[AgentLabel]; ok { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) - delete(namespace.Labels, k8s.AgentLabel) + delete(namespace.Labels, AgentLabel) namespaceCopy := namespace _, err := c.Clientset.CoreV1().Namespaces().Update(ctx, &namespaceCopy, metav1.UpdateOptions{}) if err != nil { diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go index 6c400bb97b..4abfb81e8f 100644 --- a/src/pkg/cluster/zarf_test.go +++ b/src/pkg/cluster/zarf_test.go @@ -17,7 +17,6 @@ import ( "k8s.io/client-go/kubernetes/fake" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/types" ) @@ -205,7 +204,9 @@ func TestPackageSecretNeedsWait(t *testing.T) { func TestGetDeployedPackage(t *testing.T) { t.Parallel() ctx := context.Background() - c := &Cluster{&k8s.K8s{Clientset: fake.NewSimpleClientset()}} + c := &Cluster{ + Clientset: fake.NewSimpleClientset(), + } packages := []types.DeployedPackage{ {Name: "package1"}, @@ -268,9 +269,7 @@ func TestRegistryHPA(t *testing.T) { _, err := cs.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Create(ctx, &hpa, metav1.CreateOptions{}) require.NoError(t, err) c := &Cluster{ - &k8s.K8s{ - Clientset: cs, - }, + Clientset: cs, } err = c.EnableRegHPAScaleDown(ctx) diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go deleted file mode 100644 index 2d24f6d66c..0000000000 --- a/src/pkg/k8s/common.go +++ /dev/null @@ -1,128 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "context" - "fmt" - "time" - - "github.com/go-logr/logr/funcr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/klog/v2" - - // Include the cloud auth plugins - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" -) - -const ( - // ZarfManagedByLabel is used to denote Zarf manages the lifecycle of a resource - ZarfManagedByLabel = "app.kubernetes.io/managed-by" - // AgentLabel is used to give instructions to the Zarf agent - AgentLabel = "zarf.dev/agent" -) - -// New creates a new K8s client. -func New(logger Log) (*K8s, error) { - klog.SetLogger(funcr.New(func(_, args string) { - logger(args) - }, funcr.Options{})) - - config, clientset, err := connect() - if err != nil { - return nil, fmt.Errorf("failed to connect to k8s cluster: %w", err) - } - watcher, err := pkgkubernetes.WatcherForConfig(config) - if err != nil { - return nil, err - } - - return &K8s{ - RestConfig: config, - Clientset: clientset, - Watcher: watcher, - Log: logger, - }, nil -} - -// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. -func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { - const waitDuration = 1 * time.Second - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return fmt.Errorf("error waiting for cluster to report healthy: %w", ctx.Err()) - case <-timer.C: - if k.RestConfig == nil || k.Clientset == nil { - config, clientset, err := connect() - if err != nil { - k.Log("Cluster connection not available yet: %w", err) - timer.Reset(waitDuration) - continue - } - - k.RestConfig = config - k.Clientset = clientset - } - - // Make sure there is at least one running Node - nodeList, err := k.Clientset.CoreV1().Nodes().List(ctx, metav1.ListOptions{}) - if err != nil || len(nodeList.Items) < 1 { - k.Log("No nodes reporting healthy yet: %v\n", err) - timer.Reset(waitDuration) - continue - } - - // Get the cluster pod list - pods, err := k.Clientset.CoreV1().Pods(corev1.NamespaceAll).List(ctx, metav1.ListOptions{}) - if err != nil { - k.Log("Could not get the pod list: %w", err) - timer.Reset(waitDuration) - continue - } - - // Check that at least one pod is in the 'succeeded' or 'running' state - for _, pod := range pods.Items { - if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodRunning { - return nil - } - } - - k.Log("No pods reported 'succeeded' or 'running' state yet.") - timer.Reset(waitDuration) - } - } -} - -// Use the K8s "client-go" library to get the currently active kube context, in the same way that -// "kubectl" gets it if no extra config flags like "--kubeconfig" are passed. -func connect() (config *rest.Config, clientset *kubernetes.Clientset, err error) { - // Build the config from the currently active kube context in the default way that the k8s client-go gets it, which - // is to look at the KUBECONFIG env var - config, err = clientcmd.NewNonInteractiveDeferredLoadingClientConfig( - clientcmd.NewDefaultClientConfigLoadingRules(), - &clientcmd.ConfigOverrides{}).ClientConfig() - - if err != nil { - return nil, nil, err - } - - clientset, err = kubernetes.NewForConfig(config) - if err != nil { - return nil, nil, err - } - - return config, clientset, nil -} diff --git a/src/pkg/k8s/types.go b/src/pkg/k8s/types.go deleted file mode 100644 index 409aa2fe96..0000000000 --- a/src/pkg/k8s/types.go +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package k8s provides a client for interacting with a Kubernetes cluster. -package k8s - -import ( - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" -) - -// Log is a function that logs a message. -type Log func(string, ...any) - -// K8s is a client for interacting with a Kubernetes cluster. -type K8s struct { - Clientset kubernetes.Interface - RestConfig *rest.Config - Watcher watcher.StatusWatcher - Log Log -} - -// GeneratedPKI is a struct for storing generated PKI data. -type GeneratedPKI struct { - CA []byte `json:"ca"` - Cert []byte `json:"cert"` - Key []byte `json:"key"` -} diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go index e60b030ffc..b298a1d0de 100644 --- a/src/pkg/packager/common_test.go +++ b/src/pkg/packager/common_test.go @@ -8,15 +8,15 @@ import ( "fmt" "testing" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/k8s" - "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/types" ) func TestValidatePackageArchitecture(t *testing.T) { @@ -78,14 +78,10 @@ func TestValidatePackageArchitecture(t *testing.T) { t.Parallel() cs := fake.NewSimpleClientset() - logger := func(string, ...interface{}) {} p := &Packager{ cluster: &cluster.Cluster{ - K8s: &k8s.K8s{ - Clientset: cs, - Log: logger, - }, + Clientset: cs, }, cfg: &types.PackagerConfig{ Pkg: types.ZarfPackage{ diff --git a/src/pkg/pki/pki.go b/src/pkg/pki/pki.go index f732b2e1cb..00d1e212a6 100644 --- a/src/pkg/pki/pki.go +++ b/src/pkg/pki/pki.go @@ -15,8 +15,8 @@ import ( "time" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/types" ) // Based off of https://github.com/dmcgowan/quicktls/blob/master/main.go @@ -29,8 +29,8 @@ const org = "Zarf Cluster" const validFor = time.Hour * 24 * 375 // GeneratePKI create a CA and signed server keypair. -func GeneratePKI(host string, dnsNames ...string) k8s.GeneratedPKI { - results := k8s.GeneratedPKI{} +func GeneratePKI(host string, dnsNames ...string) types.GeneratedPKI { + results := types.GeneratedPKI{} ca, caKey, err := generateCA(validFor) if err != nil { diff --git a/src/types/k8s.go b/src/types/k8s.go index 183125c72b..2bf0900cf9 100644 --- a/src/types/k8s.go +++ b/src/types/k8s.go @@ -10,7 +10,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/pkg/k8s" ) // WebhookStatus defines the status of a Component Webhook operating on a Zarf package secret. @@ -50,13 +49,20 @@ const ( ZarfInClusterArtifactServiceURL = ZarfInClusterGitServiceURL + "/api/packages/" + ZarfGitPushUser ) +// GeneratedPKI is a struct for storing generated PKI data. +type GeneratedPKI struct { + CA []byte `json:"ca"` + Cert []byte `json:"cert"` + Key []byte `json:"key"` +} + // ZarfState is maintained as a secret in the Zarf namespace to track Zarf init data. type ZarfState struct { - ZarfAppliance bool `json:"zarfAppliance" jsonschema:"description=Indicates if Zarf was initialized while deploying its own k8s cluster"` - Distro string `json:"distro" jsonschema:"description=K8s distribution of the cluster Zarf was deployed to"` - Architecture string `json:"architecture" jsonschema:"description=Machine architecture of the k8s node(s)"` - StorageClass string `json:"storageClass" jsonschema:"Default StorageClass value Zarf uses for variable templating"` - AgentTLS k8s.GeneratedPKI `json:"agentTLS" jsonschema:"PKI certificate information for the agent pods Zarf manages"` + ZarfAppliance bool `json:"zarfAppliance" jsonschema:"description=Indicates if Zarf was initialized while deploying its own k8s cluster"` + Distro string `json:"distro" jsonschema:"description=K8s distribution of the cluster Zarf was deployed to"` + Architecture string `json:"architecture" jsonschema:"description=Machine architecture of the k8s node(s)"` + StorageClass string `json:"storageClass" jsonschema:"Default StorageClass value Zarf uses for variable templating"` + AgentTLS GeneratedPKI `json:"agentTLS" jsonschema:"PKI certificate information for the agent pods Zarf manages"` GitServer GitServerInfo `json:"gitServer" jsonschema:"description=Information about the repository Zarf is configured to use"` RegistryInfo RegistryInfo `json:"registryInfo" jsonschema:"description=Information about the container registry Zarf is configured to use"` From 1893ae8af01e84eb6b4151d47dbb1991b4566360 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 24 Jun 2024 13:46:16 +0000 Subject: [PATCH 077/132] add context --- go.mod | 5 ++- src/internal/packager/images/pull.go | 49 +++++++++++++++------------- src/pkg/packager/deploy.go | 2 +- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/go.mod b/go.mod index 3f8140481c..7ae48f23fe 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( github.com/fluxcd/pkg/apis/meta v1.3.0 github.com/fluxcd/source-controller/api v1.2.4 github.com/go-git/go-git/v5 v5.11.0 - github.com/go-logr/logr v1.4.1 github.com/goccy/go-yaml v1.11.3 github.com/gofrs/flock v0.8.1 github.com/google/go-containerregistry v0.19.0 @@ -63,8 +62,6 @@ require ( sigs.k8s.io/yaml v1.4.0 ) -require github.com/evanphx/json-patch/v5 v5.6.0 // indirect - require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect @@ -225,6 +222,7 @@ require ( github.com/emicklei/proto v1.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect @@ -246,6 +244,7 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.22.0 // indirect github.com/go-openapi/errors v0.21.0 // indirect diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go index f7203cc684..5636e7229d 100644 --- a/src/internal/packager/images/pull.go +++ b/src/internal/packager/images/pull.go @@ -240,10 +240,10 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er return err } - if err := helpers.Retry(sc, 2, 5*time.Second, message.Warnf); err != nil { + if err := helpers.RetryWithContext(ctx, sc, 2, 5*time.Second, message.Warnf); err != nil { message.Warnf("Failed to save images in parallel, falling back to sequential save: %s", err.Error()) - if err := helpers.Retry(ss, 2, 5*time.Second, message.Warnf); err != nil { + if err := helpers.RetryWithContext(ctx, ss, 2, 5*time.Second, message.Warnf); err != nil { return nil, err } } @@ -348,30 +348,35 @@ func SaveConcurrent(ctx context.Context, cl clayout.Path, m map[transform.Image] for info, img := range m { info, img := info, img eg.Go(func() error { - desc, err := partial.Descriptor(img) - if err != nil { - return err - } + select { + case <-ectx.Done(): + return ectx.Err() + default: + desc, err := partial.Descriptor(img) + if err != nil { + return err + } - if err := cl.WriteImage(img); err != nil { - if err := CleanupInProgressLayers(ectx, img); err != nil { - message.WarnErr(err, "failed to clean up in-progress layers, please run `zarf tools clear-cache`") + if err := cl.WriteImage(img); err != nil { + if err := CleanupInProgressLayers(ectx, img); err != nil { + message.WarnErr(err, "failed to clean up in-progress layers, please run `zarf tools clear-cache`") + } + return err } - return err - } - mu.Lock() - defer mu.Unlock() - annotations := map[string]string{ - ocispec.AnnotationBaseImageName: info.Reference, - } - desc.Annotations = annotations - if err := cl.AppendDescriptor(*desc); err != nil { - return err - } + mu.Lock() + defer mu.Unlock() + annotations := map[string]string{ + ocispec.AnnotationBaseImageName: info.Reference, + } + desc.Annotations = annotations + if err := cl.AppendDescriptor(*desc); err != nil { + return err + } - saved[info] = img - return nil + saved[info] = img + return nil + } }) } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 7c63173b42..74661b753f 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -566,7 +566,7 @@ func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, } // Try repo push up to retry limit - if err := helpers.Retry(tryPush, p.cfg.PkgOpts.Retries, 5*time.Second, message.Warnf); err != nil { + if err := helpers.RetryWithContext(ctx, tryPush, p.cfg.PkgOpts.Retries, 5*time.Second, message.Warnf); err != nil { return fmt.Errorf("unable to push repo %s to the Git Server: %w", repoURL, err) } } From d80bd9a34c4de55033aac50ebfdee5afe4cafcf1 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 24 Jun 2024 14:26:40 +0000 Subject: [PATCH 078/132] test site site test site install npm install audit setup node get dirname in ci changing to only accept internal links feedback --- .github/workflows/test-site.yml | 33 +++++++++++++++++++++++++++++++++ site/hack/copy-examples.js | 4 +++- site/linkinator.config.json | 2 +- site/package-lock.json | 18 +++++++++++------- 4 files changed, 48 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/test-site.yml diff --git a/.github/workflows/test-site.yml b/.github/workflows/test-site.yml new file mode 100644 index 0000000000..0bcb83d271 --- /dev/null +++ b/.github/workflows/test-site.yml @@ -0,0 +1,33 @@ +name: Test Site +on: + pull_request: + +permissions: + contents: read + +# Abort prior jobs in the same workflow / PR +concurrency: + group: site-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./site + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + + - name: npm ci + run: npm ci + + - name: run site build + run: npm run build + + - name: check links + run: npm run link-check diff --git a/site/hack/copy-examples.js b/site/hack/copy-examples.js index a410817149..a3cda64641 100644 --- a/site/hack/copy-examples.js +++ b/site/hack/copy-examples.js @@ -1,8 +1,10 @@ import { promises as fs } from "fs"; import path from "path"; import yaml from "yaml"; +import { fileURLToPath } from 'url'; -const __dirname = import.meta.dirname; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); const examplesDir = path.join(__dirname, "../../examples"); const dstDir = path.join(__dirname, "../src/content/docs/ref/Examples"); diff --git a/site/linkinator.config.json b/site/linkinator.config.json index dcf747e9a4..d9bfaf33b0 100644 --- a/site/linkinator.config.json +++ b/site/linkinator.config.json @@ -1,4 +1,4 @@ { - "skip": "^(https://.*github.com)", + "skip": "^(https://.*)", "verbosity": "error" } diff --git a/site/package-lock.json b/site/package-lock.json index 2fdfd8e985..9c0f032696 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -3166,11 +3166,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -4575,9 +4576,10 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -5476,6 +5478,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -9930,6 +9933,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, From e1c74ae05418f149818e2017e299142897923893 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 24 Jun 2024 17:01:15 +0000 Subject: [PATCH 079/132] fix name --- .../{skeleton_test.go => creator_test.go} | 21 +++++++-- src/pkg/packager/creator/normal_test.go | 44 ------------------- 2 files changed, 17 insertions(+), 48 deletions(-) rename src/pkg/packager/creator/{skeleton_test.go => creator_test.go} (60%) diff --git a/src/pkg/packager/creator/skeleton_test.go b/src/pkg/packager/creator/creator_test.go similarity index 60% rename from src/pkg/packager/creator/skeleton_test.go rename to src/pkg/packager/creator/creator_test.go index ca2e9cd352..18a4a64c0a 100644 --- a/src/pkg/packager/creator/skeleton_test.go +++ b/src/pkg/packager/creator/creator_test.go @@ -14,23 +14,37 @@ import ( "github.com/stretchr/testify/require" ) -func TestSkeletonLoadPackageDefinition(t *testing.T) { +func TestLoadPackageDefinition(t *testing.T) { t.Parallel() - tests := []struct { name string testDir string expectedErr string + creator Creator }{ { name: "valid package definition", testDir: "valid", expectedErr: "", + creator: NewPackageCreator(types.ZarfCreateOptions{}, ""), + }, + { + name: "invalid package definition", + testDir: "invalid", + expectedErr: "package must have at least 1 component", + creator: NewPackageCreator(types.ZarfCreateOptions{}, ""), + }, + { + name: "valid package definition", + testDir: "valid", + expectedErr: "", + creator: NewSkeletonCreator(types.ZarfCreateOptions{}, types.ZarfPublishOptions{}), }, { name: "invalid package definition", testDir: "invalid", expectedErr: "package must have at least 1 component", + creator: NewSkeletonCreator(types.ZarfCreateOptions{}, types.ZarfPublishOptions{}), }, } @@ -40,8 +54,7 @@ func TestSkeletonLoadPackageDefinition(t *testing.T) { t.Parallel() src := layout.New(filepath.Join("testdata", tt.testDir)) - sc := NewSkeletonCreator(types.ZarfCreateOptions{}, types.ZarfPublishOptions{}) - pkg, _, err := sc.LoadPackageDefinition(context.Background(), src) + pkg, _, err := tt.creator.LoadPackageDefinition(context.Background(), src) if tt.expectedErr == "" { require.NoError(t, err) diff --git a/src/pkg/packager/creator/normal_test.go b/src/pkg/packager/creator/normal_test.go index 32b1a2ce4b..9a4830cb1f 100644 --- a/src/pkg/packager/creator/normal_test.go +++ b/src/pkg/packager/creator/normal_test.go @@ -5,12 +5,9 @@ package creator import ( - "context" "path/filepath" "testing" - "github.com/defenseunicorns/zarf/src/pkg/layout" - "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" ) @@ -59,44 +56,3 @@ func TestDifferentialPackagePathSetCorrectly(t *testing.T) { }) } } - -func TestLoadPackageDefinition(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - testDir string - expectedErr string - }{ - { - name: "valid package definition", - testDir: "valid", - expectedErr: "", - }, - { - name: "invalid package definition", - testDir: "invalid", - expectedErr: "package must have at least 1 component", - }, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - src := layout.New(filepath.Join("testdata", tt.testDir)) - pc := NewPackageCreator(types.ZarfCreateOptions{}, "") - pkg, _, err := pc.LoadPackageDefinition(context.Background(), src) - - if tt.expectedErr == "" { - require.NoError(t, err) - require.NotEmpty(t, pkg) - return - } - - require.EqualError(t, err, tt.expectedErr) - require.Empty(t, pkg) - }) - } -} From 41a7addc7fd9b0ddda693f44834a188c80e48b07 Mon Sep 17 00:00:00 2001 From: Tim Seagren Date: Mon, 24 Jun 2024 16:28:45 -0700 Subject: [PATCH 080/132] update help docs for zarf connect to add clarity --- src/config/lang/english.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/config/lang/english.go b/src/config/lang/english.go index adae1f1a41..e3cd8dff5f 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -80,11 +80,11 @@ const ( // zarf connect list CmdConnectListShort = "Lists all available connection shortcuts" - CmdConnectFlagName = "Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6" - CmdConnectFlagNamespace = "Specify the namespace. E.g. namespace=default" - CmdConnectFlagType = "Specify the resource type. E.g. type=svc or type=pod" - CmdConnectFlagLocalPort = "(Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000" - CmdConnectFlagRemotePort = "Specify the remote port of the resource to bind to. E.g. remote-port=8080" + CmdConnectFlagName = "Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6. Ignored if --name is unset" + CmdConnectFlagNamespace = "Specify the namespace. E.g. namespace=default. Ignored if --name is unset" + CmdConnectFlagType = "Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset" + CmdConnectFlagLocalPort = "(Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset" + CmdConnectFlagRemotePort = "Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset" CmdConnectFlagCliOnly = "Disable browser auto-open" CmdConnectPreparingTunnel = "Preparing a tunnel to connect to %s" From d652bd4267ec63c6a55cee817b711873c121c955 Mon Sep 17 00:00:00 2001 From: Tim Seagren Date: Mon, 24 Jun 2024 17:05:00 -0700 Subject: [PATCH 081/132] update site docs --- site/src/content/docs/commands/zarf_connect.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/site/src/content/docs/commands/zarf_connect.md b/site/src/content/docs/commands/zarf_connect.md index 8fb76bc451..df75508db7 100644 --- a/site/src/content/docs/commands/zarf_connect.md +++ b/site/src/content/docs/commands/zarf_connect.md @@ -29,11 +29,11 @@ zarf connect { REGISTRY | LOGGING | GIT | connect-name } [flags] ``` --cli-only Disable browser auto-open -h, --help help for connect - --local-port int (Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000 - --name string Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6 - --namespace string Specify the namespace. E.g. namespace=default (default "zarf") - --remote-port int Specify the remote port of the resource to bind to. E.g. remote-port=8080 - --type string Specify the resource type. E.g. type=svc or type=pod (default "svc") + --local-port int (Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset + --name string Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6. Ignored if --name is unset + --namespace string Specify the namespace. E.g. namespace=default. Ignored if --name is unset (default "zarf") + --remote-port int Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset + --type string Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset (default "svc") ``` ### Options inherited from parent commands From 633df00606467ab2a8fa669dae7233077f65a053 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 24 Jun 2024 13:47:01 +0000 Subject: [PATCH 082/132] codecov on main codecov on main --- .github/workflows/test-unit.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 90429169f1..1f956b92a3 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -10,6 +10,10 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + push: + # Running unit tests on main gives codecov a base to compare PRs against + branches: + - main permissions: contents: read @@ -31,8 +35,8 @@ jobs: - name: Run unit tests run: make test-unit - + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@125fc84a9a348dbcf27191600683ec096ec9021c # v4.4.1 with: - token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file + token: ${{ secrets.CODECOV_TOKEN }} From 4a91b72bbd8c32f4acb2cd97e0d4bb434de17af2 Mon Sep 17 00:00:00 2001 From: Tim Seagren Date: Tue, 25 Jun 2024 09:49:26 -0700 Subject: [PATCH 083/132] remove note for name flag, add punctuation --- site/src/content/docs/commands/zarf_connect.md | 10 +++++----- src/config/lang/english.go | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/site/src/content/docs/commands/zarf_connect.md b/site/src/content/docs/commands/zarf_connect.md index df75508db7..9cc76d83a0 100644 --- a/site/src/content/docs/commands/zarf_connect.md +++ b/site/src/content/docs/commands/zarf_connect.md @@ -29,11 +29,11 @@ zarf connect { REGISTRY | LOGGING | GIT | connect-name } [flags] ``` --cli-only Disable browser auto-open -h, --help help for connect - --local-port int (Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset - --name string Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6. Ignored if --name is unset - --namespace string Specify the namespace. E.g. namespace=default. Ignored if --name is unset (default "zarf") - --remote-port int Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset - --type string Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset (default "svc") + --local-port int (Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset. + --name string Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6. + --namespace string Specify the namespace. E.g. namespace=default. Ignored if --name is unset. (default "zarf") + --remote-port int Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset. + --type string Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset. (default "svc") ``` ### Options inherited from parent commands diff --git a/src/config/lang/english.go b/src/config/lang/english.go index da9dcec228..59dcdc5af2 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -77,11 +77,11 @@ const ( // zarf connect list CmdConnectListShort = "Lists all available connection shortcuts" - CmdConnectFlagName = "Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6. Ignored if --name is unset" - CmdConnectFlagNamespace = "Specify the namespace. E.g. namespace=default. Ignored if --name is unset" - CmdConnectFlagType = "Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset" - CmdConnectFlagLocalPort = "(Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset" - CmdConnectFlagRemotePort = "Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset" + CmdConnectFlagName = "Specify the resource name. E.g. name=unicorns or name=unicorn-pod-7448499f4d-b5bk6." + CmdConnectFlagNamespace = "Specify the namespace. E.g. namespace=default. Ignored if --name is unset." + CmdConnectFlagType = "Specify the resource type. E.g. type=svc or type=pod. Ignored if --name is unset." + CmdConnectFlagLocalPort = "(Optional, autogenerated if not provided) Specify the local port to bind to. E.g. local-port=42000. Ignored if --name is unset." + CmdConnectFlagRemotePort = "Specify the remote port of the resource to bind to. E.g. remote-port=8080. Ignored if --name is unset." CmdConnectFlagCliOnly = "Disable browser auto-open" CmdConnectPreparingTunnel = "Preparing a tunnel to connect to %s" From 984679095d7e07b7db215c3d59a5fd2d712bf9a9 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Tue, 25 Jun 2024 15:43:57 -0500 Subject: [PATCH 084/132] chore!: remove logging from init package --- .github/workflows/test-upgrade.yml | 2 +- README.md | 3 +- adr/0014-oci-publish.md | 2 +- examples/big-bang/zarf.yaml | 2 +- .../capabilities/zarf-types.ts | 357 ++++++++------- packages/README.md | 8 - packages/logging-pgl/connect.yaml | 12 - packages/logging-pgl/pgl-values.yaml | 34 -- packages/logging-pgl/zarf.yaml | 49 -- .../public/tutorials/logging_credentials.html | 58 --- site/public/tutorials/logging_init.html | 343 -------------- .../public/tutorials/logging_init_manual.html | 276 ----------- site/public/tutorials/logging_list.html | 58 --- site/public/tutorials/logging_remove.html | 56 --- .../public/tutorials/package_create_init.html | 23 - site/public/tutorials/zarf_init.html | 429 ------------------ .../src/content/docs/commands/zarf_connect.md | 4 +- site/src/content/docs/commands/zarf_init.md | 9 +- .../docs/commands/zarf_tools_get-creds.md | 1 - site/src/content/docs/contribute/testing.mdx | 3 +- .../content/docs/getting-started/install.mdx | 2 +- site/src/content/docs/index.mdx | 3 +- site/src/content/docs/ref/deploy.mdx | 4 +- site/src/content/docs/ref/init-package.mdx | 3 +- site/src/content/docs/ref/packages.mdx | 6 +- .../1-initializing-a-k8s-cluster.mdx | 2 - .../tutorials/2-deploying-zarf-packages.mdx | 2 +- .../tutorials/3-deploy-a-retro-arcade.mdx | 4 +- .../content/docs/tutorials/4-add-logging.mdx | 115 ----- ...=> 4-creating-a-k8s-cluster-with-zarf.mdx} | 0 .../{6-big-bang.mdx => 5-big-bang.mdx} | 1 - ...nd-deploy.mdx => 6-publish-and-deploy.mdx} | 2 +- ...ackages.mdx => 7-custom-init-packages.mdx} | 0 ...e-adoption.mdx => 8-resource-adoption.mdx} | 0 ....mdx => 9-package-create-differential.mdx} | 0 site/src/content/docs/tutorials/index.mdx | 2 +- src/cmd/connect.go | 2 +- src/config/config.go | 2 - src/config/lang/english.go | 14 +- src/internal/packager/template/template.go | 5 +- src/pkg/cluster/state.go | 11 +- src/pkg/cluster/tunnel.go | 12 - src/pkg/message/credentials.go | 11 +- src/test/e2e/00_use_cli_test.go | 2 +- src/test/e2e/20_zarf_init_test.go | 27 +- src/test/e2e/21_connect_creds_test.go | 40 +- src/types/k8s.go | 1 - zarf.yaml | 5 - 48 files changed, 232 insertions(+), 1775 deletions(-) delete mode 100644 packages/logging-pgl/connect.yaml delete mode 100644 packages/logging-pgl/pgl-values.yaml delete mode 100644 packages/logging-pgl/zarf.yaml delete mode 100644 site/public/tutorials/logging_credentials.html delete mode 100644 site/public/tutorials/logging_init.html delete mode 100644 site/public/tutorials/logging_init_manual.html delete mode 100644 site/public/tutorials/logging_list.html delete mode 100644 site/public/tutorials/logging_remove.html delete mode 100644 site/public/tutorials/zarf_init.html delete mode 100644 site/src/content/docs/tutorials/4-add-logging.mdx rename site/src/content/docs/tutorials/{5-creating-a-k8s-cluster-with-zarf.mdx => 4-creating-a-k8s-cluster-with-zarf.mdx} (100%) rename site/src/content/docs/tutorials/{6-big-bang.mdx => 5-big-bang.mdx} (99%) rename site/src/content/docs/tutorials/{7-publish-and-deploy.mdx => 6-publish-and-deploy.mdx} (99%) rename site/src/content/docs/tutorials/{8-custom-init-packages.mdx => 7-custom-init-packages.mdx} (100%) rename site/src/content/docs/tutorials/{9-resource-adoption.mdx => 8-resource-adoption.mdx} (100%) rename site/src/content/docs/tutorials/{10-package-create-differential.mdx => 9-package-create-differential.mdx} (100%) diff --git a/.github/workflows/test-upgrade.yml b/.github/workflows/test-upgrade.yml index bafbcf3760..b7b8a96dde 100644 --- a/.github/workflows/test-upgrade.yml +++ b/.github/workflows/test-upgrade.yml @@ -71,7 +71,7 @@ jobs: # NOTE: "PATH=$PATH" preserves the default user $PATH. This is needed to maintain the version of zarf installed # in a previous step. This test run will the current release to create a K3s cluster. run: | - sudo env "PATH=$PATH" CI=true zarf init --components k3s,git-server,logging --nodeport 31337 --confirm + sudo env "PATH=$PATH" CI=true zarf init --components k3s,git-server --nodeport 31337 --confirm # Before we run the regular tests we need to aggressively cleanup files to reduce disk pressure - name: Cleanup files diff --git a/README.md b/README.md index 7ea6cf63c0..221ac84ae0 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,12 @@ Zarf eliminates the [complexity of air gap software delivery](https://www.itopst - Automate Kubernetes deployments in disconnected environments - Automate [Software Bill of Materials (SBOM)](https://docs.zarf.dev/ref/sboms/) generation -- Build and [publish packages as OCI image artifacts](https://docs.zarf.dev/tutorials/7-publish-and-deploy/) +- Build and [publish packages as OCI image artifacts](https://docs.zarf.dev/tutorials/6-publish-and-deploy/) - Provide a [web dashboard](https://docs.zarf.dev/ref/sboms/#the-sbom-viewer) for viewing SBOM output - Create and verify package signatures with [cosign](https://github.com/sigstore/cosign) - [Publish](https://docs.zarf.dev/commands/zarf_package_publish), [pull](https://docs.zarf.dev/commands/zarf_package_pull), and [deploy](https://docs.zarf.dev/commands/zarf_package_deploy) packages from an [OCI registry](https://opencontainers.org/) - Powerful component lifecycle [actions](https://docs.zarf.dev/ref/actions) - Deploy a new cluster while fully disconnected with [K3s](https://k3s.io/) or into any existing cluster using a [kube config](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) -- Builtin logging stack with [Loki](https://grafana.com/oss/loki/) - Built-in Git server with [Gitea](https://gitea.io/en-us/) - Built-in Docker registry - Builtin [K9s Dashboard](https://k9scli.io/) for managing a cluster from the terminal diff --git a/adr/0014-oci-publish.md b/adr/0014-oci-publish.md index e06c7fe739..20a5407815 100644 --- a/adr/0014-oci-publish.md +++ b/adr/0014-oci-publish.md @@ -47,7 +47,7 @@ zarf-package-adr-arm64.tar.zst With this new structure in place, we can now publish Zarf packages as OCI artifacts. Under the hood this implements the `oras` Go library using Docker's authentication system. For interacting with these packages, the `oci://` package path prefix has been added (ex. `zarf package publish oci://...`). -For an example of this in action, please see the corresponding [tutorial](../docs/5-zarf-tutorials/7-publish-and-deploy.md). +For an example of this in action, please see the corresponding [tutorial](../docs/5-zarf-tutorials/6-publish-and-deploy.md). ## Consequences diff --git a/examples/big-bang/zarf.yaml b/examples/big-bang/zarf.yaml index 7a9b5735b6..534ebd0ea4 100644 --- a/examples/big-bang/zarf.yaml +++ b/examples/big-bang/zarf.yaml @@ -77,7 +77,7 @@ x-mdx: | - To see a tutorial for the creation and deployment of this package see the [Big Bang Tutorial](/tutorials/6-big-bang/). + To see a tutorial for the creation and deployment of this package see the [Big Bang Tutorial](/tutorials/5-big-bang/). :::caution diff --git a/examples/component-webhooks/capabilities/zarf-types.ts b/examples/component-webhooks/capabilities/zarf-types.ts index 11e840d2d3..4ead23ab96 100644 --- a/examples/component-webhooks/capabilities/zarf-types.ts +++ b/examples/component-webhooks/capabilities/zarf-types.ts @@ -9,28 +9,28 @@ export interface ZarfTypes { DeployedPackage: DeployedPackage; - ZarfPackage: ZarfPackage; + ZarfPackage: Data; ZarfState: ZarfState; } export interface DeployedPackage { cliVersion: string; - componentWebhooks?: { [key: string]: { [key: string]: Webhook } }; - connectStrings?: { [key: string]: ConnectString }; - data: ZarfPackage; - deployedComponents: DeployedComponent[]; + componentWebhooks?: { [key: string]: { [key: string]: ComponentWebhookValue } }; + connectStrings?: { [key: string]: ConnectStringValue }; + data: Data; + deployedComponents: DeployedComponentElement[]; generation: number; name: string; } -export interface Webhook { +export interface ComponentWebhookValue { name: string; observedGeneration: number; status: string; waitDurationSeconds?: number; } -export interface ConnectString { +export interface ConnectStringValue { /** * Descriptive text that explains what the resource you would be connecting to is used for */ @@ -41,19 +41,19 @@ export interface ConnectString { url: string; } -export interface ZarfPackage { +export interface Data { /** * Zarf-generated package build data */ - build?: ZarfBuildData; + build?: Build; /** * List of components to deploy in this package */ - components: ZarfComponent[]; + components: ComponentElement[]; /** * Constant template values applied on deploy for K8s resources */ - constants?: ZarfPackageConstant[]; + constants?: ConstantElement[]; /** * The kind of Zarf package */ @@ -61,7 +61,7 @@ export interface ZarfPackage { /** * Package metadata */ - metadata?: ZarfMetadata; + metadata?: Metadata; /** * Variable template values applied on deploy for K8s resources */ @@ -71,7 +71,7 @@ export interface ZarfPackage { /** * Zarf-generated package build data */ -export interface ZarfBuildData { +export interface Build { /** * The architecture this package was created on */ @@ -84,6 +84,15 @@ export interface ZarfBuildData { * List of components that were not included in this package due to differential packaging */ differentialMissing?: string[]; + /** + * Version of a previously built package used as the basis for creating this differential + * package + */ + differentialPackageVersion?: string; + /** + * The flavor of Zarf used to build this package + */ + flavor?: string; /** * The minimum version of Zarf that does not have breaking package structure changes */ @@ -114,15 +123,15 @@ export interface ZarfBuildData { version: string; } -export interface ZarfComponent { +export interface ComponentElement { /** * Custom commands to run at various stages of a package lifecycle */ - actions?: ZarfComponentActions; + actions?: Actions; /** * Helm charts to install during package deploy */ - charts?: ZarfChart[]; + charts?: ChartElement[]; /** * [Deprecated] Specify a path to a public key to validate signed online resources. This * will be removed in Zarf v1.0.0. @@ -131,7 +140,7 @@ export interface ZarfComponent { /** * Datasets to inject into a container in the target cluster */ - dataInjections?: ZarfDataInjection[]; + dataInjections?: DataInjectionElement[]; /** * Determines the default Y/N state for installing this component on package deploy */ @@ -143,11 +152,11 @@ export interface ZarfComponent { /** * Extend component functionality with additional features */ - extensions?: ZarfComponentExtensions; + extensions?: Extensions; /** * Files or folders to place on disk during package deployment */ - files?: ZarfFile[]; + files?: FileElement[]; /** * [Deprecated] Create a user selector field based on all components in the same group. This * will be removed in Zarf v1.0.0. Consider using 'only.flavor' instead. @@ -160,11 +169,11 @@ export interface ZarfComponent { /** * Import a component from another Zarf package */ - import?: ZarfComponentImport; + import?: Import; /** * Kubernetes manifests to be included in a generated Helm chart on package deploy */ - manifests?: ZarfManifest[]; + manifests?: ManifestElement[]; /** * The name of the component */ @@ -172,7 +181,7 @@ export interface ZarfComponent { /** * Filter when this component is included in package creation or deployment */ - only?: ZarfComponentOnlyTarget; + only?: Only; /** * List of git repos to include in the package */ @@ -185,25 +194,25 @@ export interface ZarfComponent { * [Deprecated] (replaced by actions) Custom commands to run before or after package * deployment. This will be removed in Zarf v1.0.0. */ - scripts?: DeprecatedZarfComponentScripts; + scripts?: Scripts; } /** * Custom commands to run at various stages of a package lifecycle */ -export interface ZarfComponentActions { +export interface Actions { /** * Actions to run during package creation */ - onCreate?: ZarfComponentActionSet; + onCreate?: OnCreate; /** * Actions to run during package deployment */ - onDeploy?: ZarfComponentActionSet; + onDeploy?: OnCreate; /** * Actions to run during package removal */ - onRemove?: ZarfComponentActionSet; + onRemove?: OnCreate; } /** @@ -213,30 +222,30 @@ export interface ZarfComponentActions { * * Actions to run during package removal */ -export interface ZarfComponentActionSet { +export interface OnCreate { /** * Actions to run at the end of an operation */ - after?: ZarfComponentAction[]; + after?: AfterElement[]; /** * Actions to run at the start of an operation */ - before?: ZarfComponentAction[]; + before?: AfterElement[]; /** * Default configuration for all actions in this set */ - defaults?: ZarfComponentActionDefaults; + defaults?: Defaults; /** * Actions to run if all operations fail */ - onFailure?: ZarfComponentAction[]; + onFailure?: AfterElement[]; /** * Actions to run if all operations succeed */ - onSuccess?: ZarfComponentAction[]; + onSuccess?: AfterElement[]; } -export interface ZarfComponentAction { +export interface AfterElement { /** * The command to run. Must specify either cmd or wait for the action to do anything. */ @@ -275,20 +284,20 @@ export interface ZarfComponentAction { * (onDeploy/cmd only) An array of variables to update with the output of the command. These * variables will be available to all remaining actions and components in the package. */ - setVariables?: ZarfComponentActionSetVariable[]; + setVariables?: SetVariableElement[]; /** * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on * supported operating systems */ - shell?: ZarfComponentActionShell; + shell?: Shell; /** * Wait for a condition to be met before continuing. Must specify either cmd or wait for the * action. See the 'zarf tools wait-for' command for more info. */ - wait?: ZarfComponentActionWait; + wait?: Wait; } -export interface ZarfComponentActionSetVariable { +export interface SetVariableElement { /** * Whether to automatically indent the variable's value (if multiline) when templating. * Based on the number of chars before the start of ###ZARF_VAR_. @@ -304,7 +313,7 @@ export interface ZarfComponentActionSetVariable { */ pattern?: string; /** - * Whether to mark this variable as sensitive to not print it in the Zarf log + * Whether to mark this variable as sensitive to not print it in the log */ sensitive?: boolean; /** @@ -327,7 +336,7 @@ export enum Type { * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on * supported operating systems */ -export interface ZarfComponentActionShell { +export interface Shell { /** * (default 'sh') Indicates a preference for the shell to use on macOS systems */ @@ -347,24 +356,24 @@ export interface ZarfComponentActionShell { * Wait for a condition to be met before continuing. Must specify either cmd or wait for the * action. See the 'zarf tools wait-for' command for more info. */ -export interface ZarfComponentActionWait { +export interface Wait { /** * Wait for a condition to be met in the cluster before continuing. Only one of cluster or * network can be specified. */ - cluster?: ZarfComponentActionWaitCluster; + cluster?: WaitCluster; /** * Wait for a condition to be met on the network before continuing. Only one of cluster or * network can be specified. */ - network?: ZarfComponentActionWaitNetwork; + network?: Network; } /** * Wait for a condition to be met in the cluster before continuing. Only one of cluster or * network can be specified. */ -export interface ZarfComponentActionWaitCluster { +export interface WaitCluster { /** * The condition or jsonpath state to wait for; defaults to exist */ @@ -387,7 +396,7 @@ export interface ZarfComponentActionWaitCluster { * Wait for a condition to be met on the network before continuing. Only one of cluster or * network can be specified. */ -export interface ZarfComponentActionWaitNetwork { +export interface Network { /** * The address to wait for */ @@ -414,7 +423,7 @@ export enum Protocol { /** * Default configuration for all actions in this set */ -export interface ZarfComponentActionDefaults { +export interface Defaults { /** * Working directory for commands (default CWD) */ @@ -439,21 +448,21 @@ export interface ZarfComponentActionDefaults { * (cmd only) Indicates a preference for a shell for the provided cmd to be executed in on * supported operating systems */ - shell?: ZarfComponentActionShell; + shell?: Shell; } -export interface ZarfChart { +export interface ChartElement { /** - * The path to the chart in the repo if using a git repo instead of a helm repo + * (git repo only) The sub directory to the chart within a git repo */ gitPath?: string; /** - * The path to the chart folder + * The path to a local chart's folder or .tgz archive */ localPath?: string; /** - * The name of the chart to deploy; this should be the name of the chart as it is installed - * in the helm repo + * The name of the chart within Zarf; note that this must be unique and does not need to be + * the same as the name in the chart repo */ name: string; /** @@ -465,26 +474,49 @@ export interface ZarfChart { */ noWait?: boolean; /** - * The name of the release to create; defaults to the name of the chart + * The name of the Helm release to create (defaults to the Zarf name of the chart) */ releaseName?: string; + /** + * The name of a chart within a Helm repository (defaults to the Zarf name of the chart) + */ + repoName?: string; /** * The URL of the OCI registry, chart repository, or git repo where the helm chart is stored */ url?: string; /** * List of local values file paths or remote URLs to include in the package; these will be - * merged together + * merged together when deployed */ valuesFiles?: string[]; + /** + * [alpha] List of variables to set in the Helm chart + */ + variables?: ChartVariable[]; /** * The version of the chart to deploy; for git-based charts this is also the tag of the git - * repo + * repo by default (when not using the '@' syntax for 'repos') */ version?: string; } -export interface ZarfDataInjection { +export interface ChartVariable { + /** + * A brief description of what the variable controls + */ + description: string; + /** + * The name of the variable + */ + name: string; + /** + * The path within the Helm chart values where this variable applies + */ + path: string; +} + +export interface DataInjectionElement { /** * Compress the data before transmitting using gzip. Note: this requires support for * tar/gzip locally and in the target image. @@ -498,13 +530,13 @@ export interface ZarfDataInjection { /** * The target pod + container to inject the data into */ - target: ZarfContainerTarget; + target: Target; } /** * The target pod + container to inject the data into */ -export interface ZarfContainerTarget { +export interface Target { /** * The container name to target for data injection */ @@ -526,17 +558,17 @@ export interface ZarfContainerTarget { /** * Extend component functionality with additional features */ -export interface ZarfComponentExtensions { +export interface Extensions { /** * Configurations for installing Big Bang and Flux in the cluster */ - bigbang?: BigBang; + bigbang?: Bigbang; } /** * Configurations for installing Big Bang and Flux in the cluster */ -export interface BigBang { +export interface Bigbang { /** * Optional paths to Flux kustomize strategic merge patch files */ @@ -559,7 +591,7 @@ export interface BigBang { version: string; } -export interface ZarfFile { +export interface FileElement { /** * (files only) Determines if the file should be made executable during package deploy */ @@ -590,7 +622,7 @@ export interface ZarfFile { /** * Import a component from another Zarf package */ -export interface ZarfComponentImport { +export interface Import { /** * The name of the component to import from the referenced zarf.yaml */ @@ -605,7 +637,7 @@ export interface ZarfComponentImport { url?: string; } -export interface ZarfManifest { +export interface ManifestElement { /** * List of local K8s YAML files or remote URLs to deploy (in order) */ @@ -636,11 +668,11 @@ export interface ZarfManifest { /** * Filter when this component is included in package creation or deployment */ -export interface ZarfComponentOnlyTarget { +export interface Only { /** * Only deploy component to specified clusters */ - cluster?: ZarfComponentOnlyCluster; + cluster?: OnlyCluster; /** * Only include this component when a matching '--flavor' is specified on 'zarf package * create' @@ -655,7 +687,7 @@ export interface ZarfComponentOnlyTarget { /** * Only deploy component to specified clusters */ -export interface ZarfComponentOnlyCluster { +export interface OnlyCluster { /** * Only create and deploy to clusters of the given architecture */ @@ -687,7 +719,7 @@ export enum LocalOS { * [Deprecated] (replaced by actions) Custom commands to run before or after package * deployment. This will be removed in Zarf v1.0.0. */ -export interface DeprecatedZarfComponentScripts { +export interface Scripts { /** * Scripts to run after the component successfully deploys */ @@ -714,7 +746,7 @@ export interface DeprecatedZarfComponentScripts { timeoutSeconds?: number; } -export interface ZarfPackageConstant { +export interface ConstantElement { /** * Whether to automatically indent the variable's value (if multiline) when templating. * Based on the number of chars before the start of ###ZARF_CONST_. @@ -751,7 +783,7 @@ export enum Kind { /** * Package metadata */ -export interface ZarfMetadata { +export interface Metadata { /** * Checksum of a checksums.txt file that contains checksums all the layers within the * package. @@ -773,10 +805,6 @@ export interface ZarfMetadata { * Link to package documentation when online */ documentation?: string; - /** - * An image URL to embed in this package (Reserved for future use in Zarf UI) - */ - image?: string; /** * Name to identify this Zarf package */ @@ -829,8 +857,8 @@ export interface ZarfPackageVariable { */ name: string; /** - * An optional regex pattern that a variable value must match before a package can be - * deployed. + * An optional regex pattern that a variable value must match before a package deployment + * can continue. */ pattern?: string; /** @@ -838,7 +866,7 @@ export interface ZarfPackageVariable { */ prompt?: boolean; /** - * Whether to mark this variable as sensitive to not print it in the Zarf log + * Whether to mark this variable as sensitive to not print it in the log */ sensitive?: boolean; /** @@ -848,20 +876,20 @@ export interface ZarfPackageVariable { type?: Type; } -export interface DeployedComponent { - installedCharts: InstalledChart[]; +export interface DeployedComponentElement { + installedCharts: InstalledChartElement[]; name: string; observedGeneration: number; status: string; } -export interface InstalledChart { +export interface InstalledChartElement { chartName: string; namespace: string; } export interface ZarfState { - agentTLS: GeneratedPKI; + agentTLS: AgentTLS; /** * Machine architecture of the k8s node(s) */ @@ -869,7 +897,7 @@ export interface ZarfState { /** * Information about the artifact registry Zarf is configured to use */ - artifactServer: ArtifactServerInfo; + artifactServer: ArtifactServer; /** * K8s distribution of the cluster Zarf was deployed to */ @@ -877,11 +905,7 @@ export interface ZarfState { /** * Information about the repository Zarf is configured to use */ - gitServer: GitServerInfo; - /** - * Secret value that the internal Grafana server was seeded with - */ - loggingSecret: string; + gitServer: GitServer; /** * Information about the container registry Zarf is configured to use */ @@ -893,7 +917,7 @@ export interface ZarfState { zarfAppliance: boolean; } -export interface GeneratedPKI { +export interface AgentTLS { ca: string; cert: string; key: string; @@ -902,7 +926,7 @@ export interface GeneratedPKI { /** * Information about the artifact registry Zarf is configured to use */ -export interface ArtifactServerInfo { +export interface ArtifactServer { /** * URL address of the artifact registry */ @@ -924,7 +948,7 @@ export interface ArtifactServerInfo { /** * Information about the repository Zarf is configured to use */ -export interface GitServerInfo { +export interface GitServer { /** * URL address of the git server */ @@ -1161,40 +1185,42 @@ function r(name: string) { const typeMap: any = { "ZarfTypes": o([ { json: "DeployedPackage", js: "DeployedPackage", typ: r("DeployedPackage") }, - { json: "ZarfPackage", js: "ZarfPackage", typ: r("ZarfPackage") }, + { json: "ZarfPackage", js: "ZarfPackage", typ: r("Data") }, { json: "ZarfState", js: "ZarfState", typ: r("ZarfState") }, ], false), "DeployedPackage": o([ { json: "cliVersion", js: "cliVersion", typ: "" }, - { json: "componentWebhooks", js: "componentWebhooks", typ: u(undefined, m(m(r("Webhook")))) }, - { json: "connectStrings", js: "connectStrings", typ: u(undefined, m(r("ConnectString"))) }, - { json: "data", js: "data", typ: r("ZarfPackage") }, - { json: "deployedComponents", js: "deployedComponents", typ: a(r("DeployedComponent")) }, + { json: "componentWebhooks", js: "componentWebhooks", typ: u(undefined, m(m(r("ComponentWebhookValue")))) }, + { json: "connectStrings", js: "connectStrings", typ: u(undefined, m(r("ConnectStringValue"))) }, + { json: "data", js: "data", typ: r("Data") }, + { json: "deployedComponents", js: "deployedComponents", typ: a(r("DeployedComponentElement")) }, { json: "generation", js: "generation", typ: 0 }, { json: "name", js: "name", typ: "" }, ], false), - "Webhook": o([ + "ComponentWebhookValue": o([ { json: "name", js: "name", typ: "" }, { json: "observedGeneration", js: "observedGeneration", typ: 0 }, { json: "status", js: "status", typ: "" }, { json: "waitDurationSeconds", js: "waitDurationSeconds", typ: u(undefined, 0) }, ], false), - "ConnectString": o([ + "ConnectStringValue": o([ { json: "description", js: "description", typ: "" }, { json: "url", js: "url", typ: "" }, ], false), - "ZarfPackage": o([ - { json: "build", js: "build", typ: u(undefined, r("ZarfBuildData")) }, - { json: "components", js: "components", typ: a(r("ZarfComponent")) }, - { json: "constants", js: "constants", typ: u(undefined, a(r("ZarfPackageConstant"))) }, + "Data": o([ + { json: "build", js: "build", typ: u(undefined, r("Build")) }, + { json: "components", js: "components", typ: a(r("ComponentElement")) }, + { json: "constants", js: "constants", typ: u(undefined, a(r("ConstantElement"))) }, { json: "kind", js: "kind", typ: r("Kind") }, - { json: "metadata", js: "metadata", typ: u(undefined, r("ZarfMetadata")) }, + { json: "metadata", js: "metadata", typ: u(undefined, r("Metadata")) }, { json: "variables", js: "variables", typ: u(undefined, a(r("ZarfPackageVariable"))) }, ], false), - "ZarfBuildData": o([ + "Build": o([ { json: "architecture", js: "architecture", typ: "" }, { json: "differential", js: "differential", typ: u(undefined, true) }, { json: "differentialMissing", js: "differentialMissing", typ: u(undefined, a("")) }, + { json: "differentialPackageVersion", js: "differentialPackageVersion", typ: u(undefined, "") }, + { json: "flavor", js: "flavor", typ: u(undefined, "") }, { json: "lastNonBreakingVersion", js: "lastNonBreakingVersion", typ: u(undefined, "") }, { json: "migrations", js: "migrations", typ: u(undefined, a("")) }, { json: "registryOverrides", js: "registryOverrides", typ: u(undefined, m("")) }, @@ -1203,38 +1229,38 @@ const typeMap: any = { { json: "user", js: "user", typ: "" }, { json: "version", js: "version", typ: "" }, ], false), - "ZarfComponent": o([ - { json: "actions", js: "actions", typ: u(undefined, r("ZarfComponentActions")) }, - { json: "charts", js: "charts", typ: u(undefined, a(r("ZarfChart"))) }, + "ComponentElement": o([ + { json: "actions", js: "actions", typ: u(undefined, r("Actions")) }, + { json: "charts", js: "charts", typ: u(undefined, a(r("ChartElement"))) }, { json: "cosignKeyPath", js: "cosignKeyPath", typ: u(undefined, "") }, - { json: "dataInjections", js: "dataInjections", typ: u(undefined, a(r("ZarfDataInjection"))) }, + { json: "dataInjections", js: "dataInjections", typ: u(undefined, a(r("DataInjectionElement"))) }, { json: "default", js: "default", typ: u(undefined, true) }, { json: "description", js: "description", typ: u(undefined, "") }, - { json: "extensions", js: "extensions", typ: u(undefined, r("ZarfComponentExtensions")) }, - { json: "files", js: "files", typ: u(undefined, a(r("ZarfFile"))) }, + { json: "extensions", js: "extensions", typ: u(undefined, r("Extensions")) }, + { json: "files", js: "files", typ: u(undefined, a(r("FileElement"))) }, { json: "group", js: "group", typ: u(undefined, "") }, { json: "images", js: "images", typ: u(undefined, a("")) }, - { json: "import", js: "import", typ: u(undefined, r("ZarfComponentImport")) }, - { json: "manifests", js: "manifests", typ: u(undefined, a(r("ZarfManifest"))) }, + { json: "import", js: "import", typ: u(undefined, r("Import")) }, + { json: "manifests", js: "manifests", typ: u(undefined, a(r("ManifestElement"))) }, { json: "name", js: "name", typ: "" }, - { json: "only", js: "only", typ: u(undefined, r("ZarfComponentOnlyTarget")) }, + { json: "only", js: "only", typ: u(undefined, r("Only")) }, { json: "repos", js: "repos", typ: u(undefined, a("")) }, { json: "required", js: "required", typ: u(undefined, true) }, - { json: "scripts", js: "scripts", typ: u(undefined, r("DeprecatedZarfComponentScripts")) }, + { json: "scripts", js: "scripts", typ: u(undefined, r("Scripts")) }, ], false), - "ZarfComponentActions": o([ - { json: "onCreate", js: "onCreate", typ: u(undefined, r("ZarfComponentActionSet")) }, - { json: "onDeploy", js: "onDeploy", typ: u(undefined, r("ZarfComponentActionSet")) }, - { json: "onRemove", js: "onRemove", typ: u(undefined, r("ZarfComponentActionSet")) }, + "Actions": o([ + { json: "onCreate", js: "onCreate", typ: u(undefined, r("OnCreate")) }, + { json: "onDeploy", js: "onDeploy", typ: u(undefined, r("OnCreate")) }, + { json: "onRemove", js: "onRemove", typ: u(undefined, r("OnCreate")) }, ], false), - "ZarfComponentActionSet": o([ - { json: "after", js: "after", typ: u(undefined, a(r("ZarfComponentAction"))) }, - { json: "before", js: "before", typ: u(undefined, a(r("ZarfComponentAction"))) }, - { json: "defaults", js: "defaults", typ: u(undefined, r("ZarfComponentActionDefaults")) }, - { json: "onFailure", js: "onFailure", typ: u(undefined, a(r("ZarfComponentAction"))) }, - { json: "onSuccess", js: "onSuccess", typ: u(undefined, a(r("ZarfComponentAction"))) }, + "OnCreate": o([ + { json: "after", js: "after", typ: u(undefined, a(r("AfterElement"))) }, + { json: "before", js: "before", typ: u(undefined, a(r("AfterElement"))) }, + { json: "defaults", js: "defaults", typ: u(undefined, r("Defaults")) }, + { json: "onFailure", js: "onFailure", typ: u(undefined, a(r("AfterElement"))) }, + { json: "onSuccess", js: "onSuccess", typ: u(undefined, a(r("AfterElement"))) }, ], false), - "ZarfComponentAction": o([ + "AfterElement": o([ { json: "cmd", js: "cmd", typ: u(undefined, "") }, { json: "description", js: "description", typ: u(undefined, "") }, { json: "dir", js: "dir", typ: u(undefined, "") }, @@ -1243,78 +1269,85 @@ const typeMap: any = { { json: "maxTotalSeconds", js: "maxTotalSeconds", typ: u(undefined, 0) }, { json: "mute", js: "mute", typ: u(undefined, true) }, { json: "setVariable", js: "setVariable", typ: u(undefined, "") }, - { json: "setVariables", js: "setVariables", typ: u(undefined, a(r("ZarfComponentActionSetVariable"))) }, - { json: "shell", js: "shell", typ: u(undefined, r("ZarfComponentActionShell")) }, - { json: "wait", js: "wait", typ: u(undefined, r("ZarfComponentActionWait")) }, + { json: "setVariables", js: "setVariables", typ: u(undefined, a(r("SetVariableElement"))) }, + { json: "shell", js: "shell", typ: u(undefined, r("Shell")) }, + { json: "wait", js: "wait", typ: u(undefined, r("Wait")) }, ], false), - "ZarfComponentActionSetVariable": o([ + "SetVariableElement": o([ { json: "autoIndent", js: "autoIndent", typ: u(undefined, true) }, { json: "name", js: "name", typ: "" }, { json: "pattern", js: "pattern", typ: u(undefined, "") }, { json: "sensitive", js: "sensitive", typ: u(undefined, true) }, { json: "type", js: "type", typ: u(undefined, r("Type")) }, ], false), - "ZarfComponentActionShell": o([ + "Shell": o([ { json: "darwin", js: "darwin", typ: u(undefined, "") }, { json: "linux", js: "linux", typ: u(undefined, "") }, { json: "windows", js: "windows", typ: u(undefined, "") }, ], false), - "ZarfComponentActionWait": o([ - { json: "cluster", js: "cluster", typ: u(undefined, r("ZarfComponentActionWaitCluster")) }, - { json: "network", js: "network", typ: u(undefined, r("ZarfComponentActionWaitNetwork")) }, + "Wait": o([ + { json: "cluster", js: "cluster", typ: u(undefined, r("WaitCluster")) }, + { json: "network", js: "network", typ: u(undefined, r("Network")) }, ], false), - "ZarfComponentActionWaitCluster": o([ + "WaitCluster": o([ { json: "condition", js: "condition", typ: u(undefined, "") }, { json: "kind", js: "kind", typ: "" }, { json: "name", js: "name", typ: "" }, { json: "namespace", js: "namespace", typ: u(undefined, "") }, ], false), - "ZarfComponentActionWaitNetwork": o([ + "Network": o([ { json: "address", js: "address", typ: "" }, { json: "code", js: "code", typ: u(undefined, 0) }, { json: "protocol", js: "protocol", typ: r("Protocol") }, ], false), - "ZarfComponentActionDefaults": o([ + "Defaults": o([ { json: "dir", js: "dir", typ: u(undefined, "") }, { json: "env", js: "env", typ: u(undefined, a("")) }, { json: "maxRetries", js: "maxRetries", typ: u(undefined, 0) }, { json: "maxTotalSeconds", js: "maxTotalSeconds", typ: u(undefined, 0) }, { json: "mute", js: "mute", typ: u(undefined, true) }, - { json: "shell", js: "shell", typ: u(undefined, r("ZarfComponentActionShell")) }, + { json: "shell", js: "shell", typ: u(undefined, r("Shell")) }, ], false), - "ZarfChart": o([ + "ChartElement": o([ { json: "gitPath", js: "gitPath", typ: u(undefined, "") }, { json: "localPath", js: "localPath", typ: u(undefined, "") }, { json: "name", js: "name", typ: "" }, { json: "namespace", js: "namespace", typ: "" }, { json: "noWait", js: "noWait", typ: u(undefined, true) }, { json: "releaseName", js: "releaseName", typ: u(undefined, "") }, + { json: "repoName", js: "repoName", typ: u(undefined, "") }, { json: "url", js: "url", typ: u(undefined, "") }, { json: "valuesFiles", js: "valuesFiles", typ: u(undefined, a("")) }, + { json: "variables", js: "variables", typ: u(undefined, a(r("ChartVariable"))) }, { json: "version", js: "version", typ: u(undefined, "") }, ], false), - "ZarfDataInjection": o([ + "ChartVariable": o([ + { json: "description", js: "description", typ: "" }, + { json: "name", js: "name", typ: "" }, + { json: "path", js: "path", typ: "" }, + ], false), + "DataInjectionElement": o([ { json: "compress", js: "compress", typ: u(undefined, true) }, { json: "source", js: "source", typ: "" }, - { json: "target", js: "target", typ: r("ZarfContainerTarget") }, + { json: "target", js: "target", typ: r("Target") }, ], false), - "ZarfContainerTarget": o([ + "Target": o([ { json: "container", js: "container", typ: "" }, { json: "namespace", js: "namespace", typ: "" }, { json: "path", js: "path", typ: "" }, { json: "selector", js: "selector", typ: "" }, ], false), - "ZarfComponentExtensions": o([ - { json: "bigbang", js: "bigbang", typ: u(undefined, r("BigBang")) }, + "Extensions": o([ + { json: "bigbang", js: "bigbang", typ: u(undefined, r("Bigbang")) }, ], false), - "BigBang": o([ + "Bigbang": o([ { json: "fluxPatchFiles", js: "fluxPatchFiles", typ: u(undefined, a("")) }, { json: "repo", js: "repo", typ: u(undefined, "") }, { json: "skipFlux", js: "skipFlux", typ: u(undefined, true) }, { json: "valuesFiles", js: "valuesFiles", typ: u(undefined, a("")) }, { json: "version", js: "version", typ: "" }, ], false), - "ZarfFile": o([ + "FileElement": o([ { json: "executable", js: "executable", typ: u(undefined, true) }, { json: "extractPath", js: "extractPath", typ: u(undefined, "") }, { json: "shasum", js: "shasum", typ: u(undefined, "") }, @@ -1322,12 +1355,12 @@ const typeMap: any = { { json: "symlinks", js: "symlinks", typ: u(undefined, a("")) }, { json: "target", js: "target", typ: "" }, ], false), - "ZarfComponentImport": o([ + "Import": o([ { json: "name", js: "name", typ: u(undefined, "") }, { json: "path", js: "path", typ: u(undefined, "") }, { json: "url", js: "url", typ: u(undefined, "") }, ], false), - "ZarfManifest": o([ + "ManifestElement": o([ { json: "files", js: "files", typ: u(undefined, a("")) }, { json: "kustomizations", js: "kustomizations", typ: u(undefined, a("")) }, { json: "kustomizeAllowAnyDirectory", js: "kustomizeAllowAnyDirectory", typ: u(undefined, true) }, @@ -1335,16 +1368,16 @@ const typeMap: any = { { json: "namespace", js: "namespace", typ: u(undefined, "") }, { json: "noWait", js: "noWait", typ: u(undefined, true) }, ], false), - "ZarfComponentOnlyTarget": o([ - { json: "cluster", js: "cluster", typ: u(undefined, r("ZarfComponentOnlyCluster")) }, + "Only": o([ + { json: "cluster", js: "cluster", typ: u(undefined, r("OnlyCluster")) }, { json: "flavor", js: "flavor", typ: u(undefined, "") }, { json: "localOS", js: "localOS", typ: u(undefined, r("LocalOS")) }, ], false), - "ZarfComponentOnlyCluster": o([ + "OnlyCluster": o([ { json: "architecture", js: "architecture", typ: u(undefined, r("Architecture")) }, { json: "distros", js: "distros", typ: u(undefined, a("")) }, ], false), - "DeprecatedZarfComponentScripts": o([ + "Scripts": o([ { json: "after", js: "after", typ: u(undefined, a("")) }, { json: "before", js: "before", typ: u(undefined, a("")) }, { json: "prepare", js: "prepare", typ: u(undefined, a("")) }, @@ -1352,20 +1385,19 @@ const typeMap: any = { { json: "showOutput", js: "showOutput", typ: u(undefined, true) }, { json: "timeoutSeconds", js: "timeoutSeconds", typ: u(undefined, 0) }, ], false), - "ZarfPackageConstant": o([ + "ConstantElement": o([ { json: "autoIndent", js: "autoIndent", typ: u(undefined, true) }, { json: "description", js: "description", typ: u(undefined, "") }, { json: "name", js: "name", typ: "" }, { json: "pattern", js: "pattern", typ: u(undefined, "") }, { json: "value", js: "value", typ: "" }, ], false), - "ZarfMetadata": o([ + "Metadata": o([ { json: "aggregateChecksum", js: "aggregateChecksum", typ: u(undefined, "") }, { json: "architecture", js: "architecture", typ: u(undefined, "") }, { json: "authors", js: "authors", typ: u(undefined, "") }, { json: "description", js: "description", typ: u(undefined, "") }, { json: "documentation", js: "documentation", typ: u(undefined, "") }, - { json: "image", js: "image", typ: u(undefined, "") }, { json: "name", js: "name", typ: "" }, { json: "source", js: "source", typ: u(undefined, "") }, { json: "uncompressed", js: "uncompressed", typ: u(undefined, true) }, @@ -1384,39 +1416,38 @@ const typeMap: any = { { json: "sensitive", js: "sensitive", typ: u(undefined, true) }, { json: "type", js: "type", typ: u(undefined, r("Type")) }, ], false), - "DeployedComponent": o([ - { json: "installedCharts", js: "installedCharts", typ: a(r("InstalledChart")) }, + "DeployedComponentElement": o([ + { json: "installedCharts", js: "installedCharts", typ: a(r("InstalledChartElement")) }, { json: "name", js: "name", typ: "" }, { json: "observedGeneration", js: "observedGeneration", typ: 0 }, { json: "status", js: "status", typ: "" }, ], false), - "InstalledChart": o([ + "InstalledChartElement": o([ { json: "chartName", js: "chartName", typ: "" }, { json: "namespace", js: "namespace", typ: "" }, ], false), "ZarfState": o([ - { json: "agentTLS", js: "agentTLS", typ: r("GeneratedPKI") }, + { json: "agentTLS", js: "agentTLS", typ: r("AgentTLS") }, { json: "architecture", js: "architecture", typ: "" }, - { json: "artifactServer", js: "artifactServer", typ: r("ArtifactServerInfo") }, + { json: "artifactServer", js: "artifactServer", typ: r("ArtifactServer") }, { json: "distro", js: "distro", typ: "" }, - { json: "gitServer", js: "gitServer", typ: r("GitServerInfo") }, - { json: "loggingSecret", js: "loggingSecret", typ: "" }, + { json: "gitServer", js: "gitServer", typ: r("GitServer") }, { json: "registryInfo", js: "registryInfo", typ: r("RegistryInfo") }, { json: "storageClass", js: "storageClass", typ: "" }, { json: "zarfAppliance", js: "zarfAppliance", typ: true }, ], false), - "GeneratedPKI": o([ + "AgentTLS": o([ { json: "ca", js: "ca", typ: "" }, { json: "cert", js: "cert", typ: "" }, { json: "key", js: "key", typ: "" }, ], false), - "ArtifactServerInfo": o([ + "ArtifactServer": o([ { json: "address", js: "address", typ: "" }, { json: "internalServer", js: "internalServer", typ: true }, { json: "pushPassword", js: "pushPassword", typ: "" }, { json: "pushUsername", js: "pushUsername", typ: "" }, ], false), - "GitServerInfo": o([ + "GitServer": o([ { json: "address", js: "address", typ: "" }, { json: "internalServer", js: "internalServer", typ: true }, { json: "pullPassword", js: "pullPassword", typ: "" }, diff --git a/packages/README.md b/packages/README.md index 95686e0ab7..1776860ba4 100644 --- a/packages/README.md +++ b/packages/README.md @@ -38,14 +38,6 @@ Users who rely heavily on GitOps find it useful to deploy an internal Git reposi zarf init --components=git-server ``` -## Logging PGL - -The Logging PGL package deploys the Promtail, Grafana, and Loki stack which aggregates logs from different containers and presents them in a web dashboard. This is useful as a quick way to get logging into a cluster when you otherwise wouldn't be bringing over a logging stack. - -```bash -zarf init --components=logging -``` - ## Zarf Agent The Zarf Agent is a mutating admission controller used to modify the image property within a PodSpec. The purpose is to redirect it to Zarf's configured registry instead of the the original registry (such as DockerHub, GHCR, or Quay). Additionally, the webhook attaches the appropriate `ImagePullSecret` for the seed registry to the pod. This configuration allows the pod to successfully retrieve the image from the seed registry, even when operating in an air-gapped environment. diff --git a/packages/logging-pgl/connect.yaml b/packages/logging-pgl/connect.yaml deleted file mode 100644 index 41be9204d6..0000000000 --- a/packages/logging-pgl/connect.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: zarf-connect-logging - labels: - # Enables "zarf connect logging" - zarf.dev/connect-name: logging - annotations: - zarf.dev/connect-description: "Zarf Cluster Logging Service (Grafana)" -spec: - ports: - - port: 3000 diff --git a/packages/logging-pgl/pgl-values.yaml b/packages/logging-pgl/pgl-values.yaml deleted file mode 100644 index 45662d9fa2..0000000000 --- a/packages/logging-pgl/pgl-values.yaml +++ /dev/null @@ -1,34 +0,0 @@ -grafana: - enabled: true - adminUser: "zarf-admin" - adminPassword: "###ZARF_LOGGING_AUTH###" - # K8s 1.25+ compatibility until loki stack upgrades the grafana chart to 6.36.1 - rbac: - pspEnabled: false - grafana.ini: - server: - root_url: "%(protocol)s://%(domain)s/monitor" - serve_from_sub_path: true -promtail: - extraScrapeConfigs: - - job_name: journal - journal: - max_age: 12h - labels: - job: systemd-journal - relabel_configs: - - source_labels: ["__journal__systemd_unit"] - target_label: "unit" - - source_labels: ["__journal__hostname"] - target_label: "hostname" - - # Mount journal directory into promtail pods - extraVolumes: - - name: journal - hostPath: - path: /var/log/journal - - extraVolumeMounts: - - name: journal - mountPath: /var/log/journal - readOnly: true diff --git a/packages/logging-pgl/zarf.yaml b/packages/logging-pgl/zarf.yaml deleted file mode 100644 index d5fc213dc3..0000000000 --- a/packages/logging-pgl/zarf.yaml +++ /dev/null @@ -1,49 +0,0 @@ -kind: ZarfPackageConfig -metadata: - name: init-package-logging - -components: - - name: logging - description: | - Deploys the Promtail Grafana & Loki (PGL) stack. - Aggregates logs from different containers and presents them in a web dashboard. - Recommended if no other logging stack is deployed in the cluster. - images: - - docker.io/grafana/promtail:2.9.2 - - grafana/grafana:8.3.5 - - grafana/loki:2.6.1 - - quay.io/kiwigrid/k8s-sidecar:1.19.2 - manifests: - - name: logging-connect - namespace: zarf - files: - - connect.yaml - charts: - - name: loki-stack - releaseName: zarf-loki-stack - url: https://grafana.github.io/helm-charts - version: 2.10.1 - namespace: zarf - valuesFiles: - - pgl-values.yaml - actions: - onDeploy: - after: - - wait: - cluster: - kind: pod - namespace: zarf - name: app=loki - condition: Ready - - wait: - cluster: - kind: pod - namespace: zarf - name: app.kubernetes.io/name=grafana - condition: Ready - - wait: - cluster: - kind: pod - namespace: zarf - name: app.kubernetes.io/name=promtail - condition: Ready diff --git a/site/public/tutorials/logging_credentials.html b/site/public/tutorials/logging_credentials.html deleted file mode 100644 index c412ee8442..0000000000 --- a/site/public/tutorials/logging_credentials.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - -
- Zarf deployment complete
-
-
-     Application | Username   | Password                 | Connect              
-     Registry    | zarf-push  | Tka7dWq4GEit5G3GDX2dQwdh | zarf connect registry
-     Logging     | zarf-admin | ysC9TEWsSm37pBmA3hvqrLN3 | zarf connect logging
-
- - diff --git a/site/public/tutorials/logging_init.html b/site/public/tutorials/logging_init.html deleted file mode 100644 index 0786a5c310..0000000000 --- a/site/public/tutorials/logging_init.html +++ /dev/null @@ -1,343 +0,0 @@ - - - - - - -
-$ zarf init --components="logging" --confirm
-
-Saving log file to -/var/folders/bk/rz1xx2sd5zn134c0_j1s2n5r0000gp/T/zarf-2023-03-22-11-47-22-867656842.log - - • Loading Zarf Package /Users/jason/.zarf-cache/zarf-init-arm64-v0.24.3.tar.zst - • Loading Zarf Package /Users/jason/.zarf-cache/zarf-init-arm64-v0.24.3.tar.zst - -kind: ZarfInitConfig -metadata: - name: init - description: Used to establish a new Zarf cluster - architecture: arm64 -build: - terminal: fv-az488-281 - user: runner - architecture: arm64 - timestamp: Sun, 26 Feb 2023 01:28:31 +0000 - version: v0.24.3 - migrations: - - scripts-to-actions -components: -- name: zarf-injector - description: | - Bootstraps a Kubernetes cluster by cloning a running pod in the cluster and hosting the registry image. - Removed and destroyed after the Zarf Registry is self-hosting the registry image. - required: true - cosignKeyPath: cosign.pub - files: - - source: sget://defenseunicorns/zarf-injector:arm64-2023-02-09 - target: "###ZARF_TEMP###/zarf-injector" - executable: true -- name: zarf-seed-registry - description: | - Deploys the Zarf Registry using the registry image provided by the Zarf Injector. - required: true - charts: - - name: docker-registry - releaseName: zarf-docker-registry - version: 1.0.0 - namespace: zarf - valuesFiles: - - packages/zarf-registry/registry-values.yaml - - packages/zarf-registry/registry-values-seed.yaml - localPath: packages/zarf-registry/chart -- name: zarf-registry - description: | - Updates the Zarf Registry to use the self-hosted registry image. - Serves as the primary docker registry for the cluster. - required: true - charts: - - name: docker-registry - releaseName: zarf-docker-registry - version: 1.0.0 - namespace: zarf - valuesFiles: - - packages/zarf-registry/registry-values.yaml - localPath: packages/zarf-registry/chart - manifests: - - name: registry-connect - namespace: zarf - files: - - packages/zarf-registry/connect.yaml - - name: kep-1755-registry-annotation - namespace: zarf - files: - - packages/zarf-registry/configmap.yaml - images: - - registry:2.8.1 -- name: zarf-agent - description: | - A Kubernetes mutating webhook to enable automated URL rewriting for container - images and git repository references in Kubernetes manifests. This prevents - the need to manually update URLs from their original sources to the Zarf-managed - docker registry and git server. - required: true - actions: - onCreate: - before: - - cmd: make init-package-local-agent AGENT_IMAGE_TAG="v0.24.3" - manifests: - - name: zarf-agent - namespace: zarf - files: - - packages/zarf-agent/manifests/service.yaml - - packages/zarf-agent/manifests/secret.yaml - - packages/zarf-agent/manifests/deployment.yaml - - packages/zarf-agent/manifests/webhook.yaml - images: - - ghcr.io/defenseunicorns/zarf/agent:v0.24.3 -- name: logging - description: | - Deploys the Promtail Grafana & Loki (PGL) stack. - Aggregates logs from different containers and presents them in a web dashboard. - Recommended if no other logging stack is deployed in the cluster. - charts: - - name: loki-stack - releaseName: zarf-loki-stack - url: https://grafana.github.io/helm-charts - version: 2.8.9 - namespace: zarf - valuesFiles: - - packages/logging-pgl/pgl-values.yaml - manifests: - - name: logging-connect - namespace: zarf - files: - - packages/logging-pgl/connect.yaml - images: - - docker.io/grafana/promtail:2.7.0 - - grafana/grafana:8.3.5 - - grafana/loki:2.6.1 - - quay.io/kiwigrid/k8s-sidecar:1.19.2 -- name: git-server - description: | - Deploys Gitea to provide git repositories for Kubernetes configurations. - Required for GitOps deployments if no other git server is available. - actions: - onDeploy: - after: - - maxTotalSeconds: 60 - maxRetries: 3 - cmd: ./zarf internal create-read-only-gitea-user - charts: - - name: gitea - releaseName: zarf-gitea - url: https://dl.gitea.io/charts - version: 7.0.2 - namespace: zarf - valuesFiles: - - packages/gitea/gitea-values.yaml - manifests: - - name: git-connect - namespace: zarf - files: - - packages/gitea/connect.yaml - images: - - gitea/gitea:1.18.3 -variables: -- name: K3S_ARGS - description: Arguments to pass to K3s - default: --disable traefik -- name: REGISTRY_EXISTING_PVC - description: "Optional: Use an existing PVC for the registry instead of creating a new one. If this is set, the REGISTRY_PVC_SIZE variable will be ignored." -- name: REGISTRY_PVC_SIZE - description: The size of the persistent volume claim for the registry - default: 20Gi -- name: REGISTRY_CPU_REQ - description: The CPU request for the registry - default: 100m -- name: REGISTRY_MEM_REQ - description: The memory request for the registry - default: 256Mi -- name: REGISTRY_CPU_LIMIT - description: The CPU limit for the registry - default: "3" -- name: REGISTRY_MEM_LIMIT - description: The memory limit for the registry - default: 2Gi -- name: REGISTRY_HPA_MIN - description: The minimum number of registry replicas - default: "1" -- name: REGISTRY_HPA_MAX - description: The maximum number of registry replicas - default: "5" -- name: REGISTRY_HPA_ENABLE - description: Enable the Horizontal Pod Autoscaler for the registry - default: "true" -- name: GIT_SERVER_EXISTING_PVC - description: "Optional: Use an existing PVC for the git server instead of creating a new one. If this is set, the GIT_SERVER_PVC_SIZE variable will be ignored." -- name: GIT_SERVER_PVC_SIZE - description: The size of the persistent volume claim for git server - default: 10Gi -- name: GIT_SERVER_CPU_REQ - description: The CPU request for git server - default: 200m -- name: GIT_SERVER_MEM_REQ - description: The memory request for git server - default: 512Mi -- name: GIT_SERVER_CPU_LIMIT - description: The CPU limit for git server - default: "3" -- name: GIT_SERVER_MEM_LIMIT - description: The memory limit for git server - default: 2Gi -constants: -- name: AGENT_IMAGE - value: defenseunicorns/zarf/agent -- name: AGENT_IMAGE_TAG - value: v0.24.3 -- name: REGISTRY_IMAGE - value: registry -- name: REGISTRY_IMAGE_TAG - value: 2.8.1 -This package has 9 artifacts with software bill-of-materials (SBOM) included. You can view them now -in the zarf-sbom folder in this directory or to go directly to one, open this in your browser: -/Users/jason/src/github.com/jasonvanbrackel/zarf/docs/.examples/tutorials/zarf-sbom/sbom-viewer-docker.io_grafana_promtail_2.7.0.html - -* This directory will be removed after package deployment. - - Deploy Zarf package confirmed - -
- - 📦 ZARF-INJECTOR COMPONENT - - -
- - • Copying 1 files - • Copying 1 files - • Gathering cluster information - • Gathering cluster information - • Attempting to bootstrap the seed image into the cluster - • Attempting to bootstrap the seed image into the cluster -
- - 📦 ZARF-SEED-REGISTRY COMPONENT - - -
- • Loading the Zarf State from the Kubernetes cluster - • Loading the Zarf State from the Kubernetes cluster - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - -
- - 📦 ZARF-REGISTRY COMPONENT - - -
- - • Opening tunnel 52538 -> 5000 for svc/zarf-docker-registry in namespace zarf - • Creating port forwarding tunnel at http://127.0.0.1:52538/v2/_catalog - • Storing images in the zarf registry - • Storing images in the zarf registry - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Starting helm chart generation registry-connect - • Starting helm chart generation registry-connect - • Processing helm chart raw-init-zarf-registry-registry-connect:0.1.1679500042 from Zarf-generated - helm chart - • Processing helm chart raw-init-zarf-registry-registry-connect:0.1.1679500042 from Zarf-generated - helm chart - • Starting helm chart generation kep-1755-registry-annotation - • Starting helm chart generation kep-1755-registry-annotation - • Processing helm chart raw-init-zarf-registry-kep-1755-registry-annotation:0.1.1679500042 from - Zarf-generated helm chart - • Processing helm chart raw-init-zarf-registry-kep-1755-registry-annotation:0.1.1679500042 from - Zarf-generated helm chart - -
- - 📦 ZARF-AGENT COMPONENT - - -
- - • Opening tunnel 52550 -> 5000 for svc/zarf-docker-registry in namespace zarf - • Creating port forwarding tunnel at http://127.0.0.1:52550/v2/_catalog - • Storing images in the zarf registry - • Storing images in the zarf registry - • Starting helm chart generation zarf-agent - • Starting helm chart generation zarf-agent - • Processing helm chart raw-init-zarf-agent-zarf-agent:0.1.1679500042 from Zarf-generated helm chart - • Processing helm chart raw-init-zarf-agent-zarf-agent:0.1.1679500042 from Zarf-generated helm chart - -
- - 📦 LOGGING COMPONENT - - -
- - • Opening tunnel 52556 -> 5000 for svc/zarf-docker-registry in namespace zarf - • Creating port forwarding tunnel at http://127.0.0.1:52556/v2/_catalog - • Storing images in the zarf registry - • Storing images in the zarf registry - • Processing helm chart loki-stack:2.8.9 from https://grafana.github.io/helm-charts - • Processing helm chart loki-stack:2.8.9 from https://grafana.github.io/helm-charts - • Starting helm chart generation logging-connect - • Starting helm chart generation logging-connect - • Processing helm chart raw-init-logging-logging-connect:0.1.1679500042 from Zarf-generated helm chart - • Processing helm chart raw-init-logging-logging-connect:0.1.1679500042 from Zarf-generated helm chart - Zarf deployment complete - - - Application | Username | Password | Connect - Registry | zarf-push | Tka7dWq4GEit5G3GDX2dQwdh | zarf connect registry - Logging | zarf-admin | ysC9TEWsSm37pBmA3hvqrLN3 | zarf connect logging -
- - diff --git a/site/public/tutorials/logging_init_manual.html b/site/public/tutorials/logging_init_manual.html deleted file mode 100644 index e4a8fa5197..0000000000 --- a/site/public/tutorials/logging_init_manual.html +++ /dev/null @@ -1,276 +0,0 @@ - - - - - - -
-Saving log file to
-/var/folders/bk/rz1xx2sd5zn134c0_j1s2n5r0000gp/T/zarf-2023-03-23-09-17-19-1802773183.log
- Loading Zarf Package /Users/jason/.zarf-cache/zarf-init-arm64-v0.24.3.tar.zst
-
-kind: ZarfInitConfig
-metadata:
-  name: init
-  description: Used to establish a new Zarf cluster
-  architecture: arm64
-build:
-  terminal: fv-az488-281
-  user: runner
-  architecture: arm64
-  timestamp: Sun, 26 Feb 2023 01:28:31 +0000
-  version: v0.24.3
-  migrations:
-  - scripts-to-actions
-components:
-- name: zarf-injector
-  description: |
-    Bootstraps a Kubernetes cluster by cloning a running pod in the cluster and hosting the registry image.
-    Removed and destroyed after the Zarf Registry is self-hosting the registry image.
-  required: true
-  cosignKeyPath: cosign.pub
-  files:
-  - source: sget://defenseunicorns/zarf-injector:arm64-2023-02-09
-    target: "###ZARF_TEMP###/zarf-injector"
-    executable: true
-- name: zarf-seed-registry
-  description: |
-    Deploys the Zarf Registry using the registry image provided by the Zarf Injector.
-  required: true
-  charts:
-  - name: docker-registry
-    releaseName: zarf-docker-registry
-    version: 1.0.0
-    namespace: zarf
-    valuesFiles:
-    - packages/zarf-registry/registry-values.yaml
-    - packages/zarf-registry/registry-values-seed.yaml
-    localPath: packages/zarf-registry/chart
-- name: zarf-registry
-  description: |
-    Updates the Zarf Registry to use the self-hosted registry image. 
-    Serves as the primary docker registry for the cluster.
-  required: true
-  charts:
-  - name: docker-registry
-    releaseName: zarf-docker-registry
-    version: 1.0.0
-    namespace: zarf
-    valuesFiles:
-    - packages/zarf-registry/registry-values.yaml
-    localPath: packages/zarf-registry/chart
-  manifests:
-  - name: registry-connect
-    namespace: zarf
-    files:
-    - packages/zarf-registry/connect.yaml
-  - name: kep-1755-registry-annotation
-    namespace: zarf
-    files:
-    - packages/zarf-registry/configmap.yaml
-  images:
-  - registry:2.8.1
-- name: zarf-agent
-  description: |
-    A Kubernetes mutating webhook to enable automated URL rewriting for container
-    images and git repository references in Kubernetes manifests. This prevents
-    the need to manually update URLs from their original sources to the Zarf-managed
-    docker registry and git server.
-  required: true
-  actions:
-    onCreate:
-      before:
-      - cmd: make init-package-local-agent AGENT_IMAGE_TAG="v0.24.3"
-  manifests:
-  - name: zarf-agent
-    namespace: zarf
-    files:
-    - packages/zarf-agent/manifests/service.yaml
-    - packages/zarf-agent/manifests/secret.yaml
-    - packages/zarf-agent/manifests/deployment.yaml
-    - packages/zarf-agent/manifests/webhook.yaml
-  images:
-  - ghcr.io/defenseunicorns/zarf/agent:v0.24.3
-- name: logging
-  description: |
-    Deploys the Promtail Grafana & Loki (PGL) stack. 
-    Aggregates logs from different containers and presents them in a web dashboard. 
-    Recommended if no other logging stack is deployed in the cluster.
-  charts:
-  - name: loki-stack
-    releaseName: zarf-loki-stack
-    url: https://grafana.github.io/helm-charts
-    version: 2.8.9
-    namespace: zarf
-    valuesFiles:
-    - packages/logging-pgl/pgl-values.yaml
-  manifests:
-  - name: logging-connect
-    namespace: zarf
-    files:
-    - packages/logging-pgl/connect.yaml
-  images:
-  - docker.io/grafana/promtail:2.7.0
-  - grafana/grafana:8.3.5
-  - grafana/loki:2.6.1
-  - quay.io/kiwigrid/k8s-sidecar:1.19.2
-- name: git-server
-  description: |
-    Deploys Gitea to provide git repositories for Kubernetes configurations.
-    Required for GitOps deployments if no other git server is available.
-  actions:
-    onDeploy:
-      after:
-      - maxTotalSeconds: 60
-        maxRetries: 3
-        cmd: ./zarf internal create-read-only-gitea-user
-  charts:
-  - name: gitea
-    releaseName: zarf-gitea
-    url: https://dl.gitea.io/charts
-    version: 7.0.2
-    namespace: zarf
-    valuesFiles:
-    - packages/gitea/gitea-values.yaml
-  manifests:
-  - name: git-connect
-    namespace: zarf
-    files:
-    - packages/gitea/connect.yaml
-  images:
-  - gitea/gitea:1.18.3
-variables:
-- name: K3S_ARGS
-  description: Arguments to pass to K3s
-  default: --disable traefik
-- name: REGISTRY_EXISTING_PVC
-  description: "Optional: Use an existing PVC for the registry instead of creating a new one. If this is set, the REGISTRY_PVC_SIZE variable will be ignored."
-- name: REGISTRY_PVC_SIZE
-  description: The size of the persistent volume claim for the registry
-  default: 20Gi
-- name: REGISTRY_CPU_REQ
-  description: The CPU request for the registry
-  default: 100m
-- name: REGISTRY_MEM_REQ
-  description: The memory request for the registry
-  default: 256Mi
-- name: REGISTRY_CPU_LIMIT
-  description: The CPU limit for the registry
-  default: "3"
-- name: REGISTRY_MEM_LIMIT
-  description: The memory limit for the registry
-  default: 2Gi
-- name: REGISTRY_HPA_MIN
-  description: The minimum number of registry replicas
-  default: "1"
-- name: REGISTRY_HPA_MAX
-  description: The maximum number of registry replicas
-  default: "5"
-- name: REGISTRY_HPA_ENABLE
-  description: Enable the Horizontal Pod Autoscaler for the registry
-  default: "true"
-- name: GIT_SERVER_EXISTING_PVC
-  description: "Optional: Use an existing PVC for the git server instead of creating a new one. If this is set, the GIT_SERVER_PVC_SIZE variable will be ignored."
-- name: GIT_SERVER_PVC_SIZE
-  description: The size of the persistent volume claim for git server
-  default: 10Gi
-- name: GIT_SERVER_CPU_REQ
-  description: The CPU request for git server
-  default: 200m
-- name: GIT_SERVER_MEM_REQ
-  description: The memory request for git server
-  default: 512Mi
-- name: GIT_SERVER_CPU_LIMIT
-  description: The CPU limit for git server
-  default: "3"
-- name: GIT_SERVER_MEM_LIMIT
-  description: The memory limit for git server
-  default: 2Gi
-constants:
-- name: AGENT_IMAGE
-  value: defenseunicorns/zarf/agent
-- name: AGENT_IMAGE_TAG
-  value: v0.24.3
-- name: REGISTRY_IMAGE
-  value: registry
-- name: REGISTRY_IMAGE_TAG
-  value: 2.8.1
-This package has 9 artifacts with software bill-of-materials (SBOM) included. You can view them now
-in the zarf-sbom folder in this directory or to go directly to one, open this in your browser:
-/Users/jason/src/github.com/jasonvanbrackel/zarf/docs-website/zarf-sbom/sbom-viewer-docker.io_grafana_promtail_2.7.0.html
-
-* This directory will be removed after package deployment.
-
-? Deploy this Zarf package? (y/N) Yes
-
-───────────────────────────────────────────────────────────────────────────────────────
-name: logging
-charts:
-- name: loki-stack
-  releaseName: zarf-loki-stack
-  url: https://grafana.github.io/helm-charts
-  version: 2.8.9
-  namespace: zarf
-  valuesFiles:
-  - packages/logging-pgl/pgl-values.yaml
-manifests:
-- name: logging-connect
-  namespace: zarf
-  files:
-  - packages/logging-pgl/connect.yaml
-images:
-- docker.io/grafana/promtail:2.7.0
-- grafana/grafana:8.3.5
-- grafana/loki:2.6.1
-- quay.io/kiwigrid/k8s-sidecar:1.19.2
-
-Deploys the Promtail Grafana & Loki (PGL) stack. Aggregates logs from different containers and
-presents them in a web dashboard. Recommended if no other logging stack is deployed in the cluster.
-? Deploy the logging component? (y/N) Yes
-───────────────────────────────────────────────────────────────────────────────────────
-
-
- - diff --git a/site/public/tutorials/logging_list.html b/site/public/tutorials/logging_list.html deleted file mode 100644 index 2319fc86c0..0000000000 --- a/site/public/tutorials/logging_list.html +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - -
-$ zarf package list
-Saving log file to -/var/folders/bk/rz1xx2sd5zn134c0_j1s2n5r0000gp/T/zarf-2023-03-22-13-13-47-3918845089.log - Package | Components - dos-games | [baseline] - init | [zarf-injector zarf-seed-registry zarf-registry zarf-agent logging] -
- - diff --git a/site/public/tutorials/logging_remove.html b/site/public/tutorials/logging_remove.html deleted file mode 100644 index bc6d258564..0000000000 --- a/site/public/tutorials/logging_remove.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - -
-$ zarf package remove init --components="logging" --confirm
-Saving log file to -/var/folders/bk/rz1xx2sd5zn134c0_j1s2n5r0000gp/T/zarf-2023-03-22-13-17-09-3967112348.log -Removing zarf package init -
- - diff --git a/site/public/tutorials/package_create_init.html b/site/public/tutorials/package_create_init.html index 9ed5ef2265..6a21effc08 100644 --- a/site/public/tutorials/package_create_init.html +++ b/site/public/tutorials/package_create_init.html @@ -159,29 +159,6 @@ - packages/zarf-agent/manifests/webhook.yaml images: - ghcr.io/defenseunicorns/zarf/agent:local -- name: logging - description: | - Deploys the Promtail Grafana & Loki (PGL) stack. - Aggregates logs from different containers and presents them in a web dashboard. - Recommended if no other logging stack is deployed in the cluster. - charts: - - name: loki-stack - releaseName: zarf-loki-stack - url: https://grafana.github.io/helm-charts - version: 2.9.9 - namespace: zarf - valuesFiles: - - packages/logging-pgl/pgl-values.yaml - manifests: - - name: logging-connect - namespace: zarf - files: - - packages/logging-pgl/connect.yaml - images: - - docker.io/grafana/promtail:2.7.2 - - grafana/grafana:8.3.5 - - grafana/loki:2.6.1 - - quay.io/kiwigrid/k8s-sidecar:1.19.2 - name: git-server description: | Deploys Gitea to provide git repositories for Kubernetes configurations. diff --git a/site/public/tutorials/zarf_init.html b/site/public/tutorials/zarf_init.html deleted file mode 100644 index b3acb5c5fd..0000000000 --- a/site/public/tutorials/zarf_init.html +++ /dev/null @@ -1,429 +0,0 @@ - - - - - - -
-$ zarf init
-Using config file /Users/josimoore/Desktop/projects/zarf/zarf-config.toml - -Saving log file to -/var/folders/gb/qfjdrlp52_v87bj_2rfzqls00000gn/T/zarf-2023-03-28-08-39-23-314846914.log - - • Loading Zarf Package /Users/josimoore/.zarf-cache/zarf-init-arm64-v0.25.0.tar.zst - • Loading Zarf Package /Users/josimoore/.zarf-cache/zarf-init-arm64-v0.25.0.tar.zst - -kind: ZarfInitConfig -metadata: - name: init - description: Used to establish a new Zarf cluster - architecture: arm64 -build: - terminal: fv-az442-536 - user: runner - architecture: arm64 - timestamp: Tue, 21 Mar 2023 23:50:40 +0000 - version: v0.25.0 - migrations: - - scripts-to-actions -components: -- name: zarf-injector - description: | - Bootstraps a Kubernetes cluster by cloning a running pod in the cluster and hosting the registry image. - Removed and destroyed after the Zarf Registry is self-hosting the registry image. - required: true - cosignKeyPath: cosign.pub - files: - - source: sget://defenseunicorns/zarf-injector:arm64-2023-02-09 - target: "###ZARF_TEMP###/zarf-injector" - executable: true -- name: zarf-seed-registry - description: | - Deploys the Zarf Registry using the registry image provided by the Zarf Injector. - required: true - charts: - - name: docker-registry - releaseName: zarf-docker-registry - version: 1.0.0 - namespace: zarf - valuesFiles: - - packages/zarf-registry/registry-values.yaml - - packages/zarf-registry/registry-values-seed.yaml - localPath: packages/zarf-registry/chart -- name: zarf-registry - description: | - Updates the Zarf Registry to use the self-hosted registry image. - Serves as the primary docker registry for the cluster. - required: true - charts: - - name: docker-registry - releaseName: zarf-docker-registry - version: 1.0.0 - namespace: zarf - valuesFiles: - - packages/zarf-registry/registry-values.yaml - localPath: packages/zarf-registry/chart - manifests: - - name: registry-connect - namespace: zarf - files: - - packages/zarf-registry/connect.yaml - - name: kep-1755-registry-annotation - namespace: zarf - files: - - packages/zarf-registry/configmap.yaml - images: - - registry:2.8.1 -- name: zarf-agent - description: | - A Kubernetes mutating webhook to enable automated URL rewriting for container - images and git repository references in Kubernetes manifests. This prevents - the need to manually update URLs from their original sources to the Zarf-managed - docker registry and git server. - required: true - actions: - onCreate: - before: - - cmd: make init-package-local-agent AGENT_IMAGE_TAG="v0.25.0" - manifests: - - name: zarf-agent - namespace: zarf - files: - - packages/zarf-agent/manifests/service.yaml - - packages/zarf-agent/manifests/secret.yaml - - packages/zarf-agent/manifests/deployment.yaml - - packages/zarf-agent/manifests/webhook.yaml - images: - - ghcr.io/defenseunicorns/zarf/agent:v0.25.0 -- name: logging - description: | - Deploys the Promtail Grafana & Loki (PGL) stack. - Aggregates logs from different containers and presents them in a web dashboard. - Recommended if no other logging stack is deployed in the cluster. - charts: - - name: loki-stack - releaseName: zarf-loki-stack - url: https://grafana.github.io/helm-charts - version: 2.9.9 - namespace: zarf - valuesFiles: - - packages/logging-pgl/pgl-values.yaml - manifests: - - name: logging-connect - namespace: zarf - files: - - packages/logging-pgl/connect.yaml - images: - - docker.io/grafana/promtail:2.7.2 - - grafana/grafana:8.3.5 - - grafana/loki:2.6.1 - - quay.io/kiwigrid/k8s-sidecar:1.19.2 -- name: git-server - description: | - Deploys Gitea to provide git repositories for Kubernetes configurations. - Required for GitOps deployments if no other git server is available. - actions: - onDeploy: - after: - - maxTotalSeconds: 60 - maxRetries: 3 - cmd: ./zarf internal create-read-only-gitea-user - charts: - - name: gitea - releaseName: zarf-gitea - url: https://dl.gitea.io/charts - version: 7.0.4 - namespace: zarf - valuesFiles: - - packages/gitea/gitea-values.yaml - manifests: - - name: git-connect - namespace: zarf - files: - - packages/gitea/connect.yaml - images: - - gitea/gitea:1.19.3-rootless -variables: -- name: K3S_ARGS - description: Arguments to pass to K3s - default: --disable traefik -- name: REGISTRY_EXISTING_PVC - description: "Optional: Use an existing PVC for the registry instead of creating a new one. If this is set, the REGISTRY_PVC_SIZE variable will be ignored." -- name: REGISTRY_PVC_SIZE - description: The size of the persistent volume claim for the registry - default: 20Gi -- name: REGISTRY_CPU_REQ - description: The CPU request for the registry - default: 100m -- name: REGISTRY_MEM_REQ - description: The memory request for the registry - default: 256Mi -- name: REGISTRY_CPU_LIMIT - description: The CPU limit for the registry - default: "3" -- name: REGISTRY_MEM_LIMIT - description: The memory limit for the registry - default: 2Gi -- name: REGISTRY_HPA_MIN - description: The minimum number of registry replicas - default: "1" -- name: REGISTRY_HPA_MAX - description: The maximum number of registry replicas - default: "5" -- name: REGISTRY_HPA_ENABLE - description: Enable the Horizontal Pod Autoscaler for the registry - default: "true" -- name: GIT_SERVER_EXISTING_PVC - description: "Optional: Use an existing PVC for the git server instead of creating a new one. If this is set, the GIT_SERVER_PVC_SIZE variable will be ignored." -- name: GIT_SERVER_PVC_SIZE - description: The size of the persistent volume claim for git server - default: 10Gi -- name: GIT_SERVER_CPU_REQ - description: The CPU request for git server - default: 200m -- name: GIT_SERVER_MEM_REQ - description: The memory request for git server - default: 512Mi -- name: GIT_SERVER_CPU_LIMIT - description: The CPU limit for git server - default: "3" -- name: GIT_SERVER_MEM_LIMIT - description: The memory limit for git server - default: 2Gi -constants: -- name: AGENT_IMAGE - value: defenseunicorns/zarf/agent -- name: AGENT_IMAGE_TAG - value: v0.25.0 -- name: REGISTRY_IMAGE - value: registry -- name: REGISTRY_IMAGE_TAG - value: 2.8.1 -This package has 9 artifacts with software bill-of-materials (SBOM) included. You can view them now -in the zarf-sbom folder in this directory or to go directly to one, open this in your browser: -/Users/josimoore/Desktop/projects/zarf/zarf-sbom/sbom-viewer-docker.io_grafana_promtail_2.7.2.html - -* This directory will be removed after package deployment. - -? Deploy this Zarf package? (y/N) Yes - -─────────────────────────────────────────────────────────────────────────────────────── -name: k3s -only: - localOS: linux - cluster: - architecture: amd64 -actions: - onDeploy: - defaults: - maxRetries: 5 - before: - - maxRetries: 0 - cmd: ./zarf internal is-valid-hostname - - cmd: "[ -e /etc/redhat-release ] && systemctl disable firewalld --now || echo ''" - after: - - cmd: systemctl daemon-reload - - cmd: systemctl enable k3s - - cmd: systemctl start k3s -files: -- source: packages/distros/k3s/common/zarf-clean-k3s.sh - target: /opt/zarf/zarf-clean-k3s.sh - executable: true -- source: packages/distros/k3s/common/k3s.service - target: /etc/systemd/system/k3s.service - symlinks: - - /etc/systemd/system/multi-user.target.wants/k3s.service -- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s - shasum: ca398d82fee8f9f52b05fb184582054be3c0285a1b9e8fb5c7b9a91448a - target: /usr/sbin/k3s - executable: true - symlinks: - - /usr/sbin/kubctl - - /usr/sbin/ctr - - /usr/sbin/crictl -- source: https://github.com/k3s-io/k3s/releases/download/v1.24.1+k3s1/k3s-airgap-images-amd64.tar.zst - shasum: 6736bf9fa4d5754d60b0508bafb2f888a70cb99a203a3a1617a919ca4ee74034 - target: /var/lib/rancher/k3s/agent/images/k3s.tar.zst - -*** REQUIRES ROOT *** Install K3s, certified Kubernetes distribution built for IoT & Edge computing -K3s provides the cluster need for Zarf running in Appliance Mode as well as can host a low-resource -Gitops Service if not using an existing Kubernetes platform. - -? Deploy the k3s component? (y/N) No - -─────────────────────────────────────────────────────────────────────────────────────── - -name: logging -charts: -- name: loki-stack - releaseName: zarf-loki-stack - url: https://grafana.github.io/helm-charts - version: 2.9.9 - namespace: zarf - valuesFiles: - - packages/logging-pgl/pgl-values.yaml -manifests: -- name: logging-connect - namespace: zarf - files: - - packages/logging-pgl/connect.yaml -images: -- docker.io/grafana/promtail:2.7.2 -- grafana/grafana:8.3.5 -- grafana/loki:2.6.1 -- quay.io/kiwigrid/k8s-sidecar:1.19.2 - -Deploys the Promtail Grafana & Loki (PGL) stack. Aggregates logs from different containers and -presents them in a web dashboard. Recommended if no other logging stack is deployed in the cluster. - -? Deploy the logging component? (y/N) No - -─────────────────────────────────────────────────────────────────────────────────────── - -name: git-server -actions: - onDeploy: - after: - - maxTotalSeconds: 60 - maxRetries: 3 - cmd: ./zarf internal create-read-only-gitea-user -charts: -- name: gitea - releaseName: zarf-gitea - url: https://d1.gitea.io/charts - version: 7.0.4 - namespace: zarf - valuesFiles: - - packages/gitea/gitea-values.yaml -manifests: -- name: git-connect - namespace: zarf - files: - - packages/gitea/connect.yaml -images: -- gitea/gitea:1.19.3-rootless - -Deploys Gitea to provide git repositories for Kubernetes configurations. Required for GitOps -deployments if no other git server is available. -? Deploy the git-server component? (y/N) No - -
- - 📦 ZARF-INJECTOR COMPONENT - - -
- - • Copying 1 files - • Copying 1 files - • Gathering cluster information - • Gathering cluster information - • Attempting to bootstrap the seed image into the cluster - • Attempting to bootstrap the seed image into the cluster - -
- - 📦 ZARF-SEED-REGISTRY COMPONENT - - -
- - • Loading the Zarf State from the Kubernetes cluster - • Loading the Zarf State from the Kubernetes cluster - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - -
- - 📦 ZARF-REGISTRY COMPONENT - - -
- - • Opening tunnel 62270 -> 5000 for svc/zarf-docker-registry in namespace zarf - • Creating port forwarding tunnel at http://127.0.0.1:62270/v2/_catalog - • Storing images in the zarf registry - • Storing images in the zarf registry - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Processing helm chart docker-registry:1.0.0 from Zarf-generated helm chart - • Starting helm chart generation registry-connect - • Starting helm chart generation registry-connect - • Processing helm chart raw-init-zarf-registry-registry-connect:0.1.1680014363 from Zarf-generated - helm chart - • Processing helm chart raw-init-zarf-registry-registry-connect:0.1.1680014363 from Zarf-generated - helm chart - • Starting helm chart generation kep-1755-registry-annotation - • Starting helm chart generation kep-1755-registry-annotation - • Processing helm chart raw-init-zarf-registry-kep-1755-registry-annotation:0.1.1680014363 from - Zarf-generated helm chart - • Processing helm chart raw-init-zarf-registry-kep-1755-registry-annotation:0.1.1680014363 from - Zarf-generated helm chart - -
- - 📦 ZARF-AGENT COMPONENT - - -
- - • Opening tunnel 62284 -> 5000 for svc/zarf-docker-registry in namespace zarf - • Creating port forwarding tunnel at http://127.0.0.1:62284/v2/_catalog - • Storing images in the zarf registry - • Storing images in the zarf registry - • Starting helm chart generation zarf-agent - • Starting helm chart generation zarf-agent - • Processing helm chart raw-init-zarf-agent-zarf-agent:0.1.1680014363 from Zarf-generated helm chart - • Processing helm chart raw-init-zarf-agent-zarf-agent:0.1.1680014363 from Zarf-generated helm chart - Zarf deployment complete - - - Application | Username | Password | Connect - Registry | zarf-push | DdarrzTahz6oclGTUAUOfbsY | zarf connect registry - - -
- - diff --git a/site/src/content/docs/commands/zarf_connect.md b/site/src/content/docs/commands/zarf_connect.md index 9cc76d83a0..88f519d332 100644 --- a/site/src/content/docs/commands/zarf_connect.md +++ b/site/src/content/docs/commands/zarf_connect.md @@ -13,7 +13,7 @@ Accesses services or pods deployed in the cluster ### Synopsis Uses a k8s port-forward to connect to resources within the cluster referenced by your kube-context. -Three default options for this command are . These will connect to the Zarf created resources (assuming they were selected when performing the `zarf init` command). +Two default options for this command are . These will connect to the Zarf created resources (assuming they were selected when performing the `zarf init` command). Packages can provide service manifests that define their own shortcut connection options. These options will be printed to the terminal when the package finishes deploying. If you don't remember what connection shortcuts your deployed package offers, you can search your cluster for services that have the 'zarf.dev/connect-name' label. The value of that label is the name you will pass into the 'zarf connect' command. @@ -21,7 +21,7 @@ Packages can provide service manifests that define their own shortcut connection Even if the packages you deploy don't define their own shortcut connection options, you can use the command flags to connect into specific resources. You can read the command flag descriptions below to get a better idea how to connect to whatever resource you are trying to connect to. ``` -zarf connect { REGISTRY | LOGGING | GIT | connect-name } [flags] +zarf connect { REGISTRY | GIT | connect-name } [flags] ``` ### Options diff --git a/site/src/content/docs/commands/zarf_init.md b/site/src/content/docs/commands/zarf_init.md index 650925435c..8fc88244c3 100644 --- a/site/src/content/docs/commands/zarf_init.md +++ b/site/src/content/docs/commands/zarf_init.md @@ -12,8 +12,8 @@ Prepares a k8s cluster for the deployment of Zarf packages ### Synopsis -Injects a docker registry as well as other optional useful things (such as a git server and a logging stack) into a k8s cluster under the 'zarf' namespace to support future application deployments. -If you do not have a k8s cluster already configured, this command will give you the ability to install a cluster locally. +Injects an OCI registry as well as an optional git server into a Kubernetes cluster in the zarf namespace to support future application deployments. +If you do not have a cluster already configured, this command will give you the ability to install a cluster locally. This command looks for a zarf-init package in the local directory that the command was executed from. If no package is found in the local directory and the Zarf CLI exists somewhere outside of the current directory, Zarf will failover and attempt to find a zarf-init package in the directory that the Zarf binary is located in. @@ -35,9 +35,6 @@ $ zarf init # Initializing w/ Zarfs internal git server: $ zarf init --components=git-server -# Initializing w/ Zarfs internal git server and PLG stack: -$ zarf init --components=git-server,logging - # Initializing w/ an internal registry but with a different nodeport: $ zarf init --nodeport=30333 @@ -61,7 +58,7 @@ $ zarf init --artifact-push-password={PASSWORD} --artifact-push-username={USERNA --artifact-push-token string [alpha] API Token for the push-user to access the artifact registry --artifact-push-username string [alpha] Username to access to the artifact registry Zarf is configured to use. User must be able to upload package artifacts. --artifact-url string [alpha] External artifact registry url to use for this Zarf cluster - --components string Specify which optional components to install. E.g. --components=git-server,logging + --components string Specify which optional components to install. E.g. --components=git-server --confirm Confirms package deployment without prompting. ONLY use with packages you trust. Skips prompts to review SBOM, configure variables, select optional components and review potential breaking changes. --git-pull-password string Password for the pull-only user to access the git server --git-pull-username string Username for pull-only access to the git server diff --git a/site/src/content/docs/commands/zarf_tools_get-creds.md b/site/src/content/docs/commands/zarf_tools_get-creds.md index 188313daff..4d56b4e2b8 100644 --- a/site/src/content/docs/commands/zarf_tools_get-creds.md +++ b/site/src/content/docs/commands/zarf_tools_get-creds.md @@ -31,7 +31,6 @@ $ zarf tools get-creds registry-readonly $ zarf tools get-creds git $ zarf tools get-creds git-readonly $ zarf tools get-creds artifact -$ zarf tools get-creds logging ``` diff --git a/site/src/content/docs/contribute/testing.mdx b/site/src/content/docs/contribute/testing.mdx index 1fa09ec997..c64c643aa6 100644 --- a/site/src/content/docs/contribute/testing.mdx +++ b/site/src/content/docs/contribute/testing.mdx @@ -125,9 +125,8 @@ Due to resource constraints in public GitHub runners, K8s tests are only perform ::: - 20 is reserved for `zarf init`. -- 21 is reserved for logging tests so they can be removed first (they take the most resources in the cluster). - 22 is reserved for tests required the git-server, which is removed at the end of the test. -- 23-98 are for the remaining tests that only require a basic Zarf cluster without logging or the git-server. +- 23-98 are for the remaining tests that only require a basic Zarf cluster without the git-server. - 99 is reserved for the `zarf destroy` and [YOLO Mode](/ref/examples/yolo/) test. ## CLI Unit Tests diff --git a/site/src/content/docs/getting-started/install.mdx b/site/src/content/docs/getting-started/install.mdx index cd713ee04c..7c115d8cfb 100644 --- a/site/src/content/docs/getting-started/install.mdx +++ b/site/src/content/docs/getting-started/install.mdx @@ -138,6 +138,6 @@ The default 'init' package can also be obtained by visiting the [Zarf releases]( :::tip -You can build your own custom 'init' package too if you'd like. For this you should check out the [Creating a Custom 'init' Package Tutorial](/tutorials/8-custom-init-packages). +You can build your own custom 'init' package too if you'd like. For this you should check out the [Creating a Custom 'init' Package Tutorial](/tutorials/7-custom-init-packages). ::: diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx index ebceb10ace..48ac086e1c 100644 --- a/site/src/content/docs/index.mdx +++ b/site/src/content/docs/index.mdx @@ -36,13 +36,12 @@ Zarf provides a way to package and deploy software in a way that is **repeatable - Automate Kubernetes deployments in disconnected environments - Automate [Software Bill of Materials (SBOM)](/ref/sboms/) generation -- Build and [publish packages as OCI image artifacts](/tutorials/7-publish-and-deploy/) +- Build and [publish packages as OCI image artifacts](/tutorials/6-publish-and-deploy/) - Provide a [web dashboard](/ref/sboms/#the-sbom-viewer) for viewing SBOM output - Create and verify package signatures with [cosign](https://github.com/sigstore/cosign) - [Publish](/commands/zarf_package_publish/), [pull](/commands/zarf_package_pull/), and [deploy](/commands/zarf_package_deploy/) packages from an [OCI registry](https://opencontainers.org/) - Powerful component lifecycle [actions](/ref/actions/) - Deploy a new cluster while fully disconnected with [K3s](https://k3s.io/) or into any existing cluster using a [kube config](https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/) -- Builtin logging stack with [Loki](https://grafana.com/oss/loki/) - Builtin Git server with [Gitea](https://gitea.com/) - Builtin Docker registry - Builtin [K9s Dashboard](https://k9scli.io/) for managing a cluster from the terminal diff --git a/site/src/content/docs/ref/deploy.mdx b/site/src/content/docs/ref/deploy.mdx index 3b92ee1070..28e61f7114 100644 --- a/site/src/content/docs/ref/deploy.mdx +++ b/site/src/content/docs/ref/deploy.mdx @@ -18,7 +18,7 @@ A typical Zarf deployment is made up of three parts: 2. A [Zarf init package](/ref/init-package/): - A compressed tarball package that contains the configuration needed to instantiate an environment without connectivity. - Automatically seeds your cluster with a container registry or wires up a pre-existing one - - Provides additional capabilities such as logging, git server support, and/or a K8s cluster. + - Provides additional capabilities such as a git server and K3s cluster. 3. A [Zarf Package](/ref/packages/): - A compressed tarball package that contains all of the files, manifests, source repositories, and images needed to deploy your infrastructure, application, and resources in a disconnected environment. @@ -92,7 +92,7 @@ Zarf normally expects to operate against a Kubernetes cluster that has been [Zar - **YOLO Mode** - Yaml-OnLy Online mode allows for a faster deployment without requiring the `zarf init` command to be run beforehand. It can be useful for testing or for environments that manage their own registries and Git servers completely outside of Zarf. Given this mode does not use the [Zarf Agent](/faq#what-is-the-zarf-agent) any resources specified will need to be manually modified for the environment. -- **Cluster-less** - Zarf normally interacts with clusters and kubernetes resources, but it is possible to have Zarf perform actions before a cluster exists (including [deploying the cluster itself](/tutorials/5-creating-a-k8s-cluster-with-zarf)). These packages generally have more dependencies on the host or environment that they run within. +- **Cluster-less** - Zarf normally interacts with clusters and kubernetes resources, but it is possible to have Zarf perform actions before a cluster exists (including [deploying the cluster itself](/tutorials/4-creating-a-k8s-cluster-with-zarf)). These packages generally have more dependencies on the host or environment that they run within. ## Typical Deployment Workflow diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx index f1669ae471..8bf4c8506b 100644 --- a/site/src/content/docs/ref/init-package.mdx +++ b/site/src/content/docs/ref/init-package.mdx @@ -184,7 +184,6 @@ The Zarf team maintains some optional components in the default 'init' package. | Components | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | k3s | REQUIRES ROOT (not sudo). Installs a lightweight Kubernetes Cluster on the local host [K3s](https://k3s.io/) and configures it to start up on boot. | -| logging | Adds a log monitoring stack [promtail/loki/grafana (aka PLG)](https://github.com/grafana/loki) into the cluster. | | git-server | Adds a [GitOps](https://about.gitlab.com/topics/gitops/)-compatible source control service [Gitea](https://gitea.io/en-us/) into the cluster. | There are two ways to deploy these optional components. First, you can provide a comma-separated list of components to the `--components` flag, such as `zarf init --components k3s,git-server --confirm`, or, you can choose to exclude the `--components` and `--confirm` flags and respond with a yes (`y`) or no (`n`) for each optional component when interactively prompted. @@ -271,7 +270,7 @@ cd zarf mv zarf.yaml zarf.yaml.bak ``` -You can learn more about creating a custom init package in the [Creating a Custom 'init' Package Tutorial](/tutorials/8-custom-init-packages). +You can learn more about creating a custom init package in the [Creating a Custom 'init' Package Tutorial](/tutorials/7-custom-init-packages). ::: diff --git a/site/src/content/docs/ref/packages.mdx b/site/src/content/docs/ref/packages.mdx index 4cb1e4434c..723c6ed845 100644 --- a/site/src/content/docs/ref/packages.mdx +++ b/site/src/content/docs/ref/packages.mdx @@ -32,7 +32,7 @@ Check out our [K3s cluster package](https://github.com/defenseunicorns/zarf/blob ::: -During the initialization process, Zarf will seed the cluster with a container registry to store images that other packages may require. The init package has the option to deploy other features to your cluster, such as a Git server to manage your repositories or a PLG logging stack that allows you to monitor the applications running on your cluster. For additional information on the init package, we provide detailed documentation on the Zarf ['init' package page](/ref/init-package/). +During the initialization process, Zarf will seed the cluster with a container registry to store images that other packages may require. The init package has the option to deploy other features to your cluster, such as a Git server to manage your repositories. For additional information on the init package, we provide detailed documentation on the Zarf ['init' package page](/ref/init-package/). #### Using the init-package @@ -50,7 +50,7 @@ During the deployment process, Zarf will leverage the infrastructure created dur ## Differential Packages -If you already have a Zarf package and you want to create an updated package you would normally have to re-create the entire package from scratch, including things that might not have changed. Depending on your workflow, you may want to create a package that only contains the artifacts that have changed since the last time you built your package. This can be achieved by using the `--differential` flag while running the `zarf package create` command. You can use this flag to point to an already built package you have locally or to a package that has been previously [published](/tutorials/7-publish-and-deploy#publish-package) to a registry. +If you already have a Zarf package and you want to create an updated package you would normally have to re-create the entire package from scratch, including things that might not have changed. Depending on your workflow, you may want to create a package that only contains the artifacts that have changed since the last time you built your package. This can be achieved by using the `--differential` flag while running the `zarf package create` command. You can use this flag to point to an already built package you have locally or to a package that has been previously [published](/tutorials/6-publish-and-deploy#publish-package) to a registry. ## Package Sources @@ -79,7 +79,7 @@ A remote tarball is a Zarf package tarball that is hosted on a web server that i ### Remote OCI Reference (`oci://`) -An OCI package is one that has been published to an OCI compatible registry using `zarf package publish` or the `-o` option on `zarf package create`. These packages live within a given registry and you can learn more about them in our [Publish & Deploy Packages w/OCI Tutorial](/tutorials/7-publish-and-deploy/). +An OCI package is one that has been published to an OCI compatible registry using `zarf package publish` or the `-o` option on `zarf package create`. These packages live within a given registry and you can learn more about them in our [Publish & Deploy Packages w/OCI Tutorial](/tutorials/6-publish-and-deploy/). :::note diff --git a/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx b/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx index 0966abe3b4..006d3a5743 100644 --- a/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx +++ b/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx @@ -35,8 +35,6 @@ More information about the init-package and its components can be found [here](/ ::: - - :::note You will only be prompted to deploy the k3s component if you are on a Linux machine ::: diff --git a/site/src/content/docs/tutorials/2-deploying-zarf-packages.mdx b/site/src/content/docs/tutorials/2-deploying-zarf-packages.mdx index b813b31a39..a196b15e94 100644 --- a/site/src/content/docs/tutorials/2-deploying-zarf-packages.mdx +++ b/site/src/content/docs/tutorials/2-deploying-zarf-packages.mdx @@ -98,7 +98,7 @@ If you receive this error, either you don't have a Kubernetes cluster, your clus If you need to setup a cluster, you can perform the following. -1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/5-creating-a-k8s-cluster-with-zarf/) tutorial. +1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/4-creating-a-k8s-cluster-with-zarf/) tutorial. 2. Perform the [Initialize a cluster](/tutorials/1-initializing-a-k8s-cluster/) tutorial. After that you can try deploying the package again. diff --git a/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx b/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx index ff12f4cd91..9364e98bbe 100644 --- a/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx +++ b/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx @@ -26,7 +26,7 @@ Before beginning this tutorial you will need the following: :::tip -You can publish your own packages for deployment too via `oci://`. See the [Store and Deploy Packages with OCI](/tutorials/7-publish-and-deploy/) tutorial for more information. +You can publish your own packages for deployment too via `oci://`. See the [Store and Deploy Packages with OCI](/tutorials/6-publish-and-deploy/) tutorial for more information. ::: @@ -82,7 +82,7 @@ If you receive this error, either you don't have a Kubernetes cluster, your clus If you need to setup a cluster, you can perform the following. -1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/5-creating-a-k8s-cluster-with-zarf/) tutorial. +1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/4-creating-a-k8s-cluster-with-zarf/) tutorial. 2. Perform the [Initialize a cluster](/tutorials/1-initializing-a-k8s-cluster/) tutorial. After that you can try deploying the package again. diff --git a/site/src/content/docs/tutorials/4-add-logging.mdx b/site/src/content/docs/tutorials/4-add-logging.mdx deleted file mode 100644 index 9f19ce4881..0000000000 --- a/site/src/content/docs/tutorials/4-add-logging.mdx +++ /dev/null @@ -1,115 +0,0 @@ ---- -title: Add Logging to a Cluster -sidebar: - order: 4 ---- - -## Introduction - -In this tutorial, we are going to show how you can use a Zarf component to inject zero-config, centralized logging into your Zarf cluster. - -More specifically, you'll be adding a [Promtail / Loki / Grafana (PLG)](https://github.com/grafana/loki) stack to the [Retro Arcade Tutorial](/tutorials/3-deploy-a-retro-arcade/) by installing Zarf's "logging" component. - -## System Requirements - -- You'll need an internet connection to grab the Zarf Init Package if it's not already on your machine. - -## Prerequisites - -Prior to this tutorial you'll want to have a working cluster with Zarf initialized. - -- Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install/)) -- [Initialize a cluster](/tutorials/1-initializing-a-k8s-cluster/). -- Perform the [Retro Arcade Tutorial](/tutorials/3-deploy-a-retro-arcade/). - -## Installing the Logging Component - -1. Run the `zarf init` command on your cluster. - -```sh -$ zarf init -``` - -2. When prompted to deploy the package select `y` for Yes, then hit the `enter` -key.
When prompted to deploy the logging component select `y` for Yes, then hit the `enter` key. - - - -4. You can automatically accept the logging component, and confirm the package using the `--confirm` and `--components` flags. - - - -### Connecting to the Logging Component - -#### Note the Credentials - -1. Review the `zarf init` command output for the following: - - - -You should see a section for `Logging`. You will need these credentials later on. - -## Deploy the Retro Arcade Tutorial - -1. If you haven't already in the prerequisites, deploy the [Retro Arcade Tutorial](/tutorials/3-deploy-a-retro-arcade/). - -## Check the logs - -:::note - -Because Doom may be freshly installed it is recommended to refresh the page a few times to generate more log traffic to view in Grafana - -::: - - -### Log into Grafana - -To open Grafana you can use the `zarf connect logging` command. - -You'll be redirected the `/login` page where you have to sign in with the Grafana credentials you saved [in a previous step](#note-the-credentials). - -![zarf user logging into Loki](../../../assets/tutorials/logging_login.png) - -Once you've successfully logged in go to: - -1. The "Explore" page (Button on the left that looks like a compass) -2. Select `Loki` in the dropdown, and then -3. Enter `{app="game"}` into the Log Browser query input field - -![zarf user typing {app='game'} into the Loki Log Browser](../../../assets/tutorials/logging_query.png) - -Submit that query and you'll get back a dump of all the game pod logs that Loki has collected. - -![Loki query results](../../../assets/tutorials/logging_logs.png) - -## Removal - -1. Use the `zarf package list` command to get a list of the installed packages and their components. This will give you the name of the init package and the logging component to remove it. - - - -2. Use the `zarf package remove` command to remove the `logging` component from the init package. Don't forget the `--confirm` flag. Otherwise you'll receive an error. - - - - -## Troubleshooting - -### Unable to connect to the Kubernetes cluster. - - - -:::note[Remediation] - -If you receive this error, either you don't have a Kubernetes cluster, your cluster is down, or your cluster is unreachable. - -1. Check your kubectl configuration, then try again. For more information about kubectl configuration see [Configure Access to Multiple Clusters](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) from the Kubernetes documentation. - -If you need to setup a cluster, you can perform the following. - -1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/5-creating-a-k8s-cluster-with-zarf/) tutorial. -2. Perform the [Initialize a cluster](/tutorials/1-initializing-a-k8s-cluster/) tutorial. - -After that you can try deploying the package again. - -::: diff --git a/site/src/content/docs/tutorials/5-creating-a-k8s-cluster-with-zarf.mdx b/site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx similarity index 100% rename from site/src/content/docs/tutorials/5-creating-a-k8s-cluster-with-zarf.mdx rename to site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx diff --git a/site/src/content/docs/tutorials/6-big-bang.mdx b/site/src/content/docs/tutorials/5-big-bang.mdx similarity index 99% rename from site/src/content/docs/tutorials/6-big-bang.mdx rename to site/src/content/docs/tutorials/5-big-bang.mdx index b609c1668c..b239f73ea3 100644 --- a/site/src/content/docs/tutorials/6-big-bang.mdx +++ b/site/src/content/docs/tutorials/5-big-bang.mdx @@ -145,7 +145,6 @@ zarf init # ? Do you want to download this init package? Yes # ? Deploy this Zarf package? Yes # ? Deploy the k3s component? No -# ? Deploy the logging component? No # ? Deploy the git-server component? Yes # (Optional) Inspect the results diff --git a/site/src/content/docs/tutorials/7-publish-and-deploy.mdx b/site/src/content/docs/tutorials/6-publish-and-deploy.mdx similarity index 99% rename from site/src/content/docs/tutorials/7-publish-and-deploy.mdx rename to site/src/content/docs/tutorials/6-publish-and-deploy.mdx index c50b8fee65..a03b0ec392 100644 --- a/site/src/content/docs/tutorials/7-publish-and-deploy.mdx +++ b/site/src/content/docs/tutorials/6-publish-and-deploy.mdx @@ -158,7 +158,7 @@ If you receive this error, either you don't have a Kubernetes cluster, your clus If you need to setup a cluster, you can perform the following. -1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/5-creating-a-k8s-cluster-with-zarf/) tutorial. +1. Deploy a Kubernetes cluster with the [Creating a K8s Cluster with Zarf](/tutorials/4-creating-a-k8s-cluster-with-zarf/) tutorial. 2. Perform the [Initialize a cluster](/tutorials/1-initializing-a-k8s-cluster/) tutorial. After that you can try deploying the package again. diff --git a/site/src/content/docs/tutorials/8-custom-init-packages.mdx b/site/src/content/docs/tutorials/7-custom-init-packages.mdx similarity index 100% rename from site/src/content/docs/tutorials/8-custom-init-packages.mdx rename to site/src/content/docs/tutorials/7-custom-init-packages.mdx diff --git a/site/src/content/docs/tutorials/9-resource-adoption.mdx b/site/src/content/docs/tutorials/8-resource-adoption.mdx similarity index 100% rename from site/src/content/docs/tutorials/9-resource-adoption.mdx rename to site/src/content/docs/tutorials/8-resource-adoption.mdx diff --git a/site/src/content/docs/tutorials/10-package-create-differential.mdx b/site/src/content/docs/tutorials/9-package-create-differential.mdx similarity index 100% rename from site/src/content/docs/tutorials/10-package-create-differential.mdx rename to site/src/content/docs/tutorials/9-package-create-differential.mdx diff --git a/site/src/content/docs/tutorials/index.mdx b/site/src/content/docs/tutorials/index.mdx index 0e27235ae2..9c0b43ef6d 100644 --- a/site/src/content/docs/tutorials/index.mdx +++ b/site/src/content/docs/tutorials/index.mdx @@ -18,7 +18,7 @@ Almost all tutorials will have the following prerequisites/assumptions: ## Setting Up a Local Kubernetes Cluster -While Zarf is able to deploy a local k3s Kubernetes cluster for you, (as you'll find out more in the [Creating a K8s Cluster with Zarf](/tutorials/5-creating-a-k8s-cluster-with-zarf) tutorial), that k3s cluster will only work if you are on a root user on a Linux machine. If you are on a Mac, or you're on Linux but don't have root access, you'll need to set up a local dockerized Kubernetes cluster manually. We provide instructions on how to quickly set up a local k3d cluster that you can use for the majority of the tutorials. +While Zarf is able to deploy a local k3s Kubernetes cluster for you, (as you'll find out more in the [Creating a K8s Cluster with Zarf](/tutorials/4-creating-a-k8s-cluster-with-zarf) tutorial), that k3s cluster will only work if you are on a root user on a Linux machine. If you are on a Mac, or you're on Linux but don't have root access, you'll need to set up a local dockerized Kubernetes cluster manually. We provide instructions on how to quickly set up a local k3d cluster that you can use for the majority of the tutorials. ### Install k3d diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 054587337c..e2f3884183 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -25,7 +25,7 @@ var ( cliOnly bool connectCmd = &cobra.Command{ - Use: "connect { REGISTRY | LOGGING | GIT | connect-name }", + Use: "connect { REGISTRY | GIT | connect-name }", Aliases: []string{"c"}, Short: lang.CmdConnectShort, Long: lang.CmdConnectLong, diff --git a/src/config/config.go b/src/config/config.go index 53e49d720b..d3b0c07a21 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -41,8 +41,6 @@ const ( ZarfImagePullSecretName = "private-registry" ZarfGitServerSecretName = "private-git-server" - ZarfLoggingUser = "zarf-admin" - UnsetCLIVersion = "unset-development-only" ) diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 59dcdc5af2..68caf91270 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -64,7 +64,7 @@ const ( // zarf connect CmdConnectShort = "Accesses services or pods deployed in the cluster" CmdConnectLong = "Uses a k8s port-forward to connect to resources within the cluster referenced by your kube-context.\n" + - "Three default options for this command are . These will connect to the Zarf created resources " + + "Two default options for this command are . These will connect to the Zarf created resources " + "(assuming they were selected when performing the `zarf init` command).\n\n" + "Packages can provide service manifests that define their own shortcut connection options. These options will be " + "printed to the terminal when the package finishes deploying.\n If you don't remember what connection shortcuts your deployed " + @@ -112,10 +112,10 @@ const ( // zarf init CmdInitShort = "Prepares a k8s cluster for the deployment of Zarf packages" - CmdInitLong = "Injects a docker registry as well as other optional useful things (such as a git server " + - "and a logging stack) into a k8s cluster under the 'zarf' namespace " + + CmdInitLong = "Injects an OCI registry as well as an optional git server " + + "into a Kubernetes cluster in the zarf namespace " + "to support future application deployments.\n" + - "If you do not have a k8s cluster already configured, this command will give you " + + "If you do not have a cluster already configured, this command will give you " + "the ability to install a cluster locally.\n\n" + "This command looks for a zarf-init package in the local directory that the command was executed " + "from. If no package is found in the local directory and the Zarf CLI exists somewhere outside of " + @@ -129,9 +129,6 @@ $ zarf init # Initializing w/ Zarfs internal git server: $ zarf init --components=git-server -# Initializing w/ Zarfs internal git server and PLG stack: -$ zarf init --components=git-server,logging - # Initializing w/ an internal registry but with a different nodeport: $ zarf init --nodeport=30333 @@ -162,7 +159,7 @@ $ zarf init --artifact-push-password={PASSWORD} --artifact-push-username={USERNA CmdInitFlagSet = "Specify deployment variables to set on the command line (KEY=value)" CmdInitFlagConfirm = "Confirms package deployment without prompting. ONLY use with packages you trust. Skips prompts to review SBOM, configure variables, select optional components and review potential breaking changes." - CmdInitFlagComponents = "Specify which optional components to install. E.g. --components=git-server,logging" + CmdInitFlagComponents = "Specify which optional components to install. E.g. --components=git-server" CmdInitFlagStorageClass = "Specify the storage class to use for the registry and git server. E.g. --storage-class=standard" CmdInitFlagGitURL = "External git server url to use for this Zarf cluster" @@ -590,7 +587,6 @@ $ zarf tools get-creds registry-readonly $ zarf tools get-creds git $ zarf tools get-creds git-readonly $ zarf tools get-creds artifact -$ zarf tools get-creds logging ` CmdToolsUpdateCredsShort = "Updates the credentials for deployed Zarf services. Pass a service key to update credentials for a single service" diff --git a/src/internal/packager/template/template.go b/src/internal/packager/template/template.go index ad7ee64f11..8b76252aa1 100644 --- a/src/internal/packager/template/template.go +++ b/src/internal/packager/template/template.go @@ -81,9 +81,6 @@ func GetZarfTemplates(componentName string, state *types.ZarfState) (templateMap } builtinMap["HTPASSWD"] = htpasswd builtinMap["REGISTRY_SECRET"] = regInfo.Secret - - case "logging": - builtinMap["LOGGING_AUTH"] = state.LoggingSecret } // Iterate over any custom variables and add them to the mappings for templating @@ -93,7 +90,7 @@ func GetZarfTemplates(componentName string, state *types.ZarfState) (templateMap Value: value, } - if key == "LOGGING_AUTH" || key == "REGISTRY_SECRET" || key == "HTPASSWD" || + if key == "REGISTRY_SECRET" || key == "HTPASSWD" || key == "AGENT_CA" || key == "AGENT_KEY" || key == "AGENT_CRT" || key == "GIT_AUTH_PULL" || key == "GIT_AUTH_PUSH" || key == "REGISTRY_AUTH_PULL" || key == "REGISTRY_AUTH_PUSH" { // Sanitize any builtin templates that are sensitive diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index ca3abf75ea..5471628667 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -35,10 +35,7 @@ const ( // InitZarfState initializes the Zarf state with the given temporary directory and init configs. func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitOptions) error { - var ( - distro string - err error - ) + var distro string spinner := message.NewProgressSpinner("Gathering cluster state information") defer spinner.Stop() @@ -79,9 +76,6 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // Defaults state.Distro = distro - if state.LoggingSecret, err = helpers.RandomString(types.ZarfGeneratedPasswordLen); err != nil { - return fmt.Errorf("%s: %w", lang.ErrUnableToGenerateRandomSecret, err) - } // Setup zarf agent PKI state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) @@ -246,9 +240,6 @@ func (c *Cluster) sanitizeZarfState(state *types.ZarfState) *types.ZarfState { // Overwrite the ArtifactServer secret state.ArtifactServer.PushToken = "**sanitized**" - // Overwrite the Logging secret - state.LoggingSecret = "**sanitized**" - return state } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 3ce53fa53e..e09103829d 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -31,7 +31,6 @@ import ( // Zarf specific connect strings const ( ZarfRegistry = "REGISTRY" - ZarfLogging = "LOGGING" ZarfGit = "GIT" ZarfInjector = "INJECTOR" @@ -107,28 +106,18 @@ func (c *Cluster) Connect(ctx context.Context, target string) (*Tunnel, error) { zt.resourceName = ZarfRegistryName zt.remotePort = ZarfRegistryPort zt.urlSuffix = `/v2/_catalog` - - case ZarfLogging: - zt.resourceName = "zarf-loki-stack-grafana" - zt.remotePort = 3000 - // Start the logs with something useful. - zt.urlSuffix = `/monitor/explore?orgId=1&left=%5B"now-12h","now","Loki",%7B"refId":"Zarf%20Logs","expr":"%7Bnamespace%3D%5C"zarf%5C"%7D"%7D%5D` - case ZarfGit: zt.resourceName = ZarfGitServerName zt.remotePort = ZarfGitServerPort - case ZarfInjector: zt.resourceName = ZarfInjectorName zt.remotePort = ZarfInjectorPort - default: if target != "" { if zt, err = c.checkForZarfConnectLabel(ctx, target); err != nil { return nil, fmt.Errorf("problem looking for a zarf connect label in the cluster: %s", err.Error()) } } - if zt.resourceName == "" { return nil, fmt.Errorf("missing resource name") } @@ -136,7 +125,6 @@ func (c *Cluster) Connect(ctx context.Context, target string) (*Tunnel, error) { return nil, fmt.Errorf("missing remote port") } } - return c.ConnectTunnelInfo(ctx, zt) } diff --git a/src/pkg/message/credentials.go b/src/pkg/message/credentials.go index 07b85525bc..19f86f8516 100644 --- a/src/pkg/message/credentials.go +++ b/src/pkg/message/credentials.go @@ -8,7 +8,6 @@ import ( "fmt" "strings" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/types" "github.com/pterm/pterm" ) @@ -20,14 +19,13 @@ const ( GitKey = "git" GitReadKey = "git-readonly" ArtifactKey = "artifact" - LoggingKey = "logging" AgentKey = "agent" ) // PrintCredentialTable displays credentials in a table func PrintCredentialTable(state *types.ZarfState, componentsToDeploy []types.DeployedComponent) { if len(componentsToDeploy) == 0 { - componentsToDeploy = []types.DeployedComponent{{Name: "logging"}, {Name: "git-server"}} + componentsToDeploy = []types.DeployedComponent{{Name: "git-server"}} } // Pause the logfile's output to avoid credentials being printed to the log file @@ -45,10 +43,6 @@ func PrintCredentialTable(state *types.ZarfState, componentsToDeploy []types.Dep } for _, component := range componentsToDeploy { - // Show message if including logging stack - if component.Name == "logging" { - loginData = append(loginData, []string{"Logging", config.ZarfLoggingUser, state.LoggingSecret, "zarf connect logging", LoggingKey}) - } // Show message if including git-server if component.Name == "git-server" { loginData = append(loginData, @@ -68,9 +62,6 @@ func PrintCredentialTable(state *types.ZarfState, componentsToDeploy []types.Dep // PrintComponentCredential displays credentials for a single component func PrintComponentCredential(state *types.ZarfState, componentName string) { switch strings.ToLower(componentName) { - case LoggingKey: - Notef("Logging credentials (username: %s):", config.ZarfLoggingUser) - fmt.Println(state.LoggingSecret) case GitKey: Notef("Git Server push password (username: %s):", state.GitServer.PushUsername) fmt.Println(state.GitServer.PushPassword) diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index be3afb6b4f..93c5778c0c 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -75,7 +75,7 @@ func TestUseCLI(t *testing.T) { t.Parallel() // Test for expected failure when given a bad component input path := fmt.Sprintf("build/zarf-package-component-actions-%s.tar.zst", e2e.Arch) - _, _, err := e2e.Zarf("package", "deploy", path, "--components=on-create,foo,logging", "--confirm") + _, _, err := e2e.Zarf("package", "deploy", path, "--components=on-create,foo,git-server", "--confirm") require.Error(t, err) }) diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go index ddcf084394..92fe26bc98 100644 --- a/src/test/e2e/20_zarf_init_test.go +++ b/src/test/e2e/20_zarf_init_test.go @@ -20,10 +20,9 @@ func TestZarfInit(t *testing.T) { t.Log("E2E: Zarf init") e2e.SetupWithCluster(t) - initComponents := "logging,git-server" - // Add k3s component in appliance mode + initComponents := "git-server" if e2e.ApplianceMode { - initComponents = "k3s,logging,git-server" + initComponents = "k3s,git-server" } initPackageVersion := e2e.GetZarfVersion(t) @@ -83,11 +82,6 @@ func TestZarfInit(t *testing.T) { require.NoError(t, err) checkLogForSensitiveState(t, logText, state) - // Check the old state values as well (if they exist) to ensure they weren't printed and then updated during init - if oldState.LoggingSecret != "" { - checkLogForSensitiveState(t, logText, oldState) - } - if e2e.ApplianceMode { // make sure that we upgraded `k3s` correctly and are running the correct version - this should match that found in `packages/distros/k3s` kubeletVersion, _, err := e2e.Kubectl("get", "nodes", "-o", "jsonpath={.items[0].status.nodeInfo.kubeletVersion}") @@ -130,7 +124,6 @@ func checkLogForSensitiveState(t *testing.T, logText string, zarfState types.Zar require.NotContains(t, logText, zarfState.RegistryInfo.PullPassword) require.NotContains(t, logText, zarfState.RegistryInfo.PushPassword) require.NotContains(t, logText, zarfState.RegistryInfo.Secret) - require.NotContains(t, logText, zarfState.LoggingSecret) } func verifyZarfNamespaceLabels(t *testing.T) { @@ -198,16 +191,8 @@ func verifyZarfPodLabels(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedLabels, actualLabels) - // logging and git server pods should have the `zarf-agent=patched` label - // since they should have been mutated by the agent - patchedLabel := `"zarf-agent":"patched"` - - // logging - actualLabels, _, err = e2e.Kubectl("get", "-n=zarf", "--selector=app.kubernetes.io/instance=zarf-loki-stack", "pods", "-o=jsonpath='{.items[0].metadata.labels}'") - require.NoError(t, err) - require.Contains(t, actualLabels, patchedLabel) - // git server + patchedLabel := `"zarf-agent":"patched"` actualLabels, _, err = e2e.Kubectl("get", "-n=zarf", "--selector=app.kubernetes.io/instance=zarf-gitea ", "pods", "-o=jsonpath='{.items[0].metadata.labels}'") require.NoError(t, err) require.Contains(t, actualLabels, patchedLabel) @@ -222,12 +207,6 @@ func verifyZarfServiceLabels(t *testing.T) { require.NoError(t, err) require.Equal(t, expectedLabels, actualLabels) - // logging - expectedLabels = `'{"app.kubernetes.io/managed-by":"Helm","zarf.dev/connect-name":"logging"}'` - actualLabels, _, err = e2e.Kubectl("get", "-n=zarf", "service", "zarf-connect-logging", "-o=jsonpath='{.metadata.labels}'") - require.NoError(t, err) - require.Equal(t, expectedLabels, actualLabels) - // git server expectedLabels = `'{"app.kubernetes.io/managed-by":"Helm","zarf.dev/connect-name":"git"}'` actualLabels, _, err = e2e.Kubectl("get", "-n=zarf", "service", "zarf-connect-git", "-o=jsonpath='{.metadata.labels}'") diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index f7bc4ce25a..5c68277ff7 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -40,26 +40,6 @@ func TestConnectAndCreds(t *testing.T) { require.NotEqual(t, prevAgentSecretData, newAgentSecretData, "agent secrets should not be the same") connectToZarfServices(ctx, t) - - stdOut, stdErr, err = e2e.Zarf("package", "remove", "init", "--components=logging", "--confirm") - require.NoError(t, err, stdOut, stdErr) - - // Prune the images from Grafana and ensure that they are gone - stdOut, stdErr, err = e2e.Zarf("tools", "registry", "prune", "--confirm") - require.NoError(t, err, stdOut, stdErr) - - stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/library/registry") - require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdOut, "2.8.3") - stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/promtail") - require.NoError(t, err, stdOut, stdErr) - require.Empty(t, stdOut) - stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/grafana") - require.NoError(t, err, stdOut, stdErr) - require.Empty(t, stdOut) - stdOut, stdErr, err = e2e.Zarf("tools", "registry", "ls", "127.0.0.1:31337/grafana/loki") - require.NoError(t, err, stdOut, stdErr) - require.Empty(t, stdOut) } func TestMetrics(t *testing.T) { @@ -107,15 +87,11 @@ func connectToZarfServices(ctx context.Context, t *testing.T) { require.NoError(t, err, stdOut, stdErr) registryList := strings.Split(strings.Trim(stdOut, "\n "), "\n") - // We assert greater than or equal to since the base init has 12 images + // We assert greater than or equal to since the base init has 8 images // HOWEVER during an upgrade we could have mismatched versions/names resulting in more images - require.GreaterOrEqual(t, len(registryList), 7) + require.GreaterOrEqual(t, len(registryList), 3) require.Contains(t, stdOut, "defenseunicorns/zarf/agent") require.Contains(t, stdOut, "gitea/gitea") - require.Contains(t, stdOut, "grafana/grafana") - require.Contains(t, stdOut, "grafana/loki") - require.Contains(t, stdOut, "grafana/promtail") - require.Contains(t, stdOut, "kiwigrid/k8s-sidecar") require.Contains(t, stdOut, "library/registry") // Get the git credentials @@ -149,16 +125,4 @@ func connectToZarfServices(ctx context.Context, t *testing.T) { respGit, err = http.Get(gitArtifactURL) require.NoError(t, err) require.Equal(t, 200, respGit.StatusCode) - - // Connect to the Logging Stack - c, err = cluster.NewCluster() - require.NoError(t, err) - tunnelLog, err := c.Connect(ctx, cluster.ZarfLogging) - require.NoError(t, err) - defer tunnelLog.Close() - - // Make sure Grafana comes up cleanly - respLog, err := http.Get(tunnelLog.HTTPEndpoint()) - require.NoError(t, err) - require.Equal(t, 200, respLog.StatusCode) } diff --git a/src/types/k8s.go b/src/types/k8s.go index 2bf0900cf9..2c2c6b3527 100644 --- a/src/types/k8s.go +++ b/src/types/k8s.go @@ -67,7 +67,6 @@ type ZarfState struct { GitServer GitServerInfo `json:"gitServer" jsonschema:"description=Information about the repository Zarf is configured to use"` RegistryInfo RegistryInfo `json:"registryInfo" jsonschema:"description=Information about the container registry Zarf is configured to use"` ArtifactServer ArtifactServerInfo `json:"artifactServer" jsonschema:"description=Information about the artifact registry Zarf is configured to use"` - LoggingSecret string `json:"loggingSecret" jsonschema:"description=Secret value that the internal Grafana server was seeded with"` } // DeployedPackage contains information about a Zarf Package that has been deployed to a cluster diff --git a/zarf.yaml b/zarf.yaml index 859813fde5..0932f8155b 100644 --- a/zarf.yaml +++ b/zarf.yaml @@ -32,11 +32,6 @@ components: import: path: packages/zarf-agent - # (Optional) Adds logging to the cluster - - name: logging - import: - path: packages/logging-pgl - # (Optional) Adds a git server to the cluster - name: git-server import: From acb951cd964cf0a445279a1775392fe1c356ef98 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Wed, 26 Jun 2024 17:40:23 -0500 Subject: [PATCH 085/132] chore: patch CVE-2024-6104 --- go.mod | 2 +- go.sum | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7ae48f23fe..83df7d5786 100644 --- a/go.mod +++ b/go.mod @@ -290,7 +290,7 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-getter v1.7.4 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect diff --git a/go.sum b/go.sum index 3fbc8acdb4..b4033a097a 100644 --- a/go.sum +++ b/go.sum @@ -1028,11 +1028,10 @@ github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9n github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -1041,8 +1040,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= From 4ee7d54e32231a3249a4e5eb6ecfc93084b66bcb Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 27 Jun 2024 09:01:11 -0500 Subject: [PATCH 086/132] chore: patch CVE-2024-35255 --- go.mod | 10 +++++----- go.sum | 26 ++++++++++++-------------- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 83df7d5786..0547f82a15 100644 --- a/go.mod +++ b/go.mod @@ -82,9 +82,9 @@ require ( github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -96,7 +96,7 @@ require ( github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect github.com/BurntSushi/toml v1.3.2 // indirect github.com/CycloneDX/cyclonedx-go v0.8.0 // indirect github.com/DataDog/zstd v1.4.5 // indirect @@ -263,7 +263,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect - github.com/golang-jwt/jwt/v5 v5.2.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect diff --git a/go.sum b/go.sum index b4033a097a..ee9a8bb240 100644 --- a/go.sum +++ b/go.sum @@ -221,12 +221,12 @@ github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo github.com/AliyunContainerService/ack-ram-tool/pkg/credentials/alibabacloudsdkgo/helper v0.2.0/go.mod h1:GgeIE+1be8Ivm7Sh4RgwI42aTtC9qrcj+Y9Y6CjJhJs= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0 h1:1nGuui+4POelzDwI7RG56yfQJHCnKvwfMoU7VsEp+Zg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0 h1:H+U3Gk9zY56G3u872L82bk4thcsy2Gghb9ExT4Zvm1o= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.9.0/go.mod h1:mgrmMSgaLp9hmax62XQTd0N4aAqSE5E0DulSpVYK7vc= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= @@ -256,8 +256,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= @@ -635,8 +635,6 @@ github.com/distribution/distribution/v3 v3.0.0-alpha.1 h1:jn7I1gvjOvmLztH1+1cLiU github.com/distribution/distribution/v3 v3.0.0-alpha.1/go.mod h1:LCp4JZp1ZalYg0W/TN05jarCQu+h4w7xc7ZfQF4Y/cY= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/docker/cli v26.0.0+incompatible h1:90BKrx1a1HKYpSnnBFR6AgDq/FqkHxwlUyzJVPxD30I= github.com/docker/cli v26.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= @@ -850,8 +848,8 @@ github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= -github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -1486,8 +1484,8 @@ github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c h1:fPpdjePK1atuOg28PXfNSqgwf9I/qD1Hlo39JFwKBXk= -github.com/rogpeppe/go-internal v1.11.1-0.20231026093722-fa6a31e0812c/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= From c3465f9f4d2749a556dae94077141ddbf493430e Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Thu, 27 Jun 2024 09:32:21 -0500 Subject: [PATCH 087/132] chore: patch CVE-2024-6257 --- go.mod | 46 ++++++++++++++--------------- go.sum | 91 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 69 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index 0547f82a15..30abd8b0ca 100644 --- a/go.mod +++ b/go.mod @@ -66,14 +66,14 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect - cloud.google.com/go v0.113.0 // indirect - cloud.google.com/go/auth v0.4.1 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.6.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.8 // indirect - cloud.google.com/go/kms v1.16.0 // indirect + cloud.google.com/go/iam v1.1.9 // indirect + cloud.google.com/go/kms v1.18.0 // indirect cloud.google.com/go/longrunning v0.5.7 // indirect - cloud.google.com/go/storage v1.41.0 // indirect + cloud.google.com/go/storage v1.42.0 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 // indirect cuelang.org/go v0.7.0 // indirect dario.cat/mergo v1.0.0 // indirect @@ -140,7 +140,7 @@ require ( github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go v1.53.1 // indirect + github.com/aws/aws-sdk-go v1.54.9 // indirect github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect @@ -244,7 +244,7 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.22.0 // indirect github.com/go-openapi/errors v0.21.0 // indirect @@ -280,7 +280,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.4 // indirect + github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/gookit/color v1.5.4 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -288,7 +288,7 @@ require ( github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.4 // indirect + github.com/hashicorp/go-getter v1.7.5 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -296,7 +296,7 @@ require ( github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect github.com/hashicorp/go-sockaddr v1.0.5 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/vault/api v1.10.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect @@ -318,7 +318,7 @@ require ( github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect @@ -466,12 +466,12 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect - go.opentelemetry.io/otel v1.26.0 // indirect - go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect + go.opentelemetry.io/otel v1.27.0 // indirect + go.opentelemetry.io/otel/metric v1.27.0 // indirect go.opentelemetry.io/otel/sdk v1.26.0 // indirect - go.opentelemetry.io/otel/trace v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.27.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.step.sm/crypto v0.42.1 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -479,18 +479,18 @@ require ( golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.26.0 // indirect - golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/api v0.180.0 // indirect - google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect - google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.34.1 // indirect + google.golang.org/api v0.186.0 // indirect + google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d // indirect + google.golang.org/grpc v1.64.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index ee9a8bb240..7cc3787141 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA= -cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -55,8 +55,8 @@ cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjby cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= -cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g= +cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= @@ -121,10 +121,10 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= -cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= -cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= -cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= +cloud.google.com/go/iam v1.1.9 h1:oSkYLVtVme29uGYrOcKcvJRht7cHJpYD09GM9JaR0TE= +cloud.google.com/go/iam v1.1.9/go.mod h1:Nt1eDWNYH9nGQg3d/mY7U1hvfGmsaG9o/kLGoLoLXjQ= +cloud.google.com/go/kms v1.18.0 h1:pqNdaVmZJFP+i8OVLocjfpdTWETTYa20FWOegSCdrRo= +cloud.google.com/go/kms v1.18.0/go.mod h1:DyRBeWD/pYBMeyiaXFa/DGNyxMDL3TslIKb8o/JkLkw= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= @@ -187,8 +187,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= -cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= +cloud.google.com/go/storage v1.42.0 h1:4QtGpplCVt1wz6g5o1ifXd656P5z+yNgzdw1tVfp0cU= +cloud.google.com/go/storage v1.42.0/go.mod h1:HjMXRFq65pGKFn6hxj6x3HCyR41uSB72Z0SO/Vn6JFQ= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -422,8 +422,8 @@ github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkU github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.53.1 h1:15/i0m9rE8r1q3P4ooHCfZTJtkxwG2Dwqp9JhPaVbs0= -github.com/aws/aws-sdk-go v1.53.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.54.9 h1:e0Czh9AhrCVPuyaIUnibYmih3cYexJKlqlHSJ2eMKbI= +github.com/aws/aws-sdk-go v1.54.9/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= @@ -775,8 +775,8 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= @@ -988,8 +988,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= -github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= @@ -1024,8 +1024,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= -github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= @@ -1056,8 +1056,9 @@ github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3ly github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= @@ -1155,8 +1156,8 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -1738,12 +1739,12 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= -go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0 h1:vS1Ao/R55RNV4O7TA2Qopok8yN+X0LIP6RVWLFkprck= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.52.0/go.mod h1:BMsdeOxN04K0L5FNUBfjFdvwWGNe/rkmSwH4Aelu/X0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0= +go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg= +go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= @@ -1760,14 +1761,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik= +go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak= go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= -go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw= +go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -1946,8 +1947,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= -golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2232,8 +2233,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= -google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= +google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug= +google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2346,12 +2347,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 h1:XpH03M6PDRKTo1oGfZBXu2SzwcbfxUokgobVinuUZoU= -google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8/go.mod h1:OLh2Ylz+WlYAJaSBRpJIJLP8iQP+8da+fpxbwNEAV/o= -google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= -google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= +google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d h1:Aqf0fiIdUQEj0Gn9mKFFXoQfTTEaNopWpfVyYADxiSg= +google.golang.org/genproto/googleapis/api v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Od4k8V1LQSizPRUK4OzZ7TBE/20k+jPczUDAEyvn69Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d h1:k3zyW3BYYR30e8v3x0bTDdE9vpYFjZHK+HcyqkrppWk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2388,8 +2389,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2406,8 +2407,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From b2797a914544afdfebdc2d6a0e4ee348cdd7a41f Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Wed, 26 Jun 2024 14:50:23 +0000 Subject: [PATCH 088/132] data injection docs --- examples/kiwix/manifests/deployment.yaml | 1 + site/src/content/docs/ref/components.mdx | 10 +++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/kiwix/manifests/deployment.yaml b/examples/kiwix/manifests/deployment.yaml index 679d7a66d7..e187c684e6 100644 --- a/examples/kiwix/manifests/deployment.yaml +++ b/examples/kiwix/manifests/deployment.yaml @@ -15,6 +15,7 @@ spec: app: kiwix-serve spec: # Kiwix can hot-load files from the filesystem, but if your application cannot, this example shows how you can use an initContainer to bootstrap the injected files. + # It's necessary to include the ###ZARF_DATA_INJECTION_MARKER### somewhere in the podspec, otherwise data injections will not occur. initContainers: - name: data-loader image: alpine:3.18 diff --git a/site/src/content/docs/ref/components.mdx b/site/src/content/docs/ref/components.mdx index aff22ada6e..ed2e8c884e 100644 --- a/site/src/content/docs/ref/components.mdx +++ b/site/src/content/docs/ref/components.mdx @@ -14,7 +14,7 @@ These components define what dependencies they have along with a declarative def Each package can have as many components as the package creator wants but a package isn't anything without at least one component. -{/* +{/* ## What can be Packaged? The following types of software can be rolled into a Zarf Package: @@ -141,10 +141,10 @@ Images can either be discovered manually, or automatically by using [`zarf dev f ### Git Repositories -The [`podinfo-flux`](/ref/examples/podinfo-flux/) example showcases a simple GitOps workflow using Flux and Zarf. - +The [`podinfo-flux`](/ref/examples/podinfo-flux/) example showcases a simple GitOps workflow using Flux and Zarf. + @@ -208,6 +208,10 @@ Git repositories included in a package can be deployed with `zarf package deploy +During `zarf package create`, data injections pull files from the host at the path specified by the `source` key. During `zarf package deploy`, these files are injected into the container specified by the `target` key. The pod holding the targeted container must have the variable `###ZARF_DATA_INJECTION_MARKER###` within the pod spec otherwise the data injection will not occur. This variable gets templated at deploy time to become the name of the extra file Zarf injects into the pod to signify that the data injection is complete. + +The [`kiwix`](/ref/examples/kiwix/) example showcases a simple data injection use case. + ### Component Imports From 74daaa27e3ef3634617cbbac4016225ffb27e93d Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 17:15:13 +0000 Subject: [PATCH 089/132] cleanup windows tests --- .github/workflows/test-windows.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index a26614f029..d7441888d1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -44,13 +44,6 @@ jobs: os: windows shell: pwsh - # TODO: (@WSTARR) Builds an init package manually off of the v0.30.1 - # release since Windows in GitHub cannot natively build linux containers - # and the tests this workflow run do not use the agent at all! - - name: Build init-package - run: | - make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.30.1 - - name: Run windows E2E tests - run: make test-e2e ARCH=amd64 -e SKIP_K8S=true + run: make test-e2e-without-cluster shell: pwsh From 6d9c93a305f764f6d97a7f14a3d06b1264e188f9 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 17:38:26 +0000 Subject: [PATCH 090/132] create cache --- .github/workflows/test-windows.yml | 24 ++++++++++++++++++++++++ src/test/e2e/main_test.go | 1 - 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index d7441888d1..740e97c312 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -33,6 +33,23 @@ jobs: - name: Setup golang uses: ./.github/actions/golang + - id: go-cache-paths + run: | + Write-Output "::set-output name=go-build::$(go env GOCACHE)" + Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" + shell: pwsh + + - name: Cache Go modules + id: cache-go-mod + uses: actions/cache@v4 + with: + path: | + ${{ steps.go-cache-paths.outputs.go-build }} + ${{ steps.go-cache-paths.outputs.go-mod }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + - name: Run Windows unit tests run: make test-unit shell: pwsh @@ -44,6 +61,13 @@ jobs: os: windows shell: pwsh + # TODO: (@WSTARR) Builds an init package manually off of the v0.30.1 + # release since Windows in GitHub cannot natively build linux containers + # and the tests this workflow run do not use the agent at all! + - name: Build init-package + run: | + make release-init-package ARCH=amd64 AGENT_IMAGE_TAG=v0.30.1 + - name: Run windows E2E tests run: make test-e2e-without-cluster shell: pwsh diff --git a/src/test/e2e/main_test.go b/src/test/e2e/main_test.go index da33bbcea2..6c9c283a55 100644 --- a/src/test/e2e/main_test.go +++ b/src/test/e2e/main_test.go @@ -38,7 +38,6 @@ func TestMain(m *testing.M) { e2e.ZarfBinPath = filepath.Join("build", test.GetCLIName()) e2e.ApplianceMode = os.Getenv(applianceModeEnvVar) == "true" e2e.ApplianceModeKeep = os.Getenv(applianceModeKeepEnvVar) == "true" - e2e.RunClusterTests = os.Getenv(skipK8sEnvVar) != "true" message.SetLogLevel(message.TraceLevel) From 96a3b0c16be610d509e48712769ea8855de6831d Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 18:01:21 +0000 Subject: [PATCH 091/132] go cache paths --- .github/workflows/test-windows.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 740e97c312..4519206797 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -33,7 +33,8 @@ jobs: - name: Setup golang uses: ./.github/actions/golang - - id: go-cache-paths + - name: set go cache paths + id: go-cache-paths run: | Write-Output "::set-output name=go-build::$(go env GOCACHE)" Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" From 426b52471e8c0dba14bdeea4a49a4d8d2203b50e Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 18:11:53 +0000 Subject: [PATCH 092/132] delete optimize caching --- .github/workflows/test-windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 4519206797..8959b3afce 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -26,9 +26,9 @@ jobs: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Optimize caching - run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" - shell: cmd + # - name: Optimize caching + # run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" + # shell: cmd - name: Setup golang uses: ./.github/actions/golang From ed5cb770222f8ade0b0e7596810c22e2cfcf19c5 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 18:16:01 +0000 Subject: [PATCH 093/132] hanging --- .github/workflows/test-windows.yml | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 8959b3afce..f1abcb906e 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -33,23 +33,23 @@ jobs: - name: Setup golang uses: ./.github/actions/golang - - name: set go cache paths - id: go-cache-paths - run: | - Write-Output "::set-output name=go-build::$(go env GOCACHE)" - Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" - shell: pwsh + # - name: set go cache paths + # id: go-cache-paths + # run: | + # Write-Output "::set-output name=go-build::$(go env GOCACHE)" + # Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" + # shell: pwsh - - name: Cache Go modules - id: cache-go-mod - uses: actions/cache@v4 - with: - path: | - ${{ steps.go-cache-paths.outputs.go-build }} - ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- + # - name: Cache Go modules + # id: cache-go-mod + # uses: actions/cache@v4 + # with: + # path: | + # ${{ steps.go-cache-paths.outputs.go-build }} + # ${{ steps.go-cache-paths.outputs.go-mod }} + # key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + # restore-keys: | + # ${{ runner.os }}-go- - name: Run Windows unit tests run: make test-unit From 27249f3ec03d284125bbbe958b1101ffeec8e878 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 18:32:37 +0000 Subject: [PATCH 094/132] tryout different caching --- .github/workflows/test-windows.yml | 38 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f1abcb906e..522e880f59 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -30,26 +30,28 @@ jobs: # run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" # shell: cmd - - name: Setup golang - uses: ./.github/actions/golang + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version-file: 'go.mod' + cache: false - # - name: set go cache paths - # id: go-cache-paths - # run: | - # Write-Output "::set-output name=go-build::$(go env GOCACHE)" - # Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" - # shell: pwsh + - name: set go cache paths + id: go-cache-paths + run: | + Write-Output "::set-output name=go-build::$(go env GOCACHE)" + Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" + shell: pwsh - # - name: Cache Go modules - # id: cache-go-mod - # uses: actions/cache@v4 - # with: - # path: | - # ${{ steps.go-cache-paths.outputs.go-build }} - # ${{ steps.go-cache-paths.outputs.go-mod }} - # key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - # restore-keys: | - # ${{ runner.os }}-go- + - name: Cache Go modules + id: cache-go-mod + uses: actions/cache@v4 + with: + path: | + ${{ steps.go-cache-paths.outputs.go-build }} + ${{ steps.go-cache-paths.outputs.go-mod }} + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- - name: Run Windows unit tests run: make test-unit From 22c5e0d28e4298633d15321ec045f8a6a845e46c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 18:55:02 +0000 Subject: [PATCH 095/132] disable cache entirely --- .github/workflows/test-windows.yml | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 522e880f59..71b0a62738 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -35,23 +35,23 @@ jobs: go-version-file: 'go.mod' cache: false - - name: set go cache paths - id: go-cache-paths - run: | - Write-Output "::set-output name=go-build::$(go env GOCACHE)" - Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" - shell: pwsh + # - name: set go cache paths + # id: go-cache-paths + # run: | + # Write-Output "::set-output name=go-build::$(go env GOCACHE)" + # Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" + # shell: pwsh - - name: Cache Go modules - id: cache-go-mod - uses: actions/cache@v4 - with: - path: | - ${{ steps.go-cache-paths.outputs.go-build }} - ${{ steps.go-cache-paths.outputs.go-mod }} - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- + # - name: Cache Go modules + # id: cache-go-mod + # uses: actions/cache@v4 + # with: + # path: | + # ${{ steps.go-cache-paths.outputs.go-build }} + # ${{ steps.go-cache-paths.outputs.go-mod }} + # key: windows-go-${{ hashFiles('**/go.sum') }} + # restore-keys: | + # windows-go- - name: Run Windows unit tests run: make test-unit From 50f4ffba0af383d9844ae5adfc961e836b8f3274 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 19:04:43 +0000 Subject: [PATCH 096/132] separate windows runs into separate jobs --- .github/workflows/test-windows.yml | 39 +++++++++--------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 71b0a62738..bb6398c2a5 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -20,43 +20,28 @@ concurrency: cancel-in-progress: true jobs: - validate: + test-unit: runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - # - name: Optimize caching - # run: echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" - # shell: cmd - - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version-file: 'go.mod' - cache: false - - # - name: set go cache paths - # id: go-cache-paths - # run: | - # Write-Output "::set-output name=go-build::$(go env GOCACHE)" - # Write-Output "::set-output name=go-mod::$(go env GOMODCACHE)" - # shell: pwsh - - # - name: Cache Go modules - # id: cache-go-mod - # uses: actions/cache@v4 - # with: - # path: | - # ${{ steps.go-cache-paths.outputs.go-build }} - # ${{ steps.go-cache-paths.outputs.go-mod }} - # key: windows-go-${{ hashFiles('**/go.sum') }} - # restore-keys: | - # windows-go- + - name: Setup golang + uses: ./.github/actions/golang - name: Run Windows unit tests run: make test-unit shell: pwsh + test-e2e: + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: Setup golang + uses: ./.github/actions/golang + - name: Build Windows binary and zarf packages uses: ./.github/actions/packages with: From b8bed72779e92032281dbb0d40840456f2a57e68 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Thu, 27 Jun 2024 19:59:20 +0000 Subject: [PATCH 097/132] no cache --- .github/workflows/test-windows.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index bb6398c2a5..38927d4230 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -26,22 +26,16 @@ jobs: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Setup golang - uses: ./.github/actions/golang - - name: Run Windows unit tests run: make test-unit shell: pwsh - test-e2e: + test-e2e-without-cluster: runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - name: Setup golang - uses: ./.github/actions/golang - - name: Build Windows binary and zarf packages uses: ./.github/actions/packages with: From 2b08e4ac959fed92c20a441fe01e2628d20c098c Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 28 Jun 2024 12:47:57 +0000 Subject: [PATCH 098/132] feat: flux OCI support in Zarf Agent --- examples/podinfo-flux/flux-install.yaml | 4195 ----------------- .../{ => git}/podinfo-kustomization.yaml | 4 +- .../{ => git}/podinfo-source.yaml | 2 +- .../helm/podinfo-helmrelease.yaml | 17 + .../podinfo-flux/helm/podinfo-source.yaml | 9 + .../oci/podinfo-kustomization.yaml | 14 + examples/podinfo-flux/oci/podinfo-source.yaml | 11 + examples/podinfo-flux/zarf.yaml | 90 +- go.mod | 25 +- go.sum | 65 +- .../zarf-agent/manifests/clusterrole.yaml | 12 + .../manifests/clusterrolebinding.yaml | 12 + packages/zarf-agent/manifests/webhook.yaml | 86 + packages/zarf-agent/zarf.yaml | 2 + site/src/content/docs/ref/actions.mdx | 2 +- site/src/content/docs/ref/components.mdx | 2 +- site/src/content/docs/ref/init-package.mdx | 16 +- src/config/lang/english.go | 7 +- .../agent/hooks/{flux.go => flux-gitrepo.go} | 0 .../{flux_test.go => flux-gitrepo_test.go} | 0 src/internal/agent/hooks/flux-helmrepo.go | 106 + .../agent/hooks/flux-helmrepo_test.go | 179 + src/internal/agent/hooks/flux-ocirepo.go | 133 + src/internal/agent/hooks/flux-ocirepo_test.go | 208 + src/internal/agent/hooks/utils_test.go | 1 + src/internal/agent/http/server.go | 16 +- src/internal/packager/helm/post-render.go | 7 +- src/pkg/cluster/secrets.go | 48 +- src/pkg/cluster/secrets_test.go | 64 +- src/pkg/cluster/tunnel.go | 18 +- src/pkg/cluster/tunnel_test.go | 10 +- src/pkg/transform/image.go | 4 +- src/pkg/transform/image_test.go | 4 + src/test/e2e/22_git_and_gitops_test.go | 42 +- src/test/external/common.go | 15 +- src/test/external/ext_in_cluster_test.go | 80 +- src/test/external/ext_out_cluster_test.go | 40 +- src/test/external/registries.yaml | 4 + 38 files changed, 1211 insertions(+), 4339 deletions(-) delete mode 100644 examples/podinfo-flux/flux-install.yaml rename examples/podinfo-flux/{ => git}/podinfo-kustomization.yaml (81%) rename examples/podinfo-flux/{ => git}/podinfo-source.yaml (95%) create mode 100644 examples/podinfo-flux/helm/podinfo-helmrelease.yaml create mode 100644 examples/podinfo-flux/helm/podinfo-source.yaml create mode 100644 examples/podinfo-flux/oci/podinfo-kustomization.yaml create mode 100644 examples/podinfo-flux/oci/podinfo-source.yaml create mode 100644 packages/zarf-agent/manifests/clusterrole.yaml create mode 100644 packages/zarf-agent/manifests/clusterrolebinding.yaml rename src/internal/agent/hooks/{flux.go => flux-gitrepo.go} (100%) rename src/internal/agent/hooks/{flux_test.go => flux-gitrepo_test.go} (100%) create mode 100644 src/internal/agent/hooks/flux-helmrepo.go create mode 100644 src/internal/agent/hooks/flux-helmrepo_test.go create mode 100644 src/internal/agent/hooks/flux-ocirepo.go create mode 100644 src/internal/agent/hooks/flux-ocirepo_test.go create mode 100644 src/test/external/registries.yaml diff --git a/examples/podinfo-flux/flux-install.yaml b/examples/podinfo-flux/flux-install.yaml deleted file mode 100644 index 50128a3b4e..0000000000 --- a/examples/podinfo-flux/flux-install.yaml +++ /dev/null @@ -1,4195 +0,0 @@ ---- -# This manifest was generated by flux. DO NOT EDIT. -# Flux Version: v0.33.0 -# Components: source-controller,kustomize-controller -apiVersion: v1 -kind: Namespace -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - pod-security.kubernetes.io/warn: restricted - pod-security.kubernetes.io/warn-version: latest - name: flux-system ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: buckets.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: Bucket - listKind: BucketList - plural: buckets - singular: bucket - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: BucketSpec defines the desired state of an S3 compatible - bucket - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - bucketName: - description: The bucket name. - type: string - endpoint: - description: The bucket endpoint address. - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS S3 HTTP endpoint. - type: boolean - interval: - description: The interval at which to check for bucket updates. - type: string - provider: - default: generic - description: The S3 compatible storage provider name, default ('generic'). - enum: - - generic - - aws - - gcp - type: string - region: - description: The bucket region. - type: string - secretRef: - description: The name of the secret containing authentication credentials - for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for download operations, defaults to 60s. - type: string - required: - - bucketName - - endpoint - - interval - type: object - status: - default: - observedGeneration: -1 - description: BucketStatus defines the observed state of a bucket - properties: - artifact: - description: Artifact represents the output of the last successful - Bucket sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last Bucket sync. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.endpoint - name: Endpoint - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: Bucket is the Schema for the buckets API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: BucketSpec specifies the required configuration to produce - an Artifact for an object storage bucket. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - bucketName: - description: BucketName is the name of the object storage bucket. - type: string - endpoint: - description: Endpoint is the object storage address the BucketName - is located at. - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - insecure: - description: Insecure allows connecting to a non-TLS HTTP Endpoint. - type: boolean - interval: - description: Interval at which to check the Endpoint for updates. - type: string - provider: - default: generic - description: Provider of the object storage bucket. Defaults to 'generic', - which expects an S3 (API) compatible object storage. - enum: - - generic - - aws - - gcp - - azure - type: string - region: - description: Region of the Endpoint where the BucketName is located - in. - type: string - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the Bucket. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this Bucket. - type: boolean - timeout: - default: 60s - description: Timeout for fetch operations, defaults to 60s. - type: string - required: - - bucketName - - endpoint - - interval - type: object - status: - default: - observedGeneration: -1 - description: BucketStatus records the observed state of a Bucket. - properties: - artifact: - description: Artifact represents the last successful Bucket reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the Bucket. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the Bucket object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: gitrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: GitRepository - listKind: GitRepositoryList - plural: gitrepositories - shortNames: - - gitrepo - singular: gitrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GitRepositorySpec defines the desired state of a Git repository. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - gitImplementation: - default: go-git - description: Determines which git client library to use. Defaults - to go-git, valid values are ('go-git', 'libgit2'). - enum: - - go-git - - libgit2 - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - include: - description: Extra git repositories to map into the repository - items: - description: GitRepositoryInclude defines a source with a from and - to path. - properties: - fromPath: - description: The path to copy contents from, defaults to the - root directory. - type: string - repository: - description: Reference to a GitRepository to include. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: The path to copy contents to, defaults to the name - of the source ref. - type: string - required: - - repository - type: object - type: array - interval: - description: The interval at which to check for repository updates. - type: string - recurseSubmodules: - description: When enabled, after the clone is created, initializes - all submodules within, using their default settings. This option - is available only when using the 'go-git' GitImplementation. - type: boolean - ref: - description: The Git reference to checkout and monitor for changes, - defaults to master branch. - properties: - branch: - description: The Git branch to checkout, defaults to master. - type: string - commit: - description: The Git commit SHA to checkout, if specified Tag - filters will be ignored. - type: string - semver: - description: The Git tag semver expression, takes precedence over - Tag. - type: string - tag: - description: The Git tag to checkout, takes precedence over Branch. - type: string - type: object - secretRef: - description: The secret name containing the Git credentials. For HTTPS - repositories the secret must contain username and password fields. - For SSH repositories the secret must contain identity and known_hosts - fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote Git operations like cloning, defaults - to 60s. - type: string - url: - description: The repository URL, can be a HTTP/S or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: Verify OpenPGP signature for the Git commit HEAD points - to. - properties: - mode: - description: Mode describes what git object should be verified, - currently ('head'). - enum: - - head - type: string - secretRef: - description: The secret name containing the public keys of all - trusted Git authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - mode - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus defines the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the output of the last successful - repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - includedArtifacts: - description: IncludedArtifacts represents the included artifacts from - the last successful repository sync. - items: - description: Artifact represents the output of a source synchronisation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last repository sync. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: GitRepository is the Schema for the gitrepositories API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: GitRepositorySpec specifies the required configuration to - produce an Artifact for a Git repository. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - gitImplementation: - default: go-git - description: GitImplementation specifies which Git client library - implementation to use. Defaults to 'go-git', valid values are ('go-git', - 'libgit2'). - enum: - - go-git - - libgit2 - type: string - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - include: - description: Include specifies a list of GitRepository resources which - Artifacts should be included in the Artifact produced for this GitRepository. - items: - description: GitRepositoryInclude specifies a local reference to - a GitRepository which Artifact (sub-)contents must be included, - and where they should be placed. - properties: - fromPath: - description: FromPath specifies the path to copy contents from, - defaults to the root of the Artifact. - type: string - repository: - description: GitRepositoryRef specifies the GitRepository which - Artifact contents must be included. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - toPath: - description: ToPath specifies the path to copy contents to, - defaults to the name of the GitRepositoryRef. - type: string - required: - - repository - type: object - type: array - interval: - description: Interval at which to check the GitRepository for updates. - type: string - recurseSubmodules: - description: RecurseSubmodules enables the initialization of all submodules - within the GitRepository as cloned from the URL, using their default - settings. This option is available only when using the 'go-git' - GitImplementation. - type: boolean - ref: - description: Reference specifies the Git reference to resolve and - monitor for changes, defaults to the 'master' branch. - properties: - branch: - description: "Branch to check out, defaults to 'master' if no - other field is defined. \n When GitRepositorySpec.GitImplementation - is set to 'go-git', a shallow clone of the specified branch - is performed." - type: string - commit: - description: "Commit SHA to check out, takes precedence over all - reference fields. \n When GitRepositorySpec.GitImplementation - is set to 'go-git', this can be combined with Branch to shallow - clone the branch, in which the commit is expected to exist." - type: string - semver: - description: SemVer tag expression to check out, takes precedence - over Tag. - type: string - tag: - description: Tag to check out, takes precedence over Branch. - type: string - type: object - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the GitRepository. For HTTPS repositories the Secret - must contain 'username' and 'password' fields. For SSH repositories - the Secret must contain 'identity' and 'known_hosts' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this GitRepository. - type: boolean - timeout: - default: 60s - description: Timeout for Git operations like cloning, defaults to - 60s. - type: string - url: - description: URL specifies the Git repository URL, it can be an HTTP/S - or SSH address. - pattern: ^(http|https|ssh)://.*$ - type: string - verify: - description: Verification specifies the configuration to verify the - Git commit signature(s). - properties: - mode: - description: Mode specifies what Git object should be verified, - currently ('head'). - enum: - - head - type: string - secretRef: - description: SecretRef specifies the Secret containing the public - keys of trusted Git authors. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - mode - type: object - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: GitRepositoryStatus records the observed state of a Git repository. - properties: - artifact: - description: Artifact represents the last successful GitRepository - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the GitRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - contentConfigChecksum: - description: 'ContentConfigChecksum is a checksum of all the configurations - related to the content of the source artifact: - .spec.ignore - - .spec.recurseSubmodules - .spec.included and the checksum of the - included artifacts observed in .status.observedGeneration version - of the object. This can be used to determine if the content of the - included repository has changed. It has the format of `:`, - for example: `sha256:`.' - type: string - includedArtifacts: - description: IncludedArtifacts contains a list of the last successfully - included Artifacts as instructed by GitRepositorySpec.Include. - items: - description: Artifact represents the output of a Source reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact - file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI - annotations. - type: object - path: - description: Path is the relative file path of the Artifact. - It can be used to locate the file in the root of the Artifact - storage on the local file system of the controller managing - the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the GitRepository object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise GitRepositoryStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: helmcharts.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmChart - listKind: HelmChartList - plural: helmcharts - shortNames: - - hc - singular: helmchart - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmChartSpec defines the desired state of a Helm chart. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - chart: - description: The name or path the Helm chart is available at in the - SourceRef. - type: string - interval: - description: The interval at which to check the Source for updates. - type: string - reconcileStrategy: - default: ChartVersion - description: Determines what enables the creation of a new artifact. - Valid values are ('ChartVersion', 'Revision'). See the documentation - of the values for an explanation on their behavior. Defaults to - ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: The reference to the Source the chart is available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent, valid values are ('HelmRepository', - 'GitRepository', 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - valuesFile: - description: Alternative values file to use as the default chart values, - expected to be a relative path in the SourceRef. Deprecated in favor - of ValuesFiles, for backwards compatibility the file defined here - is merged before the ValuesFiles items. Ignored when omitted. - type: string - valuesFiles: - description: Alternative list of values files to use as the chart - values (values.yaml is not included by default), expected to be - a relative path in the SourceRef. Values files are merged in the - order of this list with the last file overriding the first. Ignored - when omitted. - items: - type: string - type: array - version: - default: '*' - description: The chart version semver expression, ignored for charts - from GitRepository and Bucket sources. Defaults to latest when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus defines the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful - chart sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last chart pulled. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.chart - name: Chart - type: string - - jsonPath: .spec.version - name: Version - type: string - - jsonPath: .spec.sourceRef.kind - name: Source Kind - type: string - - jsonPath: .spec.sourceRef.name - name: Source Name - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmChart is the Schema for the helmcharts API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmChartSpec specifies the desired state of a Helm chart. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - chart: - description: Chart is the name or path the Helm chart is available - at in the SourceRef. - type: string - interval: - description: Interval is the interval at which to check the Source - for updates. - type: string - reconcileStrategy: - default: ChartVersion - description: ReconcileStrategy determines what enables the creation - of a new artifact. Valid values are ('ChartVersion', 'Revision'). - See the documentation of the values for an explanation on their - behavior. Defaults to ChartVersion when omitted. - enum: - - ChartVersion - - Revision - type: string - sourceRef: - description: SourceRef is the reference to the Source the chart is - available at. - properties: - apiVersion: - description: APIVersion of the referent. - type: string - kind: - description: Kind of the referent, valid values are ('HelmRepository', - 'GitRepository', 'Bucket'). - enum: - - HelmRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - required: - - kind - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this source. - type: boolean - valuesFile: - description: ValuesFile is an alternative values file to use as the - default chart values, expected to be a relative path in the SourceRef. - Deprecated in favor of ValuesFiles, for backwards compatibility - the file specified here is merged before the ValuesFiles items. - Ignored when omitted. - type: string - valuesFiles: - description: ValuesFiles is an alternative list of values files to - use as the chart values (values.yaml is not included by default), - expected to be a relative path in the SourceRef. Values files are - merged in the order of this list with the last file overriding the - first. Ignored when omitted. - items: - type: string - type: array - version: - default: '*' - description: Version is the chart version semver expression, ignored - for charts from GitRepository and Bucket sources. Defaults to latest - when omitted. - type: string - required: - - chart - - interval - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: HelmChartStatus records the observed state of the HelmChart. - properties: - artifact: - description: Artifact represents the output of the last successful - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmChart. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedChartName: - description: ObservedChartName is the last observed chart name as - specified by the resolved chart reference. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the HelmChart object. - format: int64 - type: integer - observedSourceArtifactRevision: - description: ObservedSourceArtifactRevision is the last observed Artifact.Revision - of the HelmChartSpec.SourceRef. - type: string - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise BucketStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: helmrepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: HelmRepository - listKind: HelmRepositoryList - plural: helmrepositories - shortNames: - - helmrepo - singular: helmrepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmRepositorySpec defines the reference to a Helm repository. - properties: - accessFrom: - description: AccessFrom defines an Access Control List for allowing - cross-namespace references to this object. - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - interval: - description: The interval at which to check the upstream for updates. - type: string - passCredentials: - description: PassCredentials allows the credentials from the SecretRef - to be passed on to a host that does not match the host as defined - in URL. This may be required if the host of the advertised chart - URLs in the index differ from the defined URL. Enabling this should - be done with caution, as it can potentially result in credentials - getting stolen in a MITM-attack. - type: boolean - secretRef: - description: The name of the secret containing authentication credentials - for the Helm repository. For HTTP/S basic auth the secret must contain - username and password fields. For TLS the secret must contain a - certFile and keyFile, and/or caCert fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout of index downloading, defaults to 60s. - type: string - url: - description: The Helm repository URL, a valid URL contains at least - a protocol and host. - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus defines the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the output of the last successful - repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the artifact. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of this artifact. - format: date-time - type: string - path: - description: Path is the relative file path of this artifact. - type: string - revision: - description: Revision is a human readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm index timestamp, a Helm chart version, etc. - type: string - url: - description: URL is the HTTP address of this artifact. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the last index fetched. - type: string - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: HelmRepository is the Schema for the helmrepositories API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: HelmRepositorySpec specifies the required configuration to - produce an Artifact for a Helm repository index YAML. - properties: - accessFrom: - description: 'AccessFrom specifies an Access Control List for allowing - cross-namespace references to this object. NOTE: Not implemented, - provisional as of https://github.com/fluxcd/flux2/pull/2092' - properties: - namespaceSelectors: - description: NamespaceSelectors is the list of namespace selectors - to which this ACL applies. Items in this list are evaluated - using a logical OR operation. - items: - description: NamespaceSelector selects the namespaces to which - this ACL applies. An empty map of MatchLabels matches all - namespaces in a cluster. - properties: - matchLabels: - additionalProperties: - type: string - description: MatchLabels is a map of {key,value} pairs. - A single {key,value} in the matchLabels map is equivalent - to an element of matchExpressions, whose key field is - "key", the operator is "In", and the values array contains - only "value". The requirements are ANDed. - type: object - type: object - type: array - required: - - namespaceSelectors - type: object - interval: - description: Interval at which to check the URL for updates. - type: string - passCredentials: - description: PassCredentials allows the credentials from the SecretRef - to be passed on to a host that does not match the host as defined - in URL. This may be required if the host of the advertised chart - URLs in the index differ from the defined URL. Enabling this should - be done with caution, as it can potentially result in credentials - getting stolen in a MITM-attack. - type: boolean - provider: - default: generic - description: Provider used for authentication, can be 'aws', 'azure', - 'gcp' or 'generic'. This field is optional, and only taken into - account if the .spec.type field is set to 'oci'. When not specified, - defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - secretRef: - description: SecretRef specifies the Secret containing authentication - credentials for the HelmRepository. For HTTP/S basic auth the secret - must contain 'username' and 'password' fields. For TLS the secret - must contain a 'certFile' and 'keyFile', and/or 'caCert' fields. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - suspend: - description: Suspend tells the controller to suspend the reconciliation - of this HelmRepository. - type: boolean - timeout: - default: 60s - description: Timeout is used for the index fetch operation for an - HTTPS helm repository, and for remote OCI Repository operations - like pulling for an OCI helm repository. Its default value is 60s. - type: string - type: - description: Type of the HelmRepository. When this field is set to "oci", - the URL field value must be prefixed with "oci://". - enum: - - default - - oci - type: string - url: - description: URL of the Helm repository, a valid URL contains at least - a protocol and host. - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: HelmRepositoryStatus records the observed state of the HelmRepository. - properties: - artifact: - description: Artifact represents the last successful HelmRepository - reconciliation. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the HelmRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation of - the HelmRepository object. - format: int64 - type: integer - url: - description: URL is the dynamic fetch link for the latest Artifact. - It is provided on a "best effort" basis, and using the precise HelmRepositoryStatus.Artifact - data is recommended. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: kustomizations.kustomize.toolkit.fluxcd.io -spec: - group: kustomize.toolkit.fluxcd.io - names: - kind: Kustomization - listKind: KustomizationList - plural: kustomizations - shortNames: - - ks - singular: kustomization - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta1 - schema: - openAPIV3Schema: - description: Kustomization is the Schema for the kustomizations API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: KustomizationSpec defines the desired state of a kustomization. - properties: - decryption: - description: Decrypt Kubernetes secrets before applying them on the - cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: The secret name containing the private OpenPGP keys - used for decryption. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to Kustomization resources that must be ready - before this Kustomization can be reconciled. - items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - force: - default: false - description: Force instructs the controller to recreate resources - when patching fails due to an immutable field change. - type: boolean - healthChecks: - description: A list of resources to be included in the health assessment. - items: - description: NamespacedObjectKindReference contains enough information - to locate the typed referenced Kubernetes resource object in any - namespace. - properties: - apiVersion: - description: API version of the referent, if not specified the - Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - type: array - images: - description: Images is a list of (image name, new name, new tag or - digest) for changing image names, tags or digests. This can also - be achieved with a patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag - or digest, which will replace the original name and tag. - properties: - digest: - description: Digest is the value used to replace the original - image tag. If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original - name. - type: string - newTag: - description: NewTag is the value used to replace the original - tag. - type: string - required: - - name - type: object - type: array - interval: - description: The interval at which to reconcile the Kustomization. - type: string - kubeConfig: - description: The KubeConfig for reconciling the Kustomization on a - remote cluster. When specified, KubeConfig takes precedence over - ServiceAccountName. - properties: - secretRef: - description: SecretRef holds the name to a secret that contains - a 'value' key with the kubeconfig file as the value. It must - be in the same namespace as the Kustomization. It is recommended - that the kubeconfig is self-contained, and the secret is regularly - updated if credentials such as a cloud-access-token expire. - Cloud specific `cmd-path` auth helpers will not function without - adding binaries and credentials to the Pod that is responsible - for reconciling the Kustomization. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - type: object - patches: - description: Strategic merge and JSON patches, defined as inline YAML - objects, capable of targeting objects based on kind, label and annotation - selectors. - items: - description: Patch contains an inline StrategicMerge or JSON6902 - patch, and the target the patch should be applied to. - properties: - patch: - description: Patch contains an inline StrategicMerge patch or - an inline JSON6902 patch with an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document - should be applied to. - properties: - annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable of - unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that follows the - label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - type: object - type: array - patchesJson6902: - description: JSON 6902 patches, defined as inline YAML objects. - items: - description: JSON6902Patch contains a JSON6902 patch and the target - the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with - an array of operation objects. - items: - description: JSON6902 is a JSON6902 operation object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 - properties: - from: - description: From contains a JSON-pointer value that references - a location within the target document where the operation - is performed. The meaning of the value depends on the - value of Op, and is NOT taken into account by all operations. - type: string - op: - description: Op indicates the operation to perform. Its - value MUST be one of "add", "remove", "replace", "move", - "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - description: Path contains the JSON-pointer value that - references a location within the target document where - the operation is performed. The meaning of the value - depends on the value of Op. - type: string - value: - description: Value contains a valid JSON structure. The - meaning of the value depends on the value of Op, and - is NOT taken into account by all operations. - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that the patch document - should be applied to. - properties: - annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable of - unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that follows the - label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: Strategic merge patches, defined as inline YAML objects. - items: - x-kubernetes-preserve-unknown-fields: true - type: array - path: - description: Path to the directory containing the kustomization.yaml - file, or the set of plain YAMLs a kustomization.yaml should be generated - for. Defaults to 'None', which translates to the root path of the - SourceRef. - type: string - postBuild: - description: PostBuild describes which actions to perform on the YAML - manifest generated by building the kustomize overlay. - properties: - substitute: - additionalProperties: - type: string - description: Substitute holds a map of key/value pairs. The variables - defined in your YAML manifests that match any of the keys defined - in the map will be substituted with the set value. Includes - support for bash string replacement functions e.g. ${var:=default}, - ${var:position} and ${var/substring/replacement}. - type: object - substituteFrom: - description: SubstituteFrom holds references to ConfigMaps and - Secrets containing the variables and their values to be substituted - in the YAML manifests. The ConfigMap and the Secret data keys - represent the var names and they must match the vars declared - in the manifests for the substitution to happen. - items: - description: SubstituteReference contains a reference to a resource - containing the variables name and value. - properties: - kind: - description: Kind of the values referent, valid values are - ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: Name of the values referent. Should reside - in the same namespace as the referring resource. - maxLength: 253 - minLength: 1 - type: string - required: - - kind - - name - type: object - type: array - type: object - prune: - description: Prune enables garbage collection. - type: boolean - retryInterval: - description: The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KustomizationSpec.Interval - value to retry failures. - type: string - serviceAccountName: - description: The name of the Kubernetes service account to impersonate - when reconciling this Kustomization. - type: string - sourceRef: - description: Reference of the source where the kustomization file - is. - properties: - apiVersion: - description: API version of the referent - type: string - kind: - description: Kind of the referent - enum: - - GitRepository - - Bucket - type: string - name: - description: Name of the referent - type: string - namespace: - description: Namespace of the referent, defaults to the Kustomization - namespace - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent - kustomize executions, it does not apply to already started executions. - Defaults to false. - type: boolean - targetNamespace: - description: TargetNamespace sets or overrides the namespace in the - kustomization.yaml file. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: Timeout for validation, apply and health checking operations. - Defaults to 'Interval' duration. - type: string - validation: - description: Validate the Kubernetes objects before applying them - on the cluster. The validation strategy can be 'client' (local dry-run), - 'server' (APIServer dry-run) or 'none'. When 'Force' is 'true', - validation will fallback to 'client' if set to 'server' because - server-side validation is not supported in this scenario. - enum: - - none - - client - - server - type: string - required: - - interval - - prune - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: KustomizationStatus defines the observed state of a kustomization. - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastAppliedRevision: - description: The last successfully applied revision. The revision - format for Git sources is /. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation - attempt. - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - snapshot: - description: The last successfully applied revision metadata. - properties: - checksum: - description: The manifests sha1 checksum. - type: string - entries: - description: A list of Kubernetes kinds grouped by namespace. - items: - description: Snapshot holds the metadata of namespaced Kubernetes - objects - properties: - kinds: - additionalProperties: - type: string - description: The list of Kubernetes kinds. - type: object - namespace: - description: The namespace of this entry. - type: string - required: - - kinds - type: object - type: array - required: - - checksum - - entries - type: object - type: object - type: object - served: true - storage: false - subresources: - status: {} - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - name: v1beta2 - schema: - openAPIV3Schema: - description: Kustomization is the Schema for the kustomizations API. - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: KustomizationSpec defines the configuration to calculate - the desired state from a Source using Kustomize. - properties: - decryption: - description: Decrypt Kubernetes secrets before applying them on the - cluster. - properties: - provider: - description: Provider is the name of the decryption engine. - enum: - - sops - type: string - secretRef: - description: The secret name containing the private OpenPGP keys - used for decryption. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - required: - - provider - type: object - dependsOn: - description: DependsOn may contain a meta.NamespacedObjectReference - slice with references to Kustomization resources that must be ready - before this Kustomization can be reconciled. - items: - description: NamespacedObjectReference contains enough information - to locate the referenced Kubernetes resource object in any namespace. - properties: - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - name - type: object - type: array - force: - default: false - description: Force instructs the controller to recreate resources - when patching fails due to an immutable field change. - type: boolean - healthChecks: - description: A list of resources to be included in the health assessment. - items: - description: NamespacedObjectKindReference contains enough information - to locate the typed referenced Kubernetes resource object in any - namespace. - properties: - apiVersion: - description: API version of the referent, if not specified the - Kubernetes preferred version will be used. - type: string - kind: - description: Kind of the referent. - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, when not specified it - acts as LocalObjectReference. - type: string - required: - - kind - - name - type: object - type: array - images: - description: Images is a list of (image name, new name, new tag or - digest) for changing image names, tags or digests. This can also - be achieved with a patch, but this operator is simpler to specify. - items: - description: Image contains an image name, a new name, a new tag - or digest, which will replace the original name and tag. - properties: - digest: - description: Digest is the value used to replace the original - image tag. If digest is present NewTag value is ignored. - type: string - name: - description: Name is a tag-less image name. - type: string - newName: - description: NewName is the value used to replace the original - name. - type: string - newTag: - description: NewTag is the value used to replace the original - tag. - type: string - required: - - name - type: object - type: array - interval: - description: The interval at which to reconcile the Kustomization. - type: string - kubeConfig: - description: The KubeConfig for reconciling the Kustomization on a - remote cluster. When used in combination with KustomizationSpec.ServiceAccountName, - forces the controller to act on behalf of that Service Account at - the target cluster. If the --default-service-account flag is set, - its value will be used as a controller level fallback for when KustomizationSpec.ServiceAccountName - is empty. - properties: - secretRef: - description: SecretRef holds the name of a secret that contains - a key with the kubeconfig file as the value. If no key is set, - the key will default to 'value'. The secret must be in the same - namespace as the Kustomization. It is recommended that the kubeconfig - is self-contained, and the secret is regularly updated if credentials - such as a cloud-access-token expire. Cloud specific `cmd-path` - auth helpers will not function without adding binaries and credentials - to the Pod that is responsible for reconciling the Kustomization. - properties: - key: - description: Key in the Secret, when not specified an implementation-specific - default key is used. - type: string - name: - description: Name of the Secret. - type: string - required: - - name - type: object - type: object - patches: - description: Strategic merge and JSON patches, defined as inline YAML - objects, capable of targeting objects based on kind, label and annotation - selectors. - items: - description: Patch contains an inline StrategicMerge or JSON6902 - patch, and the target the patch should be applied to. - properties: - patch: - description: Patch contains an inline StrategicMerge patch or - an inline JSON6902 patch with an array of operation objects. - type: string - target: - description: Target points to the resources that the patch document - should be applied to. - properties: - annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable of - unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that follows the - label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - type: object - type: array - patchesJson6902: - description: 'JSON 6902 patches, defined as inline YAML objects. Deprecated: - Use Patches instead.' - items: - description: JSON6902Patch contains a JSON6902 patch and the target - the patch should be applied to. - properties: - patch: - description: Patch contains the JSON6902 patch document with - an array of operation objects. - items: - description: JSON6902 is a JSON6902 operation object. https://datatracker.ietf.org/doc/html/rfc6902#section-4 - properties: - from: - description: From contains a JSON-pointer value that references - a location within the target document where the operation - is performed. The meaning of the value depends on the - value of Op, and is NOT taken into account by all operations. - type: string - op: - description: Op indicates the operation to perform. Its - value MUST be one of "add", "remove", "replace", "move", - "copy", or "test". https://datatracker.ietf.org/doc/html/rfc6902#section-4 - enum: - - test - - remove - - add - - replace - - move - - copy - type: string - path: - description: Path contains the JSON-pointer value that - references a location within the target document where - the operation is performed. The meaning of the value - depends on the value of Op. - type: string - value: - description: Value contains a valid JSON structure. The - meaning of the value depends on the value of Op, and - is NOT taken into account by all operations. - x-kubernetes-preserve-unknown-fields: true - required: - - op - - path - type: object - type: array - target: - description: Target points to the resources that the patch document - should be applied to. - properties: - annotationSelector: - description: AnnotationSelector is a string that follows - the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource annotations. - type: string - group: - description: Group is the API group to select resources - from. Together with Version and Kind it is capable of - unambiguously identifying and/or selecting resources. - https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - kind: - description: Kind of the API Group to select resources from. - Together with Group and Version it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - labelSelector: - description: LabelSelector is a string that follows the - label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api - It matches with the resource labels. - type: string - name: - description: Name to match resources with. - type: string - namespace: - description: Namespace to select resources from. - type: string - version: - description: Version of the API Group to select resources - from. Together with Group and Kind it is capable of unambiguously - identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md - type: string - type: object - required: - - patch - - target - type: object - type: array - patchesStrategicMerge: - description: 'Strategic merge patches, defined as inline YAML objects. - Deprecated: Use Patches instead.' - items: - x-kubernetes-preserve-unknown-fields: true - type: array - path: - description: Path to the directory containing the kustomization.yaml - file, or the set of plain YAMLs a kustomization.yaml should be generated - for. Defaults to 'None', which translates to the root path of the - SourceRef. - type: string - postBuild: - description: PostBuild describes which actions to perform on the YAML - manifest generated by building the kustomize overlay. - properties: - substitute: - additionalProperties: - type: string - description: Substitute holds a map of key/value pairs. The variables - defined in your YAML manifests that match any of the keys defined - in the map will be substituted with the set value. Includes - support for bash string replacement functions e.g. ${var:=default}, - ${var:position} and ${var/substring/replacement}. - type: object - substituteFrom: - description: SubstituteFrom holds references to ConfigMaps and - Secrets containing the variables and their values to be substituted - in the YAML manifests. The ConfigMap and the Secret data keys - represent the var names and they must match the vars declared - in the manifests for the substitution to happen. - items: - description: SubstituteReference contains a reference to a resource - containing the variables name and value. - properties: - kind: - description: Kind of the values referent, valid values are - ('Secret', 'ConfigMap'). - enum: - - Secret - - ConfigMap - type: string - name: - description: Name of the values referent. Should reside - in the same namespace as the referring resource. - maxLength: 253 - minLength: 1 - type: string - optional: - default: false - description: Optional indicates whether the referenced resource - must exist, or whether to tolerate its absence. If true - and the referenced resource is absent, proceed as if the - resource was present but empty, without any variables - defined. - type: boolean - required: - - kind - - name - type: object - type: array - type: object - prune: - description: Prune enables garbage collection. - type: boolean - retryInterval: - description: The interval at which to retry a previously failed reconciliation. - When not specified, the controller uses the KustomizationSpec.Interval - value to retry failures. - type: string - serviceAccountName: - description: The name of the Kubernetes service account to impersonate - when reconciling this Kustomization. - type: string - sourceRef: - description: Reference of the source where the kustomization file - is. - properties: - apiVersion: - description: API version of the referent. - type: string - kind: - description: Kind of the referent. - enum: - - OCIRepository - - GitRepository - - Bucket - type: string - name: - description: Name of the referent. - type: string - namespace: - description: Namespace of the referent, defaults to the namespace - of the Kubernetes resource object that contains the reference. - type: string - required: - - kind - - name - type: object - suspend: - description: This flag tells the controller to suspend subsequent - kustomize executions, it does not apply to already started executions. - Defaults to false. - type: boolean - targetNamespace: - description: TargetNamespace sets or overrides the namespace in the - kustomization.yaml file. - maxLength: 63 - minLength: 1 - type: string - timeout: - description: Timeout for validation, apply and health checking operations. - Defaults to 'Interval' duration. - type: string - validation: - description: 'Deprecated: Not used in v1beta2.' - enum: - - none - - client - - server - type: string - wait: - description: Wait instructs the controller to check the health of - all the reconciled resources. When enabled, the HealthChecks are - ignored. Defaults to false. - type: boolean - required: - - interval - - prune - - sourceRef - type: object - status: - default: - observedGeneration: -1 - description: KustomizationStatus defines the observed state of a kustomization. - properties: - conditions: - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - inventory: - description: Inventory contains the list of Kubernetes resource object - references that have been successfully applied. - properties: - entries: - description: Entries of Kubernetes resource object references. - items: - description: ResourceRef contains the information necessary - to locate a resource within a cluster. - properties: - id: - description: ID is the string representation of the Kubernetes - resource object's metadata, in the format '___'. - type: string - v: - description: Version is the API version of the Kubernetes - resource object's kind. - type: string - required: - - id - - v - type: object - type: array - required: - - entries - type: object - lastAppliedRevision: - description: The last successfully applied revision. The revision - format for Git sources is /. - type: string - lastAttemptedRevision: - description: LastAttemptedRevision is the revision of the last reconciliation - attempt. - type: string - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last reconciled generation. - format: int64 - type: integer - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: ocirepositories.source.toolkit.fluxcd.io -spec: - group: source.toolkit.fluxcd.io - names: - kind: OCIRepository - listKind: OCIRepositoryList - plural: ocirepositories - shortNames: - - ocirepo - singular: ocirepository - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .spec.url - name: URL - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - type: string - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1beta2 - schema: - openAPIV3Schema: - description: OCIRepository is the Schema for the ocirepositories API - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: OCIRepositorySpec defines the desired state of OCIRepository - properties: - certSecretRef: - description: "CertSecretRef can be given the name of a secret containing - either or both of \n - a PEM-encoded client certificate (`certFile`) - and private key (`keyFile`); - a PEM-encoded CA certificate (`caFile`) - \n and whichever are supplied, will be used for connecting to the - \ registry. The client cert and key are useful if you are authenticating - with a certificate; the CA cert is useful if you are using a self-signed - server certificate." - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - ignore: - description: Ignore overrides the set of excluded patterns in the - .sourceignore format (which is the same as .gitignore). If not provided, - a default will be used, consult the documentation for your version - to find out what those are. - type: string - interval: - description: The interval at which to check for image updates. - type: string - layerSelector: - description: LayerSelector specifies which layer should be extracted - from the OCI artifact. When not specified, the first layer found - in the artifact is selected. - properties: - mediaType: - description: MediaType specifies the OCI media type of the layer - which should be extracted from the OCI Artifact. The first layer - matching this type is selected. - type: string - type: object - provider: - default: generic - description: The provider used for authentication, can be 'aws', 'azure', - 'gcp' or 'generic'. When not specified, defaults to 'generic'. - enum: - - generic - - aws - - azure - - gcp - type: string - ref: - description: The OCI reference to pull and monitor for changes, defaults - to the latest tag. - properties: - digest: - description: Digest is the image digest to pull, takes precedence - over SemVer. The value should be in the format 'sha256:'. - type: string - semver: - description: SemVer is the range of tags to pull selecting the - latest within the range, takes precedence over Tag. - type: string - tag: - description: Tag is the image tag to pull, defaults to latest. - type: string - type: object - secretRef: - description: SecretRef contains the secret name containing the registry - login credentials to resolve image metadata. The secret must be - of type kubernetes.io/dockerconfigjson. - properties: - name: - description: Name of the referent. - type: string - required: - - name - type: object - serviceAccountName: - description: 'ServiceAccountName is the name of the Kubernetes ServiceAccount - used to authenticate the image pull if the service account has attached - pull secrets. For more information: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#add-imagepullsecrets-to-a-service-account' - type: string - suspend: - description: This flag tells the controller to suspend the reconciliation - of this source. - type: boolean - timeout: - default: 60s - description: The timeout for remote OCI Repository operations like - pulling, defaults to 60s. - type: string - url: - description: URL is a reference to an OCI artifact repository hosted - on a remote container registry. - pattern: ^oci://.*$ - type: string - required: - - interval - - url - type: object - status: - default: - observedGeneration: -1 - description: OCIRepositoryStatus defines the observed state of OCIRepository - properties: - artifact: - description: Artifact represents the output of the last successful - OCI Repository sync. - properties: - checksum: - description: Checksum is the SHA256 checksum of the Artifact file. - type: string - lastUpdateTime: - description: LastUpdateTime is the timestamp corresponding to - the last update of the Artifact. - format: date-time - type: string - metadata: - additionalProperties: - type: string - description: Metadata holds upstream information such as OCI annotations. - type: object - path: - description: Path is the relative file path of the Artifact. It - can be used to locate the file in the root of the Artifact storage - on the local file system of the controller managing the Source. - type: string - revision: - description: Revision is a human-readable identifier traceable - in the origin source system. It can be a Git commit SHA, Git - tag, a Helm chart version, etc. - type: string - size: - description: Size is the number of bytes in the file. - format: int64 - type: integer - url: - description: URL is the HTTP address of the Artifact as exposed - by the controller managing the Source. It can be used to retrieve - the Artifact for consumption, e.g. by another controller applying - the Artifact contents. - type: string - required: - - path - - url - type: object - conditions: - description: Conditions holds the conditions for the OCIRepository. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - \n \ttype FooStatus struct{ \t // Represents the observations - of a foo's current state. \t // Known .status.conditions.type - are: \"Available\", \"Progressing\", and \"Degraded\" \t // - +patchMergeKey=type \t // +patchStrategy=merge \t // +listType=map - \t // +listMapKey=type \t Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n \t // other fields - \t}" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - lastHandledReconcileAt: - description: LastHandledReconcileAt holds the value of the most recent - reconcile request value, so a change of the annotation value can - be detected. - type: string - observedGeneration: - description: ObservedGeneration is the last observed generation. - format: int64 - type: integer - url: - description: URL is the download link for the artifact output of the - last OCI Repository sync. - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: kustomize-controller - namespace: flux-system ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: source-controller - namespace: flux-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: crd-controller-flux-system -rules: -- apiGroups: - - source.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - kustomize.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - helm.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - notification.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - image.toolkit.fluxcd.io - resources: - - '*' - verbs: - - '*' -- apiGroups: - - "" - resources: - - namespaces - - secrets - - configmaps - - serviceaccounts - verbs: - - get - - list - - watch -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - configmaps/status - verbs: - - get - - update - - patch -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: cluster-reconciler-flux-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: cluster-admin -subjects: -- kind: ServiceAccount - name: kustomize-controller - namespace: flux-system -- kind: ServiceAccount - name: helm-controller - namespace: flux-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: crd-controller-flux-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: crd-controller-flux-system -subjects: -- kind: ServiceAccount - name: kustomize-controller - namespace: flux-system -- kind: ServiceAccount - name: helm-controller - namespace: flux-system -- kind: ServiceAccount - name: source-controller - namespace: flux-system -- kind: ServiceAccount - name: notification-controller - namespace: flux-system -- kind: ServiceAccount - name: image-reflector-controller - namespace: flux-system -- kind: ServiceAccount - name: image-automation-controller - namespace: flux-system ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - control-plane: controller - name: source-controller - namespace: flux-system -spec: - ports: - - name: http - port: 80 - protocol: TCP - targetPort: http - selector: - app: source-controller - type: ClusterIP ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - control-plane: controller - name: kustomize-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: kustomize-controller - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: kustomize-controller - spec: - containers: - - args: - - --events-addr= - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/kustomize-controller:v0.27.1 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 8080 - name: http-prom - protocol: TCP - - containerPort: 9440 - name: healthz - protocol: TCP - readinessProbe: - httpGet: - path: /readyz - port: healthz - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 100m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /tmp - name: temp - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: kustomize-controller - terminationGracePeriodSeconds: 60 - volumes: - - emptyDir: {} - name: temp ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - control-plane: controller - name: source-controller - namespace: flux-system -spec: - replicas: 1 - selector: - matchLabels: - app: source-controller - strategy: - type: Recreate - template: - metadata: - annotations: - prometheus.io/port: "8080" - prometheus.io/scrape: "true" - labels: - app: source-controller - spec: - containers: - - args: - - --events-addr= - - --watch-all-namespaces=true - - --log-level=info - - --log-encoding=json - - --enable-leader-election - - --storage-path=/data - - --storage-adv-addr=source-controller.$(RUNTIME_NAMESPACE).svc.cluster.local. - env: - - name: RUNTIME_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - image: ghcr.io/fluxcd/source-controller:v0.28.0 - imagePullPolicy: IfNotPresent - livenessProbe: - httpGet: - path: /healthz - port: healthz - name: manager - ports: - - containerPort: 9090 - name: http - protocol: TCP - - containerPort: 8080 - name: http-prom - protocol: TCP - - containerPort: 9440 - name: healthz - protocol: TCP - readinessProbe: - httpGet: - path: / - port: http - resources: - limits: - cpu: 1000m - memory: 1Gi - requests: - cpu: 50m - memory: 64Mi - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: - - ALL - readOnlyRootFilesystem: true - runAsNonRoot: true - seccompProfile: - type: RuntimeDefault - volumeMounts: - - mountPath: /data - name: data - - mountPath: /tmp - name: tmp - nodeSelector: - kubernetes.io/os: linux - securityContext: - fsGroup: 1337 - serviceAccountName: source-controller - terminationGracePeriodSeconds: 10 - volumes: - - emptyDir: {} - name: data - - emptyDir: {} - name: tmp ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: allow-egress - namespace: flux-system -spec: - egress: - - {} - ingress: - - from: - - podSelector: {} - podSelector: {} - policyTypes: - - Ingress - - Egress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: allow-scraping - namespace: flux-system -spec: - ingress: - - from: - - namespaceSelector: {} - ports: - - port: 8080 - protocol: TCP - podSelector: {} - policyTypes: - - Ingress ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - labels: - app.kubernetes.io/instance: flux-system - app.kubernetes.io/part-of: flux - app.kubernetes.io/version: v0.33.0 - name: allow-webhooks - namespace: flux-system -spec: - ingress: - - from: - - namespaceSelector: {} - podSelector: - matchLabels: - app: notification-controller - policyTypes: - - Ingress diff --git a/examples/podinfo-flux/podinfo-kustomization.yaml b/examples/podinfo-flux/git/podinfo-kustomization.yaml similarity index 81% rename from examples/podinfo-flux/podinfo-kustomization.yaml rename to examples/podinfo-flux/git/podinfo-kustomization.yaml index db498ac16d..bc8d5d50ef 100644 --- a/examples/podinfo-flux/podinfo-kustomization.yaml +++ b/examples/podinfo-flux/git/podinfo-kustomization.yaml @@ -2,7 +2,7 @@ apiVersion: kustomize.toolkit.fluxcd.io/v1beta2 kind: Kustomization metadata: - name: podinfo + name: podinfo-git namespace: flux-system spec: interval: 5m0s @@ -11,4 +11,4 @@ spec: sourceRef: kind: GitRepository name: podinfo - targetNamespace: podinfo + targetNamespace: podinfo-git diff --git a/examples/podinfo-flux/podinfo-source.yaml b/examples/podinfo-flux/git/podinfo-source.yaml similarity index 95% rename from examples/podinfo-flux/podinfo-source.yaml rename to examples/podinfo-flux/git/podinfo-source.yaml index e42f85dce2..3b6351955c 100644 --- a/examples/podinfo-flux/podinfo-source.yaml +++ b/examples/podinfo-flux/git/podinfo-source.yaml @@ -7,6 +7,6 @@ metadata: spec: interval: 30s ref: - tag: 6.3.3 + tag: 6.4.0 # Currently the Zarf Agent can only mutate urls that are proper URIs (i.e. scheme://host/repo) url: https://github.com/stefanprodan/podinfo.git diff --git a/examples/podinfo-flux/helm/podinfo-helmrelease.yaml b/examples/podinfo-flux/helm/podinfo-helmrelease.yaml new file mode 100644 index 0000000000..f8cfaa9a95 --- /dev/null +++ b/examples/podinfo-flux/helm/podinfo-helmrelease.yaml @@ -0,0 +1,17 @@ +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 5m0s + releaseName: podinfo + chart: + spec: + chart: podinfo + version: '6.4.0' + sourceRef: + kind: HelmRepository + name: podinfo + interval: 5m0s + targetNamespace: podinfo-helm diff --git a/examples/podinfo-flux/helm/podinfo-source.yaml b/examples/podinfo-flux/helm/podinfo-source.yaml new file mode 100644 index 0000000000..9c6d62e005 --- /dev/null +++ b/examples/podinfo-flux/helm/podinfo-source.yaml @@ -0,0 +1,9 @@ +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: podinfo + namespace: flux-system +spec: + type: oci + interval: 30s + url: oci://ghcr.io/stefanprodan/charts diff --git a/examples/podinfo-flux/oci/podinfo-kustomization.yaml b/examples/podinfo-flux/oci/podinfo-kustomization.yaml new file mode 100644 index 0000000000..57f290e7b6 --- /dev/null +++ b/examples/podinfo-flux/oci/podinfo-kustomization.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: podinfo-oci + namespace: flux-system +spec: + interval: 5m0s + path: ./ + prune: true + sourceRef: + kind: OCIRepository + name: podinfo + targetNamespace: podinfo-oci diff --git a/examples/podinfo-flux/oci/podinfo-source.yaml b/examples/podinfo-flux/oci/podinfo-source.yaml new file mode 100644 index 0000000000..180659f63f --- /dev/null +++ b/examples/podinfo-flux/oci/podinfo-source.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: podinfo + namespace: flux-system +spec: + interval: 30s + url: oci://ghcr.io/stefanprodan/manifests/podinfo + ref: + tag: 6.4.0 diff --git a/examples/podinfo-flux/zarf.yaml b/examples/podinfo-flux/zarf.yaml index b06283e46e..4ab41b360e 100644 --- a/examples/podinfo-flux/zarf.yaml +++ b/examples/podinfo-flux/zarf.yaml @@ -8,27 +8,79 @@ components: description: Installs the flux CRDs / controllers to use flux-based deployments in the cluster required: true manifests: - - name: flux-crds + - name: flux-install namespace: flux files: - - flux-install.yaml + - https://github.com/fluxcd/flux2/releases/download/v2.3.0/install.yaml images: - - ghcr.io/fluxcd/kustomize-controller:v0.27.1 - - ghcr.io/fluxcd/source-controller:v0.28.0 + - ghcr.io/fluxcd/helm-controller:v1.0.1 + - ghcr.io/fluxcd/image-automation-controller:v0.38.0 + - ghcr.io/fluxcd/image-reflector-controller:v0.32.0 + - ghcr.io/fluxcd/kustomize-controller:v1.3.0 + - ghcr.io/fluxcd/notification-controller:v1.3.0 + - ghcr.io/fluxcd/source-controller:v1.3.0 - - name: podinfo-via-flux - description: Example deployment via flux using the famous podinfo example + - name: podinfo-via-flux-git + description: Example deployment via flux (git) using the famous podinfo example required: true manifests: - - name: podinfo-via-flux - namespace: podinfo + - name: podinfo + namespace: podinfo-git files: - - podinfo-source.yaml - - podinfo-kustomization.yaml + - git/podinfo-source.yaml + - git/podinfo-kustomization.yaml repos: - https://github.com/stefanprodan/podinfo.git images: - - ghcr.io/stefanprodan/podinfo:6.3.3 + - ghcr.io/stefanprodan/podinfo:6.4.0 + actions: + onDeploy: + after: + - description: Podinfo pods to be ready via wait action + wait: + cluster: + kind: pod + name: app=podinfo + namespace: podinfo-git + condition: ready + + - name: podinfo-via-flux-helm + description: Example deployment via flux (helm oci) using the famous podinfo example + required: true + manifests: + - name: podinfo + namespace: podinfo-helm + files: + - helm/podinfo-source.yaml + - helm/podinfo-helmrelease.yaml + images: + - ghcr.io/stefanprodan/podinfo:6.4.0 + # Note: this is a helm OCI artifact rather than a container image + - ghcr.io/stefanprodan/charts/podinfo:6.4.0 + actions: + onDeploy: + after: + - description: Podinfo pods to be ready via wait action + wait: + cluster: + kind: pod + name: app.kubernetes.io/name=podinfo + namespace: podinfo-helm + condition: ready + + - name: podinfo-via-flux-oci + description: Example deployment via flux (native oci) using the famous podinfo example + required: true + manifests: + - name: podinfo + namespace: podinfo-oci + files: + - oci/podinfo-source.yaml + - oci/podinfo-kustomization.yaml + images: + - ghcr.io/stefanprodan/podinfo:6.4.0 + # Note: this is a flux kustomize OCI artifact rather than a container image + - ghcr.io/stefanprodan/manifests/podinfo:6.4.0 actions: onDeploy: after: @@ -38,14 +90,24 @@ components: cluster: kind: pod name: app=podinfo - namespace: podinfo + namespace: podinfo-oci condition: ready # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples x-mdx: | - This example demonstrates how to use flux with Zarf to deploy the `stefanprodan/podinfo` app using GitOps. + This example demonstrates how to use Flux with Zarf to deploy the `stefanprodan/podinfo` app using GitRepositories, HelmRepositories, and OCIRepositories. - It uses a vanilla configuration of flux with upstream containers. + It uses a vanilla configuration of Flux with upstream containers. To learn more about how Zarf handles `git` repositories, see the [Git Repositories section](/ref/components/#git-repositories) of the package components documentation. + + :::caution + + Only `type: oci` HelmRepositories are supported by the Zarf Agent. The `type` key requires a HelmRepository CRD version greater than v1beta1. + + The Zarf agent will only automatically add the `insecure` key if the internal registry is used. If you are using a http registry outside of the cluster you will need to manually add this key. + + Due to an upstream bug, HelmRepositories with an insecure registry must use IP address instead of a hostname. This is not an issue with the internal Zarf registry, which is always an IP address, but will cause Flux HelmRepositories to break if Zarf is using an external http registry with a hostname. + + ::: diff --git a/go.mod b/go.mod index 30abd8b0ca..6195416cbf 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f github.com/anchore/stereoscope v0.0.1 github.com/anchore/syft v0.100.0 + github.com/defenseunicorns/pkg/helpers v1.1.3 github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 github.com/defenseunicorns/pkg/kubernetes v0.0.1 github.com/defenseunicorns/pkg/oci v1.0.1 @@ -20,8 +21,8 @@ require ( github.com/fairwindsops/pluto/v5 v5.18.4 github.com/fatih/color v1.16.0 github.com/fluxcd/helm-controller/api v0.37.4 - github.com/fluxcd/pkg/apis/meta v1.3.0 - github.com/fluxcd/source-controller/api v1.2.4 + github.com/fluxcd/pkg/apis/meta v1.5.0 + github.com/fluxcd/source-controller/api v1.3.0 github.com/go-git/go-git/v5 v5.11.0 github.com/goccy/go-yaml v1.11.3 github.com/gofrs/flock v0.8.1 @@ -49,10 +50,10 @@ require ( golang.org/x/sync v0.7.0 golang.org/x/term v0.21.0 helm.sh/helm/v3 v3.14.2 - k8s.io/api v0.29.1 - k8s.io/apimachinery v0.29.1 - k8s.io/client-go v0.29.1 - k8s.io/component-base v0.29.1 + k8s.io/api v0.30.0 + k8s.io/apimachinery v0.30.0 + k8s.io/client-go v0.30.0 + k8s.io/component-base v0.30.0 k8s.io/klog/v2 v2.120.1 k8s.io/kubectl v0.29.1 oras.land/oras-go/v2 v2.5.0 @@ -222,13 +223,13 @@ require ( github.com/emicklei/proto v1.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/felixge/fgprof v0.9.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fluxcd/pkg/apis/acl v0.1.0 // indirect + github.com/fluxcd/pkg/apis/acl v0.3.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.3.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect @@ -500,11 +501,11 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/gorm v1.25.5 // indirect - k8s.io/apiextensions-apiserver v0.29.0 // indirect - k8s.io/apiserver v0.29.0 // indirect + k8s.io/apiextensions-apiserver v0.30.0 // indirect + k8s.io/apiserver v0.30.0 // indirect k8s.io/cli-runtime v0.29.1 // indirect k8s.io/component-helpers v0.29.1 // indirect - k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/metrics v0.29.1 // indirect k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect modernc.org/libc v1.29.0 // indirect @@ -512,7 +513,7 @@ require ( modernc.org/memory v1.7.2 // indirect modernc.org/sqlite v1.28.0 // indirect oras.land/oras-go v1.2.4 // indirect - sigs.k8s.io/controller-runtime v0.16.3 // indirect + sigs.k8s.io/controller-runtime v0.18.1 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/kustomize/v5 v5.0.4-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/release-utils v0.7.7 // indirect diff --git a/go.sum b/go.sum index 7cc3787141..2cdb4bd07f 100644 --- a/go.sum +++ b/go.sum @@ -597,6 +597,8 @@ github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gwevOZ0fxT2nzM9hrtdPbsiOHjFqDRIYMzJHba3/G6Q= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= +github.com/defenseunicorns/pkg/helpers v1.1.3 h1:EVVuniq02qfAouR//AT0eoCngLWfFORj8H6+pI8M7uo= +github.com/defenseunicorns/pkg/helpers v1.1.3/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= github.com/defenseunicorns/pkg/kubernetes v0.0.1 h1:HNQBV6XXFvlDvFdOCCWam0/LCgq67M+ggQKiRIoM2vU= @@ -684,8 +686,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= +github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/facebookincubator/flog v0.0.0-20190930132826-d2511d0ce33c/go.mod h1:QGzNH9ujQ2ZUr/CjDGZGWeDAVStrWNjHeEcjJL96Nuk= @@ -708,14 +710,14 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fluxcd/helm-controller/api v0.37.4 h1:rkBMqYXexyf1s5BS8QpxGi691DsCi+yugIFCM5fNKLU= github.com/fluxcd/helm-controller/api v0.37.4/go.mod h1:KFdP5Lbrc4Vv+Jt4xRj6UUo3qiwdBqBPl1xiiAnBe9c= -github.com/fluxcd/pkg/apis/acl v0.1.0 h1:EoAl377hDQYL3WqanWCdifauXqXbMyFuK82NnX6pH4Q= -github.com/fluxcd/pkg/apis/acl v0.1.0/go.mod h1:zfEZzz169Oap034EsDhmCAGgnWlcWmIObZjYMusoXS8= +github.com/fluxcd/pkg/apis/acl v0.3.0 h1:UOrKkBTOJK+OlZX7n8rWt2rdBmDCoTK+f5TY2LcZi8A= +github.com/fluxcd/pkg/apis/acl v0.3.0/go.mod h1:WVF9XjSMVBZuU+HTTiSebGAWMgM7IYexFLyVWbK9bNY= github.com/fluxcd/pkg/apis/kustomize v1.3.0 h1:qvB46CfaOWcL1SyR2RiVWN/j7/035D0OtB1ltLN7rgI= github.com/fluxcd/pkg/apis/kustomize v1.3.0/go.mod h1:PCXf5kktTzNav0aH2Ns3jsowqwmA9xTcsrEOoPzx/K8= -github.com/fluxcd/pkg/apis/meta v1.3.0 h1:KxeEc6olmSZvQ5pBONPE4IKxyoWQbqTJF1X6K5nIXpU= -github.com/fluxcd/pkg/apis/meta v1.3.0/go.mod h1:3Ui8xFkoU4sYehqmscjpq7NjqH2YN1A2iX2okbO3/yA= -github.com/fluxcd/source-controller/api v1.2.4 h1:XjKTWhSSeLGsogWnTcLl5sUnyMlC5TKDbbBgP9SyJ5c= -github.com/fluxcd/source-controller/api v1.2.4/go.mod h1:j3QSHpIPBP5sjaGIkVtsgWCx8JcOmcsutRmdJmRMOZg= +github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM= +github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM= +github.com/fluxcd/source-controller/api v1.3.0 h1:Z5Lq0aJY87yg0cQDEuwGLKS60GhdErCHtsi546HUt10= +github.com/fluxcd/source-controller/api v1.3.0/go.mod h1:+tfd0vltjcVs/bbnq9AlYR9AAHSVfM/Z4v4TpQmdJf4= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6FI= @@ -779,8 +781,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/analysis v0.22.0 h1:wQ/d07nf78HNj4u+KiSY0sT234IAyePPbMgpUjUJQR0= github.com/go-openapi/analysis v0.22.0/go.mod h1:acDnkkCI2QxIo8sSIPgmp1wUlRohV7vfGtAIVae73b0= github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= @@ -1111,7 +1113,6 @@ github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267/go.mod h1:h1nSAbGFqGVzn6Jyl1R/iCcBUHN4g+gW1u9CoBTrb9E= github.com/jellydator/ttlcache/v3 v3.1.1 h1:RCgYJqo3jgvhl+fEWvjNW8thxGWsgxi+TPhRir1Y9y8= github.com/jellydator/ttlcache/v3 v3.1.1/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -1362,14 +1363,14 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= -github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= +github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= github.com/open-policy-agent/opa v0.61.0 h1:nhncQ2CAYtQTV/SMBhDDPsCpCQsUW+zO/1j+T5V7oZg= github.com/open-policy-agent/opa v0.61.0/go.mod h1:7OUuzJnsS9yHf8lw0ApfcbrnaRG1EkN3J2fuuqi4G/E= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -2181,8 +2182,6 @@ golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNq golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -2461,26 +2460,26 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.29.1 h1:DAjwWX/9YT7NQD4INu49ROJuZAAAP/Ijki48GUPzxqw= -k8s.io/api v0.29.1/go.mod h1:7Kl10vBRUXhnQQI8YR/R327zXC8eJ7887/+Ybta+RoQ= -k8s.io/apiextensions-apiserver v0.29.0 h1:0VuspFG7Hj+SxyF/Z/2T0uFbI5gb5LRgEyUVE3Q4lV0= -k8s.io/apiextensions-apiserver v0.29.0/go.mod h1:TKmpy3bTS0mr9pylH0nOt/QzQRrW7/h7yLdRForMZwc= -k8s.io/apimachinery v0.29.1 h1:KY4/E6km/wLBguvCZv8cKTeOwwOBqFNjwJIdMkMbbRc= -k8s.io/apimachinery v0.29.1/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU= -k8s.io/apiserver v0.29.0 h1:Y1xEMjJkP+BIi0GSEv1BBrf1jLU9UPfAnnGGbbDdp7o= -k8s.io/apiserver v0.29.0/go.mod h1:31n78PsRKPmfpee7/l9NYEv67u6hOL6AfcE761HapDM= +k8s.io/api v0.30.0 h1:siWhRq7cNjy2iHssOB9SCGNCl2spiF1dO3dABqZ8niA= +k8s.io/api v0.30.0/go.mod h1:OPlaYhoHs8EQ1ql0R/TsUgaRPhpKNxIMrKQfWUp8QSE= +k8s.io/apiextensions-apiserver v0.30.0 h1:jcZFKMqnICJfRxTgnC4E+Hpcq8UEhT8B2lhBcQ+6uAs= +k8s.io/apiextensions-apiserver v0.30.0/go.mod h1:N9ogQFGcrbWqAY9p2mUAL5mGxsLqwgtUce127VtRX5Y= +k8s.io/apimachinery v0.30.0 h1:qxVPsyDM5XS96NIh9Oj6LavoVFYff/Pon9cZeDIkHHA= +k8s.io/apimachinery v0.30.0/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= +k8s.io/apiserver v0.30.0 h1:QCec+U72tMQ+9tR6A0sMBB5Vh6ImCEkoKkTDRABWq6M= +k8s.io/apiserver v0.30.0/go.mod h1:smOIBq8t0MbKZi7O7SyIpjPsiKJ8qa+llcFCluKyqiY= k8s.io/cli-runtime v0.29.1 h1:By3WVOlEWYfyxhGko0f/IuAOLQcbBSMzwSaDren2JUs= k8s.io/cli-runtime v0.29.1/go.mod h1:vjEY9slFp8j8UoMhV5AlO8uulX9xk6ogfIesHobyBDU= -k8s.io/client-go v0.29.1 h1:19B/+2NGEwnFLzt0uB5kNJnfTsbV8w6TgQRz9l7ti7A= -k8s.io/client-go v0.29.1/go.mod h1:TDG/psL9hdet0TI9mGyHJSgRkW3H9JZk2dNEUS7bRks= -k8s.io/component-base v0.29.1 h1:MUimqJPCRnnHsskTTjKD+IC1EHBbRCVyi37IoFBrkYw= -k8s.io/component-base v0.29.1/go.mod h1:fP9GFjxYrLERq1GcWWZAE3bqbNcDKDytn2srWuHTtKc= +k8s.io/client-go v0.30.0 h1:sB1AGGlhY/o7KCyCEQ0bPWzYDL0pwOZO4vAtTSh/gJQ= +k8s.io/client-go v0.30.0/go.mod h1:g7li5O5256qe6TYdAMyX/otJqMhIiGgTapdLchhmOaY= +k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o= +k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ= k8s.io/component-helpers v0.29.1 h1:54MMEDu6xeJmMtAKztsPwu0kJKr4+jCUzaEIn2UXRoc= k8s.io/component-helpers v0.29.1/go.mod h1:+I7xz4kfUgxWAPJIVKrqe4ml4rb9UGpazlOmhXYo+cY= k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780= -k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/kubectl v0.29.1 h1:rWnW3hi/rEUvvg7jp4iYB68qW5un/urKbv7fu3Vj0/s= k8s.io/kubectl v0.29.1/go.mod h1:SZzvLqtuOJYSvZzPZR9weSuP0wDQ+N37CENJf0FhDF4= k8s.io/metrics v0.29.1 h1:qutc3aIPMCniMuEApuLaeYX47rdCn8eycVDx7R6wMlQ= @@ -2504,8 +2503,8 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/cli-utils v0.36.0 h1:k7GM6LmIMydtvM6Ad91XuqKk0QEVL9bVbaiX1uvWIrA= sigs.k8s.io/cli-utils v0.36.0/go.mod h1:uCFC3BPXB3xHFQyKkWUlTrncVDCKzbdDfqZqRTCrk24= -sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= -sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= +sigs.k8s.io/controller-runtime v0.18.1 h1:RpWbigmuiylbxOCLy0tGnq1cU1qWPwNIQzoJk+QeJx4= +sigs.k8s.io/controller-runtime v0.18.1/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/kustomize/api v0.16.0 h1:/zAR4FOQDCkgSDmVzV2uiFbuy9bhu3jEzthrHCuvm1g= diff --git a/packages/zarf-agent/manifests/clusterrole.yaml b/packages/zarf-agent/manifests/clusterrole.yaml new file mode 100644 index 0000000000..d28365447a --- /dev/null +++ b/packages/zarf-agent/manifests/clusterrole.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: service-viewer +rules: +- apiGroups: + - "" + resources: + - services + verbs: + - get + - list diff --git a/packages/zarf-agent/manifests/clusterrolebinding.yaml b/packages/zarf-agent/manifests/clusterrolebinding.yaml new file mode 100644 index 0000000000..fbfb53e3b0 --- /dev/null +++ b/packages/zarf-agent/manifests/clusterrolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: service-viewer-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: service-viewer +subjects: +- kind: ServiceAccount + name: zarf + namespace: zarf diff --git a/packages/zarf-agent/manifests/webhook.yaml b/packages/zarf-agent/manifests/webhook.yaml index 136590c5d4..afe62fd0b2 100644 --- a/packages/zarf-agent/manifests/webhook.yaml +++ b/packages/zarf-agent/manifests/webhook.yaml @@ -48,6 +48,92 @@ webhooks: - "v1" - "v1beta1" sideEffects: None + - name: agent-flux-ocirepo.zarf.dev + namespaceSelector: + matchExpressions: + # Ensure we don't mess with kube-system + - key: "kubernetes.io/metadata.name" + operator: NotIn + values: + - "kube-system" + # Allow ignoring whole namespaces + - key: zarf.dev/agent + operator: NotIn + values: + - "skip" + - "ignore" + objectSelector: + matchExpressions: + # Always ignore specific resources if requested by annotation/label + - key: zarf.dev/agent + operator: NotIn + values: + - "skip" + - "ignore" + clientConfig: + service: + name: agent-hook + namespace: zarf + path: "/mutate/flux-ocirepository" + caBundle: "###ZARF_AGENT_CA###" + rules: + - operations: + - "CREATE" + - "UPDATE" + apiGroups: + - "source.toolkit.fluxcd.io" + apiVersions: + - "*" + resources: + - "ocirepositories" + admissionReviewVersions: + - "v1" + - "v1beta1" + sideEffects: None + - name: agent-flux-helmrepo.zarf.dev + namespaceSelector: + matchExpressions: + # Ensure we don't mess with kube-system + - key: "kubernetes.io/metadata.name" + operator: NotIn + values: + - "kube-system" + # Allow ignoring whole namespaces + - key: zarf.dev/agent + operator: NotIn + values: + - "skip" + - "ignore" + objectSelector: + matchExpressions: + # Always ignore specific resources if requested by annotation/label + - key: zarf.dev/agent + operator: NotIn + values: + - "skip" + - "ignore" + clientConfig: + service: + name: agent-hook + namespace: zarf + path: "/mutate/flux-helmrepository" + caBundle: "###ZARF_AGENT_CA###" + rules: + - operations: + - "CREATE" + - "UPDATE" + apiGroups: + - "source.toolkit.fluxcd.io" + apiVersions: + # While v1beta1 doesn't have the `type: oci` and is unsupported we still want to run mutations + # so that we can show a warning in the logs + - "*" + resources: + - "helmrepositories" + admissionReviewVersions: + - "v1" + - "v1beta1" + sideEffects: None - name: agent-flux-gitrepo.zarf.dev namespaceSelector: matchExpressions: diff --git a/packages/zarf-agent/zarf.yaml b/packages/zarf-agent/zarf.yaml index 32a6c34997..65ee63170f 100644 --- a/packages/zarf-agent/zarf.yaml +++ b/packages/zarf-agent/zarf.yaml @@ -29,6 +29,8 @@ components: - manifests/webhook.yaml - manifests/role.yaml - manifests/rolebinding.yaml + - manifests/clusterrole.yaml + - manifests/clusterrolebinding.yaml - manifests/serviceaccount.yaml actions: onCreate: diff --git a/site/src/content/docs/ref/actions.mdx b/site/src/content/docs/ref/actions.mdx index ae1c0c0fbc..c65cd75df1 100644 --- a/site/src/content/docs/ref/actions.mdx +++ b/site/src/content/docs/ref/actions.mdx @@ -203,7 +203,7 @@ The below example shows using a `wait` command to wait for a GitOps deployment t diff --git a/site/src/content/docs/ref/components.mdx b/site/src/content/docs/ref/components.mdx index ed2e8c884e..b8f85d6fc6 100644 --- a/site/src/content/docs/ref/components.mdx +++ b/site/src/content/docs/ref/components.mdx @@ -129,7 +129,7 @@ Kustomizations are handled a bit differently than normal manifests in that Zarf -Images can either be discovered manually, or automatically by using [`zarf dev find-images`](/commands/zarf_dev_find-images/). +Images can either be discovered manually, or automatically by using [`zarf dev find-images`](/commands/zarf_dev_find-images/). The image list is not limited to containers, any OCI image following the [Image Manifest specification](https://github.com/opencontainers/image-spec/blob/main/manifest.md) can be pulled :::note diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx index 8bf4c8506b..8a8daa55de 100644 --- a/site/src/content/docs/ref/init-package.mdx +++ b/site/src/content/docs/ref/init-package.mdx @@ -151,13 +151,21 @@ Notably, the `REGISTRY_AFFINITY_CUSTOM` variable overrides the default pod anti- The `zarf-agent` is a [Kubernetes Mutating Webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) that intercepts requests to create resources and uses the `zarf-state` secret to mutate them to point to their air-gapped equivalents. -The `zarf-agent` is responsible for modifying [Kubernetes PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec) objects [Image](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container.Image) fields to point to the Zarf Registry. +The `zarf-agent` is responsible for modifying [Kubernetes PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec) objects [Image](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container.Image) fields to point to the Zarf Registry. This allows the cluster to pull images from the Zarf Registry instead of the internet without having to modify the original image references. -This allows the cluster to pull images from the Zarf Registry instead of the internet without having to modify the original image references. +The `zarf-agent` modifies the following [flux](https://fluxcd.io/flux/) resources: [GitRepository](https://fluxcd.io/docs/components/source/gitrepositories/), [OCIRepository](https://fluxcd.io/flux/components/source/ocirepositories/), & [HelmRepository](https://fluxcd.io/flux/components/source/helmrepositories/) to point to the local Git Server or Zarf Registry. HelmRepositories are only modified if the `type` key is set to `oci`. -The `zarf-agent` also modifies [Flux GitRepository](https://fluxcd.io/docs/components/source/gitrepositories/) objects to point to the local Git Server. +> Support for mutating OCIRepository and HelmRepository objects is in [`alpha`](/roadmap#alpha) and should be tested on non-production clusters before being deployed to production clusters. -> Support for mutating `Application` and `Repository` objects in ArgoCD is in [`beta`](/roadmap#beta) and should tested on non-production clusters before being deployed to production clusters. +:::caution + +Due to a bug in helm, HelmRepositories with an insecure registry must use IP address instead of a hostname. This is not an issue with the internal Zarf registry, which is always an IP address, but will cause Flux HelmRepositories to break if Zarf is using an external http registry with a hostname. + +::: + +The `zarf-agent` modifies [ArgoCD applications](https://argo-cd.readthedocs.io/en/stable/user-guide/application-specification/) & [ArgoCD Repositories](https://argo-cd.readthedocs.io/en/stable/user-guide/private-repositories/) objects to point to the local Git Server. + +> Support for mutating `Application` and `Repository` objects in ArgoCD is in [`beta`](/roadmap#beta) and should be tested on non-production clusters before being deployed to production clusters. :::note diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 68caf91270..d4f2e4d4d3 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -642,9 +642,10 @@ $ zarf tools update-creds artifact --artifact-push-username={USERNAME} --artifac // Zarf Agent messages // These are only seen in the Kubernetes logs. const ( - AgentInfoWebhookAllowed = "Webhook [%s - %s] - Allowed: %t" - AgentInfoPort = "Server running in port: %s" - + AgentInfoWebhookAllowed = "Webhook [%s - %s] - Allowed: %t" + AgentInfoPort = "Server running in port: %s" + AgentWarnNotOCIType = "Skipping HelmRepo mutation because the type is not OCI: %s" + AgentWarnSemVerRef = "Detected a semver OCI ref (%s) - continuing but will be unable to guarantee against collisions if multiple OCI artifacts with the same name are brought in from different registries" AgentErrBadRequest = "could not read request body: %s" AgentErrBindHandler = "Unable to bind the webhook handler" AgentErrCouldNotDeserializeReq = "could not deserialize request: %s" diff --git a/src/internal/agent/hooks/flux.go b/src/internal/agent/hooks/flux-gitrepo.go similarity index 100% rename from src/internal/agent/hooks/flux.go rename to src/internal/agent/hooks/flux-gitrepo.go diff --git a/src/internal/agent/hooks/flux_test.go b/src/internal/agent/hooks/flux-gitrepo_test.go similarity index 100% rename from src/internal/agent/hooks/flux_test.go rename to src/internal/agent/hooks/flux-gitrepo_test.go diff --git a/src/internal/agent/hooks/flux-helmrepo.go b/src/internal/agent/hooks/flux-helmrepo.go new file mode 100644 index 0000000000..f4514b639d --- /dev/null +++ b/src/internal/agent/hooks/flux-helmrepo.go @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package hooks contains the mutation hooks for the Zarf agent. +package hooks + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" + v1 "k8s.io/api/admission/v1" +) + +// NewHelmRepositoryMutationHook creates a new instance of the helm repo mutation hook. +func NewHelmRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { + message.Debug("hooks.NewHelmRepositoryMutationHook()") + return operations.Hook{ + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateHelmRepo(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateHelmRepo(ctx, r, cluster) + }, + } +} + +// mutateHelmRepo mutates the repository url to point to the repository URL defined in the ZarfState. +func mutateHelmRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) { + + src := &flux.HelmRepository{} + if err := json.Unmarshal(r.Object.Raw, &src); err != nil { + return nil, fmt.Errorf(lang.ErrUnmarshal, err) + } + + // If we see a type of helm repo other than OCI we should flag a warning and return + if strings.ToLower(src.Spec.Type) != "oci" { + message.Warnf(lang.AgentWarnNotOCIType, src.Spec.Type) + return &operations.Result{Allowed: true}, nil + } + + if src.Labels != nil && src.Labels["zarf-agent"] == "patched" { + return &operations.Result{ + Allowed: true, + PatchOps: nil, + }, nil + } + + zarfState, err := cluster.LoadZarfState(ctx) + if err != nil { + return nil, fmt.Errorf(lang.AgentErrGetState, err) + } + + // Get the registry service info if this is a NodePort service to use the internal kube-dns + registryAddress, err := cluster.GetServiceInfoFromRegistryAddress(ctx, zarfState.RegistryInfo.Address) + if err != nil { + return nil, err + } + + message.Debugf("Using the url of (%s) to mutate the flux HelmRepository", registryAddress) + + patchedSrc, err := transform.ImageTransformHost(registryAddress, src.Spec.URL) + if err != nil { + return nil, fmt.Errorf("unable to transform the HelmRepo URL: %w", err) + } + + patchedRefInfo, err := transform.ParseImageRef(patchedSrc) + if err != nil { + return nil, fmt.Errorf("unable to parse the HelmRepo URL: %w", err) + } + patchedURL := helpers.OCIURLPrefix + patchedRefInfo.Name + + message.Debugf("original HelmRepo URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) + + patches := populateHelmRepoPatchOperations(patchedURL, zarfState.RegistryInfo.InternalRegistry) + + patches = append(patches, getLabelPatch(src.Labels)) + + return &operations.Result{ + Allowed: true, + PatchOps: patches, + }, nil +} + +func populateHelmRepoPatchOperations(repoURL string, isInternal bool) []operations.PatchOperation { + var patches []operations.PatchOperation + patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) + + if isInternal { + patches = append(patches, operations.ReplacePatchOperation("/spec/insecure", true)) + } + + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", meta.LocalObjectReference{Name: config.ZarfImagePullSecretName})) + + return patches +} diff --git a/src/internal/agent/hooks/flux-helmrepo_test.go b/src/internal/agent/hooks/flux-helmrepo_test.go new file mode 100644 index 0000000000..85895bd31b --- /dev/null +++ b/src/internal/agent/hooks/flux-helmrepo_test.go @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createFluxHelmRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxHelmRepo *flux.HelmRepository) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(fluxHelmRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestFluxHelmMutationWebhook(t *testing.T) { + t.Parallel() + + ctx := context.Background() + state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}} + + tests := []admissionTest{ + { + name: "should not mutate when type is not oci", + admissionReq: createFluxHelmRepoAdmissionRequest(t, v1.Update, &flux.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "not-oci", + }, + Spec: flux.HelmRepositorySpec{ + Type: "default", + }, + }), + code: http.StatusOK, + }, + { + name: "error on bad url", + admissionReq: createFluxHelmRepoAdmissionRequest(t, v1.Update, &flux.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bad-url", + }, + Spec: flux.HelmRepositorySpec{ + Type: "oci", + URL: "bad-url$", + }, + }), + errContains: "unable to transform the HelmRepo URL", + code: http.StatusInternalServerError, + }, + { + name: "should not mutate when agent patched", + admissionReq: createFluxHelmRepoAdmissionRequest(t, v1.Update, &flux.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "already-patched", + Labels: map[string]string{ + "zarf-agent": "patched", + }, + }, + Spec: flux.HelmRepositorySpec{ + Type: "oci", + }, + }), + code: http.StatusOK, + }, + { + name: "should be mutated with no internal service registry", + admissionReq: createFluxHelmRepoAdmissionRequest(t, v1.Create, &flux.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.HelmRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/charts", + Type: "oci", + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "oci://127.0.0.1:31999/stefanprodan/charts", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfImagePullSecretName}, + ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), + }, + code: http.StatusOK, + }, + { + name: "should be mutated with internal service registry", + admissionReq: createFluxHelmRepoAdmissionRequest(t, v1.Create, &flux.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.HelmRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/charts", + Type: "oci", + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "oci://10.11.12.13:5000/stefanprodan/charts", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfImagePullSecretName}, + ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), + }, + svc: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "zarf-docker-registry", + Namespace: "zarf", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: int32(31999), + Port: 5000, + }, + }, + ClusterIP: "10.11.12.13", + }, + }, + code: http.StatusOK, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewHelmRepositoryMutationHook(ctx, c)) + if tt.svc != nil { + _, err := c.Clientset.CoreV1().Services("zarf").Create(ctx, tt.svc, metav1.CreateOptions{}) + require.NoError(t, err) + } + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/hooks/flux-ocirepo.go b/src/internal/agent/hooks/flux-ocirepo.go new file mode 100644 index 0000000000..1a811c3319 --- /dev/null +++ b/src/internal/agent/hooks/flux-ocirepo.go @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package hooks contains the mutation hooks for the Zarf agent. +package hooks + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1beta2" + v1 "k8s.io/api/admission/v1" +) + +// NewOCIRepositoryMutationHook creates a new instance of the oci repo mutation hook. +func NewOCIRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { + message.Debug("hooks.NewOCIRepositoryMutationHook()") + return operations.Hook{ + Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateOCIRepo(ctx, r, cluster) + }, + Update: func(r *v1.AdmissionRequest) (*operations.Result, error) { + return mutateOCIRepo(ctx, r, cluster) + }, + } +} + +// mutateOCIRepo mutates the oci repository url to point to the repository URL defined in the ZarfState. +func mutateOCIRepo(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) { + + src := &flux.OCIRepository{} + if err := json.Unmarshal(r.Object.Raw, &src); err != nil { + return nil, fmt.Errorf(lang.ErrUnmarshal, err) + } + + if src.Spec.Reference == nil { + src.Spec.Reference = &flux.OCIRepositoryRef{} + } + + // If we have a semver we want to continue since we wil still have the upstream tag + // but should warn that we can't guarantee there won't be collisions + if src.Spec.Reference.SemVer != "" { + message.Warnf(lang.AgentWarnSemVerRef, src.Spec.Reference.SemVer) + } + + if src.Labels != nil && src.Labels["zarf-agent"] == "patched" { + return &operations.Result{ + Allowed: true, + PatchOps: []operations.PatchOperation{}, + }, nil + } + + zarfState, err := cluster.LoadZarfState(ctx) + if err != nil { + return nil, fmt.Errorf(lang.AgentErrGetState, err) + } + + // Get the registry service info if this is a NodePort service to use the internal kube-dns + registryAddress, err := cluster.GetServiceInfoFromRegistryAddress(ctx, zarfState.RegistryInfo.Address) + if err != nil { + return nil, err + } + + // For the internal registry this will be the ip & port of the service, it may look like 10.43.36.151:5000 + message.Debugf("Using the url of (%s) to mutate the flux OCIRepository", registryAddress) + + ref := src.Spec.URL + if src.Spec.Reference.Digest != "" { + ref = fmt.Sprintf("%s@%s", ref, src.Spec.Reference.Digest) + } else if src.Spec.Reference.Tag != "" { + ref = fmt.Sprintf("%s:%s", ref, src.Spec.Reference.Tag) + } + + patchedSrc, err := transform.ImageTransformHost(registryAddress, ref) + if err != nil { + return nil, fmt.Errorf("unable to transform the OCIRepo URL: %w", err) + } + + patchedRefInfo, err := transform.ParseImageRef(patchedSrc) + if err != nil { + return nil, fmt.Errorf("unable to parse the transformed OCIRepo URL: %w", err) + } + patchedRef := src.Spec.Reference + + patchedURL := helpers.OCIURLPrefix + patchedRefInfo.Name + + if patchedRefInfo.Digest != "" { + patchedRef.Digest = patchedRefInfo.Digest + } else if patchedRefInfo.Tag != "" { + patchedRef.Tag = patchedRefInfo.Tag + } + + message.Debugf("original OCIRepo URL of (%s) got mutated to (%s)", src.Spec.URL, patchedURL) + + patches := populateOCIRepoPatchOperations(patchedURL, zarfState.RegistryInfo.InternalRegistry, patchedRef) + + patches = append(patches, getLabelPatch(src.Labels)) + return &operations.Result{ + Allowed: true, + PatchOps: patches, + }, nil +} + +func populateOCIRepoPatchOperations(repoURL string, isInternal bool, ref *flux.OCIRepositoryRef) []operations.PatchOperation { + var patches []operations.PatchOperation + patches = append(patches, operations.ReplacePatchOperation("/spec/url", repoURL)) + + patches = append(patches, operations.AddPatchOperation("/spec/secretRef", meta.LocalObjectReference{Name: config.ZarfImagePullSecretName})) + + if isInternal { + patches = append(patches, operations.ReplacePatchOperation("/spec/insecure", true)) + } + + // If semver is used we don't want to add the ":latest" tag + crc to the spec + if ref.SemVer != "" { + return patches + } + + if ref.Tag != "" { + patches = append(patches, operations.ReplacePatchOperation("/spec/ref/tag", ref.Tag)) + } + + return patches +} diff --git a/src/internal/agent/hooks/flux-ocirepo_test.go b/src/internal/agent/hooks/flux-ocirepo_test.go new file mode 100644 index 0000000000..4b2ff6cf57 --- /dev/null +++ b/src/internal/agent/hooks/flux-ocirepo_test.go @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package hooks + +import ( + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/internal/agent/http/admission" + "github.com/defenseunicorns/zarf/src/internal/agent/operations" + "github.com/defenseunicorns/zarf/src/types" + fluxmeta "github.com/fluxcd/pkg/apis/meta" + flux "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/stretchr/testify/require" + v1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +func createFluxOCIRepoAdmissionRequest(t *testing.T, op v1.Operation, fluxOCIRepo *flux.OCIRepository) *v1.AdmissionRequest { + t.Helper() + raw, err := json.Marshal(fluxOCIRepo) + require.NoError(t, err) + return &v1.AdmissionRequest{ + Operation: op, + Object: runtime.RawExtension{ + Raw: raw, + }, + } +} + +func TestFluxOCIMutationWebhook(t *testing.T) { + t.Parallel() + + tests := []admissionTest{ + { + name: "should not mutate when agent patched", + admissionReq: createFluxOCIRepoAdmissionRequest(t, v1.Update, &flux.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "already-patched", + Labels: map[string]string{ + "zarf-agent": "patched", + }, + }, + Spec: flux.OCIRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/manifests/podinfo", + Reference: &flux.OCIRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + patch: nil, + code: http.StatusOK, + }, + { + name: "bad oci url", + admissionReq: createFluxOCIRepoAdmissionRequest(t, v1.Update, &flux.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bad oci url", + }, + Spec: flux.OCIRepositorySpec{ + URL: "bad://ghcr.io/$", + }, + }), + errContains: "unable to transform the OCIRepo URL", + code: http.StatusInternalServerError, + }, + { + name: "should be mutated with no internal service registry", + admissionReq: createFluxOCIRepoAdmissionRequest(t, v1.Update, &flux.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.OCIRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/manifests/podinfo", + Reference: &flux.OCIRepositoryRef{ + Tag: "6.4.0", + }, + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "oci://127.0.0.1:31999/stefanprodan/manifests/podinfo", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfImagePullSecretName}, + ), + operations.ReplacePatchOperation( + "/spec/ref/tag", + "6.4.0-zarf-2823281104", + ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), + }, + code: http.StatusOK, + }, + { + name: "test semver tag", + admissionReq: createFluxOCIRepoAdmissionRequest(t, v1.Update, &flux.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.OCIRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/manifests/podinfo", + Reference: &flux.OCIRepositoryRef{ + SemVer: ">= 6.4.0", + }, + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "oci://127.0.0.1:31999/stefanprodan/manifests/podinfo", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfImagePullSecretName}, + ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), + }, + code: http.StatusOK, + }, + { + name: "should be mutated with internal service registry", + admissionReq: createFluxOCIRepoAdmissionRequest(t, v1.Create, &flux.OCIRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mutate-this", + }, + Spec: flux.OCIRepositorySpec{ + URL: "oci://ghcr.io/stefanprodan/charts", + Reference: &flux.OCIRepositoryRef{ + Digest: "sha256:6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b", + }, + }, + }), + patch: []operations.PatchOperation{ + operations.ReplacePatchOperation( + "/spec/url", + "oci://10.11.12.13:5000/stefanprodan/charts", + ), + operations.AddPatchOperation( + "/spec/secretRef", + fluxmeta.LocalObjectReference{Name: config.ZarfImagePullSecretName}, + ), + operations.ReplacePatchOperation( + "/metadata/labels", + map[string]string{ + "zarf-agent": "patched", + }, + ), + }, + svc: &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "zarf-docker-registry", + Namespace: "zarf", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: int32(31999), + Port: 5000, + }, + }, + ClusterIP: "10.11.12.13", + }, + }, + code: http.StatusOK, + }, + } + + ctx := context.Background() + state := &types.ZarfState{RegistryInfo: types.RegistryInfo{Address: "127.0.0.1:31999"}} + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + c := createTestClientWithZarfState(ctx, t, state) + handler := admission.NewHandler().Serve(NewOCIRepositoryMutationHook(ctx, c)) + if tt.svc != nil { + _, err := c.Clientset.CoreV1().Services("zarf").Create(ctx, tt.svc, metav1.CreateOptions{}) + require.NoError(t, err) + } + rr := sendAdmissionRequest(t, tt.admissionReq, handler) + verifyAdmission(t, rr, tt) + }) + } +} diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go index 716a5b5245..f2d4ed6136 100644 --- a/src/internal/agent/hooks/utils_test.go +++ b/src/internal/agent/hooks/utils_test.go @@ -27,6 +27,7 @@ type admissionTest struct { patch []operations.PatchOperation code int errContains string + svc *corev1.Service } func createTestClientWithZarfState(ctx context.Context, t *testing.T, state *types.ZarfState) *cluster.Cluster { diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 7938f35d6e..9bcff6a431 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -26,20 +26,24 @@ func NewAdmissionServer(ctx context.Context, port string) *http.Server { message.Fatalf(err, err.Error()) } - // Instances hooks + // Routers + admissionHandler := admission.NewHandler() podsMutation := hooks.NewPodMutationHook(ctx, c) fluxGitRepositoryMutation := hooks.NewGitRepositoryMutationHook(ctx, c) argocdApplicationMutation := hooks.NewApplicationMutationHook(ctx, c) argocdRepositoryMutation := hooks.NewRepositorySecretMutationHook(ctx, c) + fluxHelmRepositoryMutation := hooks.NewHelmRepositoryMutationHook(ctx, c) + fluxOCIRepositoryMutation := hooks.NewOCIRepositoryMutationHook(ctx, c) // Routers - ah := admission.NewHandler() mux := http.NewServeMux() mux.Handle("/healthz", healthz()) - mux.Handle("/mutate/pod", ah.Serve(podsMutation)) - mux.Handle("/mutate/flux-gitrepository", ah.Serve(fluxGitRepositoryMutation)) - mux.Handle("/mutate/argocd-application", ah.Serve(argocdApplicationMutation)) - mux.Handle("/mutate/argocd-repository", ah.Serve(argocdRepositoryMutation)) + mux.Handle("/mutate/pod", admissionHandler.Serve(podsMutation)) + mux.Handle("/mutate/flux-gitrepository", admissionHandler.Serve(fluxGitRepositoryMutation)) + mux.Handle("/mutate/flux-helmrepository", admissionHandler.Serve(fluxHelmRepositoryMutation)) + mux.Handle("/mutate/flux-ocirepository", admissionHandler.Serve(fluxOCIRepositoryMutation)) + mux.Handle("/mutate/argocd-application", admissionHandler.Serve(argocdApplicationMutation)) + mux.Handle("/mutate/argocd-repository", admissionHandler.Serve(argocdRepositoryMutation)) mux.Handle("/metrics", promhttp.Handler()) return &http.Server{ diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 1dcdbc6912..43bc102a6b 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -159,8 +159,11 @@ func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { continue } - // Try to get a valid existing secret - validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) + // Create the secret + validRegistrySecret, err := c.GenerateRegistryPullCreds(ctx, name, config.ZarfImagePullSecretName, r.state.RegistryInfo) + if err != nil { + return err + } // TODO: Refactor as error is not checked instead of checking for not found error. currentRegistrySecret, _ := c.Clientset.CoreV1().Secrets(name).Get(ctx, config.ZarfImagePullSecretName, metav1.GetOptions{}) sameSecretData := maps.EqualFunc(currentRegistrySecret.Data, validRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 2e5ff26ecc..09be8beba5 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -9,6 +9,7 @@ import ( "context" "encoding/base64" "encoding/json" + "fmt" "maps" corev1 "k8s.io/api/core/v1" @@ -33,25 +34,37 @@ type DockerConfigEntryWithAuth struct { } // GenerateRegistryPullCreds generates a secret containing the registry credentials. -func (c *Cluster) GenerateRegistryPullCreds(namespace, name string, registryInfo types.RegistryInfo) *corev1.Secret { +func (c *Cluster) GenerateRegistryPullCreds(ctx context.Context, namespace, name string, registryInfo types.RegistryInfo) (*corev1.Secret, error) { // Auth field must be username:password and base64 encoded fieldValue := registryInfo.PullUsername + ":" + registryInfo.PullPassword authEncodedValue := base64.StdEncoding.EncodeToString([]byte(fieldValue)) - registry := registryInfo.Address - // Create the expected structure for the dockerconfigjson dockerConfigJSON := DockerConfig{ Auths: DockerConfigEntry{ - registry: DockerConfigEntryWithAuth{ + // nodePort for zarf-docker-registry + registryInfo.Address: DockerConfigEntryWithAuth{ Auth: authEncodedValue, }, }, } + serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + // Build zarf-docker-registry service address string + svc, port, err := serviceInfoFromNodePortURL(serviceList.Items, registryInfo.Address) + if err == nil { + kubeDNSRegistryURL := fmt.Sprintf("%s:%d", svc.Spec.ClusterIP, port) + dockerConfigJSON.Auths[kubeDNSRegistryURL] = DockerConfigEntryWithAuth{ + Auth: authEncodedValue, + } + } + // Convert to JSON dockerConfigData, err := json.Marshal(dockerConfigJSON) if err != nil { - message.WarnErrf(err, "Unable to marshal the .dockerconfigjson secret data for the image pull secret") + return nil, fmt.Errorf("Unable to marshal the .dockerconfigjson secret data for the image pull secret: %w", err) } secretDockerConfig := &corev1.Secret{ @@ -71,7 +84,7 @@ func (c *Cluster) GenerateRegistryPullCreds(namespace, name string, registryInfo ".dockerconfigjson": dockerConfigData, }, } - return secretDockerConfig + return secretDockerConfig, nil } // GenerateGitPullCreds generates a secret containing the git credentials. @@ -122,7 +135,11 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *type (namespace.Labels[AgentLabel] != "skip" && namespace.Labels[AgentLabel] != "ignore") { spinner.Updatef("Updating existing Zarf-managed image secret for namespace: '%s'", namespace.Name) - newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) + newRegistrySecret, err := c.GenerateRegistryPullCreds(ctx, namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) + if err != nil { + message.WarnErrf(err, "Unable to generate registry creds") + continue + } if !maps.EqualFunc(currentRegistrySecret.Data, newRegistrySecret.Data, func(v1, v2 []byte) bool { return bytes.Equal(v1, v2) }) { _, err := c.Clientset.CoreV1().Secrets(newRegistrySecret.Namespace).Update(ctx, newRegistrySecret, metav1.UpdateOptions{}) if err != nil { @@ -170,3 +187,20 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types. spinner.Success() } } + +// GetServiceInfoFromRegistryAddress gets the service info for a registry address if it is a NodePort +func (c *Cluster) GetServiceInfoFromRegistryAddress(ctx context.Context, stateRegistryAddress string) (string, error) { + serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{}) + if err != nil { + return "", err + } + + // If this is an internal service then we need to look it up and + svc, port, err := serviceInfoFromNodePortURL(serviceList.Items, stateRegistryAddress) + if err != nil { + message.Debugf("registry appears to not be a nodeport service, using original address %q", stateRegistryAddress) + return stateRegistryAddress, nil + } + + return fmt.Sprintf("%s:%d", svc.Spec.ClusterIP, port), nil +} diff --git a/src/pkg/cluster/secrets_test.go b/src/pkg/cluster/secrets_test.go index dc9f6c9e22..20d3fe70f9 100644 --- a/src/pkg/cluster/secrets_test.go +++ b/src/pkg/cluster/secrets_test.go @@ -4,25 +4,27 @@ package cluster import ( + "context" "testing" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" "github.com/defenseunicorns/zarf/src/types" ) -func TestGenerateRegistryPullCreds(t *testing.T) { - c := &Cluster{} +func TestGenerateRegistryPullCredsWithOutSvc(t *testing.T) { + c := &Cluster{Clientset: fake.NewSimpleClientset()} + ctx := context.Background() ri := types.RegistryInfo{ - PushUsername: "push-user", - PushPassword: "push-password", PullUsername: "pull-user", PullPassword: "pull-password", Address: "example.com", } - secret := c.GenerateRegistryPullCreds("foo", "bar", ri) + secret, err := c.GenerateRegistryPullCreds(ctx, "foo", "bar", ri) + require.NoError(t, err) expectedSecret := corev1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: "v1", @@ -43,11 +45,59 @@ func TestGenerateRegistryPullCreds(t *testing.T) { require.Equal(t, expectedSecret, *secret) } +func TestGenerateRegistryPullCredsWithSvc(t *testing.T) { + c := &Cluster{Clientset: fake.NewSimpleClientset()} + ctx := context.Background() + svc := &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: "good-service", + Namespace: "whatever", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + NodePort: 30001, + Port: 3333, + }, + }, + ClusterIP: "10.11.12.13", + }, + } + + _, err := c.Clientset.CoreV1().Services("whatever").Create(ctx, svc, metav1.CreateOptions{}) + require.NoError(t, err) + + ri := types.RegistryInfo{ + PullUsername: "pull-user", + PullPassword: "pull-password", + Address: "127.0.0.1:30001", + } + secret, err := c.GenerateRegistryPullCreds(ctx, "foo", "bar", ri) + require.NoError(t, err) + expectedSecret := corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Secret", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "foo", + Labels: map[string]string{ + ZarfManagedByLabel: "zarf", + }, + }, + Type: corev1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + ".dockerconfigjson": []byte(`{"auths":{"10.11.12.13:3333":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="},"127.0.0.1:30001":{"auth":"cHVsbC11c2VyOnB1bGwtcGFzc3dvcmQ="}}}`), + }, + } + require.Equal(t, expectedSecret, *secret) +} + func TestGenerateGitPullCreds(t *testing.T) { c := &Cluster{} gi := types.GitServerInfo{ - PushUsername: "push-user", - PushPassword: "push-password", PullUsername: "pull-user", PullPassword: "pull-password", } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index e09103829d..10a287986d 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -159,11 +159,11 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInf if err != nil { return "", nil, err } - namespace, name, port, err := serviceInfoFromNodePortURL(serviceList.Items, registryInfo.Address) + svc, port, err := serviceInfoFromNodePortURL(serviceList.Items, registryInfo.Address) // If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource if err == nil { - if tunnel, err = c.NewTunnel(namespace, SvcResource, name, "", 0, port); err != nil { + if tunnel, err = c.NewTunnel(svc.Namespace, SvcResource, svc.Name, "", 0, port); err != nil { return "", tunnel, err } } @@ -255,42 +255,42 @@ func (c *Cluster) findPodContainerPort(ctx context.Context, svc corev1.Service) } // TODO: Refactor to use netip.AddrPort instead of a string for nodePortURL. -func serviceInfoFromNodePortURL(services []corev1.Service, nodePortURL string) (string, string, int, error) { +func serviceInfoFromNodePortURL(services []corev1.Service, nodePortURL string) (corev1.Service, int, error) { // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) parsedURL, err := url.Parse(nodePortURL) if err != nil { parsedURL, err = url.Parse("scheme://" + nodePortURL) if err != nil { - return "", "", 0, err + return corev1.Service{}, 0, err } } // Match hostname against localhost ip/hostnames hostname := parsedURL.Hostname() if hostname != helpers.IPV4Localhost && hostname != "localhost" { - return "", "", 0, fmt.Errorf("node port services should be on localhost") + return corev1.Service{}, 0, fmt.Errorf("node port services should be on localhost") } // Get the node port from the nodeportURL. nodePort, err := strconv.Atoi(parsedURL.Port()) if err != nil { - return "", "", 0, err + return corev1.Service{}, 0, err } if nodePort < 30000 || nodePort > 32767 { - return "", "", 0, fmt.Errorf("node port services should use the port range 30000-32767") + return corev1.Service{}, 0, fmt.Errorf("node port services should use the port range 30000-32767") } for _, svc := range services { if svc.Spec.Type == "NodePort" { for _, port := range svc.Spec.Ports { if int(port.NodePort) == nodePort { - return svc.Namespace, svc.Name, int(port.Port), nil + return svc, int(port.Port), nil } } } } - return "", "", 0, fmt.Errorf("no matching node port services found") + return corev1.Service{}, 0, fmt.Errorf("no matching node port services found") } // Global lock to synchronize port selections. diff --git a/src/pkg/cluster/tunnel_test.go b/src/pkg/cluster/tunnel_test.go index debe363e54..f9dfc7b5c8 100644 --- a/src/pkg/cluster/tunnel_test.go +++ b/src/pkg/cluster/tunnel_test.go @@ -21,6 +21,7 @@ func TestServiceInfoFromNodePortURL(t *testing.T) { expectedErr string expectedNamespace string expectedName string + expectedIP string expectedPort int }{ { @@ -85,6 +86,7 @@ func TestServiceInfoFromNodePortURL(t *testing.T) { Port: 3333, }, }, + ClusterIP: "good-ip", }, }, { @@ -105,6 +107,7 @@ func TestServiceInfoFromNodePortURL(t *testing.T) { }, expectedNamespace: "good-namespace", expectedName: "good-service", + expectedIP: "good-ip", expectedPort: 3333, }, } @@ -113,15 +116,16 @@ func TestServiceInfoFromNodePortURL(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - namespace, name, port, err := serviceInfoFromNodePortURL(tt.services, tt.nodePortURL) + svc, port, err := serviceInfoFromNodePortURL(tt.services, tt.nodePortURL) if tt.expectedErr != "" { require.EqualError(t, err, tt.expectedErr) return } require.NoError(t, err) - require.Equal(t, tt.expectedNamespace, namespace) - require.Equal(t, tt.expectedName, name) + require.Equal(t, tt.expectedNamespace, svc.Namespace) + require.Equal(t, tt.expectedName, svc.Name) require.Equal(t, tt.expectedPort, port) + require.Equal(t, tt.expectedIP, svc.Spec.ClusterIP) }) } } diff --git a/src/pkg/transform/image.go b/src/pkg/transform/image.go index 293cb61a64..ca6fcdc820 100644 --- a/src/pkg/transform/image.go +++ b/src/pkg/transform/image.go @@ -38,7 +38,7 @@ func ImageTransformHost(targetHost, srcReference string) (string, error) { // Generate a crc32 hash of the image host + name checksum := helpers.GetCRCHash(image.Name) - // If this image is specified by digest then don't add a checksum it as it will already be a specific SHA + // If this image is specified by digest then don't add a checksum as it will already be a specific SHA if image.Digest != "" { return fmt.Sprintf("%s/%s@%s", targetHost, image.Path, image.Digest), nil } @@ -63,6 +63,8 @@ func ImageTransformHostWithoutChecksum(targetHost, srcReference string) (string, // ParseImageRef parses a source reference into an Image struct func ParseImageRef(srcReference string) (out Image, err error) { + srcReference = strings.TrimPrefix(srcReference, helpers.OCIURLPrefix) + ref, err := reference.ParseAnyReference(srcReference) if err != nil { return out, err diff --git a/src/pkg/transform/image_test.go b/src/pkg/transform/image_test.go index 2edc2e87ec..cc61154efa 100644 --- a/src/pkg/transform/image_test.go +++ b/src/pkg/transform/image_test.go @@ -19,6 +19,7 @@ var imageRefs = []string{ "ghcr.io/stefanprodan/podinfo:6.3.3", "registry1.dso.mil/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0", "gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023", + "oci://10.43.130.183:5000/stefanprodan/manifests/podinfo", } var badImageRefs = []string{ @@ -38,6 +39,7 @@ func TestImageTransformHost(t *testing.T) { "gitlab.com/project/stefanprodan/podinfo:6.3.3-zarf-2985051089", "gitlab.com/project/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0-zarf-2003217571", "gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023", + "gitlab.com/project/stefanprodan/manifests/podinfo:latest-zarf-531355090", } for idx, ref := range imageRefs { @@ -62,6 +64,7 @@ func TestImageTransformHostWithoutChecksum(t *testing.T) { "gitlab.com/project/stefanprodan/podinfo:6.3.3", "gitlab.com/project/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0", "gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023", + "gitlab.com/project/stefanprodan/manifests/podinfo:latest", } for idx, ref := range imageRefs { @@ -86,6 +89,7 @@ func TestParseImageRef(t *testing.T) { {"ghcr.io/", "stefanprodan/podinfo", "6.3.3", ""}, {"registry1.dso.mil/", "ironbank/opensource/defenseunicorns/zarf/zarf-agent", "v0.25.0", ""}, {"gitlab.com/", "project/gitea/gitea", "1.19.3-rootless-zarf-3431384023", ""}, + {"10.43.130.183:5000/", "stefanprodan/manifests/podinfo", "latest", ""}, } for idx, ref := range imageRefs { diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index 7dc712e71a..263ddccb55 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -70,15 +70,15 @@ func testGitServerConnect(t *testing.T, gitURL string) { func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) + zarfState, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err) - gitCfg := git.New(state.GitServer) + gitCfg := git.New(zarfState.GitServer) // Get the repo as the readonly user repoName := "zarf-public-test-2469062884" - getRepoRequest, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/repos/%s/%s", gitURL, state.GitServer.PushUsername, repoName), nil) - getRepoResponseBody, _, err := gitCfg.DoHTTPThings(getRepoRequest, types.ZarfGitReadUser, state.GitServer.PullPassword) + getRepoRequest, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/repos/%s/%s", gitURL, zarfState.GitServer.PushUsername, repoName), nil) + getRepoResponseBody, _, err := gitCfg.DoHTTPThings(getRepoRequest, types.ZarfGitReadUser, zarfState.GitServer.PullPassword) require.NoError(t, err) // Make sure the only permissions are pull (read) @@ -93,16 +93,16 @@ func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) + zarfState, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") repoName := "zarf-public-test-2469062884" - gitCfg := git.New(state.GitServer) + gitCfg := git.New(zarfState.GitServer) // Get the Zarf repo tag repoTag := "v0.0.1" getRepoTagsRequest, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/repos/%s/%s/tags/%s", gitURL, types.ZarfGitPushUser, repoName, repoTag), nil) - getRepoTagsResponseBody, _, err := gitCfg.DoHTTPThings(getRepoTagsRequest, types.ZarfGitReadUser, state.GitServer.PullPassword) + getRepoTagsResponseBody, _, err := gitCfg.DoHTTPThings(getRepoTagsRequest, types.ZarfGitReadUser, zarfState.GitServer.PullPassword) require.NoError(t, err) // Make sure the pushed tag exists @@ -114,12 +114,18 @@ func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { // Get the Zarf repo commit repoHash := "01a23218923f24194133b5eb11268cf8d73ff1bb" getRepoCommitsRequest, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/repos/%s/%s/git/commits/%s", gitURL, types.ZarfGitPushUser, repoName, repoHash), nil) - getRepoCommitsResponseBody, _, err := gitCfg.DoHTTPThings(getRepoCommitsRequest, types.ZarfGitReadUser, state.GitServer.PullPassword) + getRepoCommitsResponseBody, _, err := gitCfg.DoHTTPThings(getRepoCommitsRequest, types.ZarfGitReadUser, zarfState.GitServer.PullPassword) require.NoError(t, err) require.Contains(t, string(getRepoCommitsResponseBody), repoHash) } func waitFluxPodInfoDeployment(t *testing.T) { + ctx := context.Background() + cluster := common.NewClusterOrDie(ctx) + zarfState, err := cluster.LoadZarfState(ctx) + require.NoError(t, err, "Failed to load Zarf state") + registryAddress, err := cluster.GetServiceInfoFromRegistryAddress(ctx, zarfState.RegistryInfo.Address) + require.NoError(t, err) // Deploy the flux example and verify that it works path := fmt.Sprintf("build/zarf-package-podinfo-flux-%s.tar.zst", e2e.Arch) stdOut, stdErr, err := e2e.Zarf("package", "deploy", path, "--confirm") @@ -131,6 +137,26 @@ func waitFluxPodInfoDeployment(t *testing.T) { expectedMutatedRepoURL := fmt.Sprintf("%s/%s/podinfo-1646971829.git", types.ZarfInClusterGitServiceURL, types.ZarfGitPushUser) require.Equal(t, expectedMutatedRepoURL, stdOut) + // Tests the URL mutation for HelmRepository CRD for Flux. + stdOut, stdErr, err = e2e.Kubectl("get", "helmrepositories", "podinfo", "-n", "flux-system", "-o", "jsonpath={.spec.url}") + require.NoError(t, err, stdOut, stdErr) + expectedMutatedRepoURL = fmt.Sprintf("oci://%s/stefanprodan/charts", registryAddress) + require.Equal(t, expectedMutatedRepoURL, stdOut) + stdOut, stdErr, err = e2e.Kubectl("get", "helmrelease", "podinfo", "-n", "flux-system", "-o", "jsonpath={.spec.chart.spec.version}") + require.NoError(t, err, stdOut, stdErr) + expectedMutatedRepoTag := "6.4.0" + require.Equal(t, expectedMutatedRepoTag, stdOut) + + // Tests the URL mutation for OCIRepository CRD for Flux. + stdOut, stdErr, err = e2e.Kubectl("get", "ocirepositories", "podinfo", "-n", "flux-system", "-o", "jsonpath={.spec.url}") + require.NoError(t, err, stdOut, stdErr) + expectedMutatedRepoURL = fmt.Sprintf("oci://%s/stefanprodan/manifests/podinfo", registryAddress) + require.Equal(t, expectedMutatedRepoURL, stdOut) + stdOut, stdErr, err = e2e.Kubectl("get", "ocirepositories", "podinfo", "-n", "flux-system", "-o", "jsonpath={.spec.ref.tag}") + require.NoError(t, err, stdOut, stdErr) + expectedMutatedRepoTag = "6.4.0-zarf-2823281104" + require.Equal(t, expectedMutatedRepoTag, stdOut) + // Remove the flux example when deployment completes stdOut, stdErr, err = e2e.Zarf("package", "remove", "podinfo-flux", "--confirm") require.NoError(t, err, stdOut, stdErr) diff --git a/src/test/external/common.go b/src/test/external/common.go index 5ce0efc68b..2e9cd8bb68 100644 --- a/src/test/external/common.go +++ b/src/test/external/common.go @@ -7,18 +7,29 @@ package external import ( "context" "path" + "path/filepath" "strings" "testing" "time" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/defenseunicorns/zarf/src/test" + "github.com/otiai10/copy" + "github.com/stretchr/testify/require" ) var zarfBinPath = path.Join("../../../build", test.GetCLIName()) -func verifyKubectlWaitSuccess(t *testing.T, timeoutMinutes time.Duration, args []string, onTimeout string) bool { - return verifyWaitSuccess(t, timeoutMinutes, "kubectl", args, "condition met", onTimeout) +func createPodInfoPackageWithInsecureSources(t *testing.T, temp string) { + err := copy.Copy("../../../examples/podinfo-flux", temp) + require.NoError(t, err) + // This is done because while .spec.insecure is auto set to true for internal registries by the agent + // it is not for external registries, however since we are using an insecure external registry, we still need it + err = exec.CmdWithPrint(zarfBinPath, "tools", "yq", "eval", ".spec.insecure = true", "-i", filepath.Join(temp, "helm", "podinfo-source.yaml")) + require.NoError(t, err, "unable to yq edit helm source") + err = exec.CmdWithPrint(zarfBinPath, "tools", "yq", "eval", ".spec.insecure = true", "-i", filepath.Join(temp, "oci", "podinfo-source.yaml")) + require.NoError(t, err, "unable to yq edit oci source") + exec.CmdWithPrint(zarfBinPath, "package", "create", temp, "--confirm", "--output", temp) } func verifyWaitSuccess(t *testing.T, timeoutMinutes time.Duration, cmd string, args []string, condition string, onTimeout string) bool { diff --git a/src/test/external/ext_in_cluster_test.go b/src/test/external/ext_in_cluster_test.go index 3338b5474f..655cb7c59a 100644 --- a/src/test/external/ext_in_cluster_test.go +++ b/src/test/external/ext_in_cluster_test.go @@ -9,12 +9,18 @@ import ( "fmt" "io" "net/http" + "os" + "path/filepath" "testing" + "time" + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/object" ) var inClusterCredentialArgs = []string{ @@ -31,6 +37,7 @@ type ExtInClusterTestSuite struct { } func (suite *ExtInClusterTestSuite) SetupSuite() { + fmt.Println("start: current time is ", time.Now()) suite.Assertions = require.New(suite.T()) // Install a gitea chart to the k8s cluster to act as the 'remote' git server @@ -49,14 +56,29 @@ func (suite *ExtInClusterTestSuite) SetupSuite() { suite.NoError(err, "unable to install the docker-registry chart") // Verify the registry and gitea helm charts installed successfully - registryWaitCmd := []string{"wait", "deployment", "-n=external-registry", "external-registry-docker-registry", "--for", "condition=Available=True", "--timeout=5s"} - registryErrStr := "unable to verify the docker-registry chart installed successfully" - giteaWaitCmd := []string{"wait", "pod", "-n=git-server", "gitea-0", "--for", "condition=Ready=True", "--timeout=5s"} - giteaErrStr := "unable to verify the gitea chart installed successfully" - success := verifyKubectlWaitSuccess(suite.T(), 2, registryWaitCmd, registryErrStr) - suite.True(success, registryErrStr) - success = verifyKubectlWaitSuccess(suite.T(), 3, giteaWaitCmd, giteaErrStr) - suite.True(success, giteaErrStr) + c, err := cluster.NewCluster() + suite.NoError(err) + objs := []object.ObjMetadata{ + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "external-registry", + Name: "external-registry-docker-registry", + }, + { + GroupKind: schema.GroupKind{ + Kind: "Pod", + }, + Namespace: "git-server", + Name: "gitea-0", + }, + } + waitCtx, waitCancel := context.WithTimeout(context.Background(), 60*time.Second) + defer waitCancel() + err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + suite.NoError(err) } func (suite *ExtInClusterTestSuite) TearDownSuite() { @@ -123,17 +145,47 @@ func (suite *ExtInClusterTestSuite) Test_1_Deploy() { initArgs = append(initArgs, inClusterCredentialArgs...) err := exec.CmdWithPrint(zarfBinPath, initArgs...) suite.NoError(err, "unable to initialize the k8s server with zarf") + temp := suite.T().TempDir() + defer os.Remove(temp) + createPodInfoPackageWithInsecureSources(suite.T(), temp) // Deploy the flux example package - deployArgs := []string{"package", "deploy", "../../../build/zarf-package-podinfo-flux-amd64.tar.zst", "--confirm"} + deployArgs := []string{"package", "deploy", filepath.Join(temp, "zarf-package-podinfo-flux-amd64.tar.zst"), "--confirm"} err = exec.CmdWithPrint(zarfBinPath, deployArgs...) suite.NoError(err, "unable to deploy flux example package") - // Verify flux was able to pull from the 'external' repository - podinfoWaitCmd := []string{"wait", "deployment", "-n=podinfo", "podinfo", "--for", "condition=Available=True", "--timeout=3s"} - errorStr := "unable to verify flux deployed the podinfo example" - success := verifyKubectlWaitSuccess(suite.T(), 2, podinfoWaitCmd, errorStr) - suite.True(success, errorStr) + c, err := cluster.NewCluster() + suite.NoError(err) + objs := []object.ObjMetadata{ + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "podinfo-git", + Name: "podinfo", + }, + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "podinfo-helm", + Name: "podinfo", + }, + { + GroupKind: schema.GroupKind{ + Group: "apps", + Kind: "Deployment", + }, + Namespace: "podinfo-oci", + Name: "podinfo", + }, + } + waitCtx, waitCancel := context.WithTimeout(context.Background(), 60*time.Second) + defer waitCancel() + err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + suite.NoError(err) _, _, err = exec.CmdWithContext(context.TODO(), exec.PrintCfg(), zarfBinPath, "destroy", "--confirm") suite.NoError(err, "unable to teardown zarf") diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index 050150e5fa..b32960c318 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -29,6 +29,7 @@ const ( subnet = "172.31.0.0/16" gateway = "172.31.0.1" giteaIP = "172.31.0.99" + registryIP = "172.31.0.10" giteaHost = "gitea.localhost" registryHost = "registry.localhost" clusterName = "zarf-external-test" @@ -43,7 +44,9 @@ var outClusterCredentialArgs = []string{ "--git-url=http://" + giteaHost + ":3000", "--registry-push-username=" + registryUser, "--registry-push-password=" + commonPassword, - "--registry-url=k3d-" + registryHost + ":5000"} + // TODO @AustinAbro321 once flux updates to a version of helm using ORAS v1.2.5 or greater we can switch back + // to using the registry host rather than creating an IP https://github.com/helm/helm/pull/12998 + "--registry-url=" + registryIP + ":5000"} type ExtOutClusterTestSuite struct { suite.Suite @@ -56,7 +59,8 @@ func (suite *ExtOutClusterTestSuite) SetupSuite() { // Teardown any leftovers from previous tests _ = exec.CmdWithPrint("k3d", "cluster", "delete", clusterName) - _ = exec.CmdWithPrint("k3d", "registry", "delete", registryHost) + _ = exec.CmdWithPrint("docker", "rm", "-f", "k3d-"+registryHost) + _ = exec.CmdWithPrint("docker", "compose", "down") _ = exec.CmdWithPrint("docker", "network", "remove", network) // Setup a network for everything to live inside @@ -64,11 +68,12 @@ func (suite *ExtOutClusterTestSuite) SetupSuite() { suite.NoError(err, "unable to create the k3d registry") // Install a k3d-managed registry server to act as the 'remote' container registry - err = exec.CmdWithPrint("k3d", "registry", "create", registryHost, "--port", "5000") + err = exec.CmdWithPrint("docker", "run", "-d", "--restart=always", "-p", "5000:5000", "--name", "k3d-"+registryHost, "registry:2.8.3") suite.NoError(err, "unable to create the k3d registry") // Create a k3d cluster with the proper networking and aliases - err = exec.CmdWithPrint("k3d", "cluster", "create", clusterName, "--registry-use", "k3d-"+registryHost+":5000", "--host-alias", giteaIP+":"+giteaHost, "--network", network) + err = exec.CmdWithPrint("k3d", "cluster", "create", clusterName, "--registry-config", "registries.yaml", + "--host-alias", registryIP+":"+registryHost, "--host-alias", giteaIP+":"+giteaHost, "--network", network) suite.NoError(err, "unable to create the k3d cluster") // Install a gitea server via docker compose to act as the 'remote' git server @@ -83,7 +88,9 @@ func (suite *ExtOutClusterTestSuite) SetupSuite() { // Connect gitea to the k3d network err = exec.CmdWithPrint("docker", "network", "connect", "--ip", giteaIP, network, giteaHost) - suite.NoError(err, "unable to connect the gitea-server top k3d") + suite.NoError(err, "unable to connect the gitea-server to k3d") + err = exec.CmdWithPrint("docker", "network", "connect", "--ip", registryIP, network, "k3d-"+registryHost) + suite.NoError(err, "unable to connect the registry-server to k3d") } func (suite *ExtOutClusterTestSuite) TearDownSuite() { @@ -94,7 +101,7 @@ func (suite *ExtOutClusterTestSuite) TearDownSuite() { err = exec.CmdWithPrint("docker", "compose", "down") suite.NoError(err, "unable to teardown the gitea-server") - err = exec.CmdWithPrint("k3d", "registry", "delete", registryHost) + err = exec.CmdWithPrint("docker", "rm", "-f", "k3d-"+registryHost) suite.NoError(err, "unable to teardown the k3d registry") err = exec.CmdWithPrint("docker", "network", "remove", network) @@ -135,20 +142,25 @@ func (suite *ExtOutClusterTestSuite) Test_1_Deploy() { initArgs = append(initArgs, outClusterCredentialArgs...) err := exec.CmdWithPrint(zarfBinPath, initArgs...) suite.NoError(err, "unable to initialize the k8s server with zarf") +} +func (suite *ExtOutClusterTestSuite) Test_2_DeployGitOps() { // Deploy the flux example package - deployArgs := []string{"package", "deploy", "../../../build/zarf-package-podinfo-flux-amd64.tar.zst", "--confirm"} - err = exec.CmdWithPrint(zarfBinPath, deployArgs...) + temp := suite.T().TempDir() + defer os.Remove(temp) + createPodInfoPackageWithInsecureSources(suite.T(), temp) + + deployArgs := []string{"package", "deploy", filepath.Join(temp, "zarf-package-podinfo-flux-amd64.tar.zst"), "--confirm"} + err := exec.CmdWithPrint(zarfBinPath, deployArgs...) suite.NoError(err, "unable to deploy flux example package") - // Verify flux was able to pull from the 'external' repository - podinfoArgs := []string{"wait", "deployment", "-n=podinfo", "podinfo", "--for", "condition=Available=True", "--timeout=3s"} - errorStr := "unable to verify flux deployed the podinfo example" - success := verifyKubectlWaitSuccess(suite.T(), 2, podinfoArgs, errorStr) - suite.True(success, errorStr) + path := fmt.Sprintf("../../../build/zarf-package-argocd-%s.tar.zst", "amd64") + deployArgs = []string{"package", "deploy", path, "--confirm"} + err = exec.CmdWithPrint(zarfBinPath, deployArgs...) + suite.NoError(err) } -func (suite *ExtOutClusterTestSuite) Test_2_AuthToPrivateHelmChart() { +func (suite *ExtOutClusterTestSuite) Test_3_AuthToPrivateHelmChart() { baseURL := fmt.Sprintf("http://%s:3000", giteaHost) suite.createHelmChartInGitea(baseURL, giteaUser, commonPassword) diff --git a/src/test/external/registries.yaml b/src/test/external/registries.yaml new file mode 100644 index 0000000000..8e7ac32100 --- /dev/null +++ b/src/test/external/registries.yaml @@ -0,0 +1,4 @@ +mirrors: + "172.31.0.10:5000": + endpoint: + - http://172.31.0.10:5000 From fe6b2f39ff796ccd6f3fd8166318e023e9788c38 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Mon, 24 Jun 2024 19:55:36 +0000 Subject: [PATCH 099/132] refactor lint fix e2e tests check for var in component import improve tests changes add test file I forgot to add earlier testing package finding feedback validate schema use built in interface moving change where print happens get package name pass in package move badZarfPackage to better spot better badZarfPackage if to require cleanup tests whoops accidentally committed deletion refactor lint fix e2e tests check for var in component import improve tests changes add test file I forgot to add earlier testing package finding feedback validate schema use built in interface moving change where print happens get package name pass in package move badZarfPackage to better spot better badZarfPackage if to require cleanup tests whoops accidentally committed deletion --- src/cmd/dev.go | 15 +- src/pkg/message/message.go | 2 +- src/pkg/packager/dev.go | 71 ++++++ src/pkg/packager/lint/findings.go | 41 ++++ src/pkg/packager/lint/findings_test.go | 122 +++++++++ src/pkg/packager/lint/lint.go | 260 +++++++++----------- src/pkg/packager/lint/lint_test.go | 327 ++++++++++++++++--------- src/pkg/packager/lint/validator.go | 151 ------------ src/test/e2e/12_lint_test.go | 3 - src/test/packages/12-lint/zarf.yaml | 4 - src/types/component.go | 16 +- src/types/package.go | 2 +- src/types/runtime.go | 26 ++ zarf.schema.json | 10 +- 14 files changed, 628 insertions(+), 422 deletions(-) create mode 100644 src/pkg/packager/lint/findings.go create mode 100644 src/pkg/packager/lint/findings_test.go delete mode 100644 src/pkg/packager/lint/validator.go diff --git a/src/cmd/dev.go b/src/cmd/dev.go index adc5420a15..3714759c0d 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -18,7 +18,6 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager" - "github.com/defenseunicorns/zarf/src/pkg/packager/lint" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" @@ -249,19 +248,19 @@ var devLintCmd = &cobra.Command{ Aliases: []string{"l"}, Short: lang.CmdDevLintShort, Long: lang.CmdDevLintLong, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - validator, err := lint.Validate(cmd.Context(), pkgConfig.CreateOpts) + + pkgClient, err := packager.New(&pkgConfig) if err != nil { - message.Fatal(err, err.Error()) - } - validator.DisplayFormattedMessage() - if !validator.IsSuccess() { - os.Exit(1) + return err } + defer pkgClient.ClearTempPaths() + + return pkgClient.Lint(cmd.Context()) }, } diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 206176813f..68bf711219 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -327,7 +327,7 @@ func Table(header []string, data [][]string) { // preventing future characters from taking on the given color // returns string as normal if color is disabled func ColorWrap(str string, attr color.Attribute) string { - if config.NoColor { + if config.NoColor || str == "" { return str } return fmt.Sprintf("\x1b[%dm%s\x1b[0m", attr, str) diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index 4bb07fcb0c..5332d10c10 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -6,8 +6,10 @@ package packager import ( "context" + "errors" "fmt" "os" + "path/filepath" "runtime" "github.com/defenseunicorns/pkg/helpers/v2" @@ -16,7 +18,10 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/creator" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" + "github.com/defenseunicorns/zarf/src/pkg/packager/lint" + "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" + "github.com/fatih/color" ) // DevDeploy creates + deploys a package in one shot @@ -105,3 +110,69 @@ func (p *Packager) DevDeploy(ctx context.Context) error { // cd back return os.Chdir(cwd) } + +// Lint ensures a package is valid & follows suggested conventions +func (p *Packager) Lint(ctx context.Context) error { + if err := os.Chdir(p.cfg.CreateOpts.BaseDir); err != nil { + return fmt.Errorf("unable to access directory %q: %w", p.cfg.CreateOpts.BaseDir, err) + } + + if err := utils.ReadYaml(layout.ZarfYAML, &p.cfg.Pkg); err != nil { + return err + } + + findings, err := lint.Validate(ctx, p.cfg.Pkg, p.cfg.CreateOpts) + if err != nil { + return fmt.Errorf("linting failed: %w", err) + } + + if len(findings) == 0 { + message.Successf("0 findings for %q", p.cfg.Pkg.Metadata.Name) + return nil + } + + mapOfFindingsByPath := lint.GroupFindingsByPath(findings, types.SevWarn, p.cfg.Pkg.Metadata.Name) + + header := []string{"Type", "Path", "Message"} + + for _, findings := range mapOfFindingsByPath { + lintData := [][]string{} + for _, finding := range findings { + lintData = append(lintData, []string{ + colorWrapSev(finding.Severity), + message.ColorWrap(finding.YqPath, color.FgCyan), + itemizedDescription(finding.Description, finding.Item), + }) + } + var packagePathFromUser string + if helpers.IsOCIURL(findings[0].PackagePathOverride) { + packagePathFromUser = findings[0].PackagePathOverride + } else { + packagePathFromUser = filepath.Join(p.cfg.CreateOpts.BaseDir, findings[0].PackagePathOverride) + } + message.Notef("Linting package %q at %s", findings[0].PackageNameOverride, packagePathFromUser) + message.Table(header, lintData) + } + + if lint.HasSeverity(findings, types.SevErr) { + return errors.New("errors during lint") + } + + return nil +} + +func itemizedDescription(description string, item string) string { + if item == "" { + return description + } + return fmt.Sprintf("%s - %s", description, item) +} + +func colorWrapSev(s types.Severity) string { + if s == types.SevErr { + return message.ColorWrap("Error", color.FgRed) + } else if s == types.SevWarn { + return message.ColorWrap("Warning", color.FgYellow) + } + return "unknown" +} diff --git a/src/pkg/packager/lint/findings.go b/src/pkg/packager/lint/findings.go new file mode 100644 index 0000000000..5b184146ed --- /dev/null +++ b/src/pkg/packager/lint/findings.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package lint contains functions for verifying zarf yaml files are valid +package lint + +import ( + "github.com/defenseunicorns/pkg/helpers/v2" + "github.com/defenseunicorns/zarf/src/types" +) + +// GroupFindingsByPath groups findings by their package path +func GroupFindingsByPath(findings []types.PackageFinding, severity types.Severity, packageName string) map[string][]types.PackageFinding { + findings = helpers.RemoveMatches(findings, func(finding types.PackageFinding) bool { + return finding.Severity > severity + }) + for i := range findings { + if findings[i].PackageNameOverride == "" { + findings[i].PackageNameOverride = packageName + } + if findings[i].PackagePathOverride == "" { + findings[i].PackagePathOverride = "." + } + } + + mapOfFindingsByPath := make(map[string][]types.PackageFinding) + for _, finding := range findings { + mapOfFindingsByPath[finding.PackagePathOverride] = append(mapOfFindingsByPath[finding.PackagePathOverride], finding) + } + return mapOfFindingsByPath +} + +// HasSeverity returns true if the findings contain a severity equal to or greater than the given severity +func HasSeverity(findings []types.PackageFinding, severity types.Severity) bool { + for _, finding := range findings { + if finding.Severity <= severity { + return true + } + } + return false +} diff --git a/src/pkg/packager/lint/findings_test.go b/src/pkg/packager/lint/findings_test.go new file mode 100644 index 0000000000..99f0b7a652 --- /dev/null +++ b/src/pkg/packager/lint/findings_test.go @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package lint contains functions for verifying zarf yaml files are valid +package lint + +import ( + "testing" + + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestGroupFindingsByPath(t *testing.T) { + t.Parallel() + tests := []struct { + name string + findings []types.PackageFinding + severity types.Severity + packageName string + want map[string][]types.PackageFinding + }{ + { + name: "same package multiple findings", + findings: []types.PackageFinding{ + {Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}, + {Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}, + }, + severity: types.SevWarn, + packageName: "testPackage", + want: map[string][]types.PackageFinding{ + "path": { + {Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}, + {Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}, + }, + }, + }, + { + name: "different packages single finding", + findings: []types.PackageFinding{ + {Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}, + {Severity: types.SevErr, PackageNameOverride: "", PackagePathOverride: ""}, + }, + severity: types.SevWarn, + packageName: "testPackage", + want: map[string][]types.PackageFinding{ + "path": {{Severity: types.SevWarn, PackageNameOverride: "import", PackagePathOverride: "path"}}, + ".": {{Severity: types.SevErr, PackageNameOverride: "testPackage", PackagePathOverride: "."}}, + }, + }, + { + name: "Multiple findings, mixed severity", + findings: []types.PackageFinding{ + {Severity: types.SevWarn, PackageNameOverride: "", PackagePathOverride: ""}, + {Severity: types.SevErr, PackageNameOverride: "", PackagePathOverride: ""}, + }, + severity: types.SevErr, + packageName: "testPackage", + want: map[string][]types.PackageFinding{ + ".": {{Severity: types.SevErr, PackageNameOverride: "testPackage", PackagePathOverride: "."}}, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.want, GroupFindingsByPath(tt.findings, tt.severity, tt.packageName)) + }) + } +} + +func TestHasSeverity(t *testing.T) { + t.Parallel() + tests := []struct { + name string + severity types.Severity + expected bool + findings []types.PackageFinding + }{ + { + name: "error severity present", + findings: []types.PackageFinding{ + { + Severity: types.SevErr, + }, + }, + severity: types.SevErr, + expected: true, + }, + { + name: "error severity not present", + findings: []types.PackageFinding{ + { + Severity: types.SevWarn, + }, + }, + severity: types.SevErr, + expected: false, + }, + { + name: "err and warning severity present", + findings: []types.PackageFinding{ + { + Severity: types.SevWarn, + }, + { + Severity: types.SevErr, + }, + }, + severity: types.SevErr, + expected: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + require.Equal(t, tt.expected, HasSeverity(tt.findings, tt.severity)) + }) + } +} diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go index c6e1c4dde0..ddcdea76db 100644 --- a/src/pkg/packager/lint/lint.go +++ b/src/pkg/packager/lint/lint.go @@ -6,10 +6,8 @@ package lint import ( "context" - "embed" "fmt" - "os" - "path/filepath" + "io/fs" "regexp" "strings" @@ -26,119 +24,99 @@ import ( ) // ZarfSchema is exported so main.go can embed the schema file -var ZarfSchema embed.FS +var ZarfSchema fs.ReadFileFS -func getSchemaFile() ([]byte, error) { - return ZarfSchema.ReadFile("zarf.schema.json") -} - -// Validate validates a zarf file against the zarf schema, returns *validator with warnings or errors if they exist -// along with an error if the validation itself failed -func Validate(ctx context.Context, createOpts types.ZarfCreateOptions) (*Validator, error) { - validator := Validator{} - var err error - - if err := utils.ReadYaml(filepath.Join(createOpts.BaseDir, layout.ZarfYAML), &validator.typedZarfPackage); err != nil { +// Validate the given Zarf package. The Zarf package should not already be composed when sent to this function. +func Validate(ctx context.Context, pkg types.ZarfPackage, createOpts types.ZarfCreateOptions) ([]types.PackageFinding, error) { + var findings []types.PackageFinding + compFindings, err := lintComponents(ctx, pkg, createOpts) + if err != nil { return nil, err } + findings = append(findings, compFindings...) - if err := utils.ReadYaml(filepath.Join(createOpts.BaseDir, layout.ZarfYAML), &validator.untypedZarfPackage); err != nil { + jsonSchema, err := ZarfSchema.ReadFile("zarf.schema.json") + if err != nil { return nil, err } - if err := os.Chdir(createOpts.BaseDir); err != nil { - return nil, fmt.Errorf("unable to access directory '%s': %w", createOpts.BaseDir, err) - } - - validator.baseDir = createOpts.BaseDir - - lintComponents(ctx, &validator, &createOpts) - - if validator.jsonSchema, err = getSchemaFile(); err != nil { + var untypedZarfPackage interface{} + if err := utils.ReadYaml(layout.ZarfYAML, &untypedZarfPackage); err != nil { return nil, err } - if err = validateSchema(&validator); err != nil { + schemaFindings, err := validateSchema(jsonSchema, untypedZarfPackage) + if err != nil { return nil, err } + findings = append(findings, schemaFindings...) - return &validator, nil + return findings, nil } -func lintComponents(ctx context.Context, validator *Validator, createOpts *types.ZarfCreateOptions) { - for i, component := range validator.typedZarfPackage.Components { - arch := config.GetArch(validator.typedZarfPackage.Metadata.Architecture) +func lintComponents(ctx context.Context, pkg types.ZarfPackage, createOpts types.ZarfCreateOptions) ([]types.PackageFinding, error) { + var findings []types.PackageFinding + for i, component := range pkg.Components { + arch := config.GetArch(pkg.Metadata.Architecture) if !composer.CompatibleComponent(component, arch, createOpts.Flavor) { continue } - chain, err := composer.NewImportChain(ctx, component, i, validator.typedZarfPackage.Metadata.Name, arch, createOpts.Flavor) - baseComponent := chain.Head() + chain, err := composer.NewImportChain(ctx, component, i, pkg.Metadata.Name, arch, createOpts.Flavor) - var badImportYqPath string - if baseComponent != nil { - if baseComponent.Import.URL != "" { - badImportYqPath = fmt.Sprintf(".components.[%d].import.url", i) - } - if baseComponent.Import.Path != "" { - badImportYqPath = fmt.Sprintf(".components.[%d].import.path", i) - } - } if err != nil { - validator.addError(validatorMessage{ - description: err.Error(), - packageRelPath: ".", - packageName: validator.typedZarfPackage.Metadata.Name, - yqPath: badImportYqPath, - }) + return nil, err } - node := baseComponent + node := chain.Head() for node != nil { - checkForVarInComponentImport(validator, node) - fillComponentTemplate(validator, node, createOpts) - lintComponent(validator, node) + component := node.ZarfComponent + compFindings := fillComponentTemplate(&component, &createOpts) + compFindings = append(compFindings, checkComponent(component, node.Index())...) + for i := range compFindings { + compFindings[i].PackagePathOverride = node.ImportLocation() + compFindings[i].PackageNameOverride = node.OriginalPackageName() + } + findings = append(findings, compFindings...) node = node.Next() } } + return findings, nil } -func fillComponentTemplate(validator *Validator, node *composer.Node, createOpts *types.ZarfCreateOptions) { - err := creator.ReloadComponentTemplate(&node.ZarfComponent) +func fillComponentTemplate(c *types.ZarfComponent, createOpts *types.ZarfCreateOptions) []types.PackageFinding { + var findings []types.PackageFinding + err := creator.ReloadComponentTemplate(c) if err != nil { - validator.addWarning(validatorMessage{ - description: err.Error(), - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), + findings = append(findings, types.PackageFinding{ + Description: err.Error(), + Severity: types.SevWarn, }) } templateMap := map[string]string{} setVarsAndWarn := func(templatePrefix string, deprecated bool) { - yamlTemplates, err := utils.FindYamlTemplates(node, templatePrefix, "###") + yamlTemplates, err := utils.FindYamlTemplates(c, templatePrefix, "###") if err != nil { - validator.addWarning(validatorMessage{ - description: err.Error(), - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), + findings = append(findings, types.PackageFinding{ + Description: err.Error(), + Severity: types.SevWarn, }) } for key := range yamlTemplates { if deprecated { - validator.addWarning(validatorMessage{ - description: fmt.Sprintf(lang.PkgValidateTemplateDeprecation, key, key, key), - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), + findings = append(findings, types.PackageFinding{ + Description: fmt.Sprintf(lang.PkgValidateTemplateDeprecation, key, key, key), + Severity: types.SevWarn, }) } _, present := createOpts.SetVariables[key] if !present { - validator.addWarning(validatorMessage{ - description: lang.UnsetVarLintWarning, - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), + findings = append(findings, types.PackageFinding{ + Description: lang.UnsetVarLintWarning, + Severity: types.SevWarn, }) } } @@ -153,7 +131,8 @@ func fillComponentTemplate(validator *Validator, node *composer.Node, createOpts setVarsAndWarn(types.ZarfPackageVariablePrefix, true) //nolint: errcheck // This error should bubble up - utils.ReloadYamlTemplate(node, templateMap) + utils.ReloadYamlTemplate(c, templateMap) + return findings } func isPinnedImage(image string) (bool, error) { @@ -172,87 +151,71 @@ func isPinnedRepo(repo string) bool { return (strings.Contains(repo, "@")) } -func lintComponent(validator *Validator, node *composer.Node) { - checkForUnpinnedRepos(validator, node) - checkForUnpinnedImages(validator, node) - checkForUnpinnedFiles(validator, node) +// checkComponent runs lint rules against a component +func checkComponent(c types.ZarfComponent, i int) []types.PackageFinding { + var findings []types.PackageFinding + findings = append(findings, checkForUnpinnedRepos(c, i)...) + findings = append(findings, checkForUnpinnedImages(c, i)...) + findings = append(findings, checkForUnpinnedFiles(c, i)...) + return findings } -func checkForUnpinnedRepos(validator *Validator, node *composer.Node) { - for j, repo := range node.Repos { - repoYqPath := fmt.Sprintf(".components.[%d].repos.[%d]", node.Index(), j) +func checkForUnpinnedRepos(c types.ZarfComponent, i int) []types.PackageFinding { + var findings []types.PackageFinding + for j, repo := range c.Repos { + repoYqPath := fmt.Sprintf(".components.[%d].repos.[%d]", i, j) if !isPinnedRepo(repo) { - validator.addWarning(validatorMessage{ - yqPath: repoYqPath, - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "Unpinned repository", - item: repo, + findings = append(findings, types.PackageFinding{ + YqPath: repoYqPath, + Description: "Unpinned repository", + Item: repo, + Severity: types.SevWarn, }) } } + return findings } -func checkForUnpinnedImages(validator *Validator, node *composer.Node) { - for j, image := range node.Images { - imageYqPath := fmt.Sprintf(".components.[%d].images.[%d]", node.Index(), j) +func checkForUnpinnedImages(c types.ZarfComponent, i int) []types.PackageFinding { + var findings []types.PackageFinding + for j, image := range c.Images { + imageYqPath := fmt.Sprintf(".components.[%d].images.[%d]", i, j) pinnedImage, err := isPinnedImage(image) if err != nil { - validator.addError(validatorMessage{ - yqPath: imageYqPath, - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "Invalid image reference", - item: image, + findings = append(findings, types.PackageFinding{ + YqPath: imageYqPath, + Description: "Failed to parse image reference", + Item: image, + Severity: types.SevWarn, }) continue } if !pinnedImage { - validator.addWarning(validatorMessage{ - yqPath: imageYqPath, - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "Image not pinned with digest", - item: image, + findings = append(findings, types.PackageFinding{ + YqPath: imageYqPath, + Description: "Image not pinned with digest", + Item: image, + Severity: types.SevWarn, }) } } + return findings } -func checkForUnpinnedFiles(validator *Validator, node *composer.Node) { - for j, file := range node.Files { - fileYqPath := fmt.Sprintf(".components.[%d].files.[%d]", node.Index(), j) +func checkForUnpinnedFiles(c types.ZarfComponent, i int) []types.PackageFinding { + var findings []types.PackageFinding + for j, file := range c.Files { + fileYqPath := fmt.Sprintf(".components.[%d].files.[%d]", i, j) if file.Shasum == "" && helpers.IsURL(file.Source) { - validator.addWarning(validatorMessage{ - yqPath: fileYqPath, - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "No shasum for remote file", - item: file.Source, + findings = append(findings, types.PackageFinding{ + YqPath: fileYqPath, + Description: "No shasum for remote file", + Item: file.Source, + Severity: types.SevWarn, }) } } -} - -func checkForVarInComponentImport(validator *Validator, node *composer.Node) { - if strings.Contains(node.Import.Path, types.ZarfPackageTemplatePrefix) { - validator.addWarning(validatorMessage{ - yqPath: fmt.Sprintf(".components.[%d].import.path", node.Index()), - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "Zarf does not evaluate variables at component.x.import.path", - item: node.Import.Path, - }) - } - if strings.Contains(node.Import.URL, types.ZarfPackageTemplatePrefix) { - validator.addWarning(validatorMessage{ - yqPath: fmt.Sprintf(".components.[%d].import.url", node.Index()), - packageRelPath: node.ImportLocation(), - packageName: node.OriginalPackageName(), - description: "Zarf does not evaluate variables at component.x.import.url", - item: node.Import.URL, - }) - } + return findings } func makeFieldPathYqCompat(field string) string { @@ -268,25 +231,38 @@ func makeFieldPathYqCompat(field string) string { return fmt.Sprintf(".%s", wrappedField) } -func validateSchema(validator *Validator) error { - schemaLoader := gojsonschema.NewBytesLoader(validator.jsonSchema) - documentLoader := gojsonschema.NewGoLoader(validator.untypedZarfPackage) +func validateSchema(jsonSchema []byte, untypedZarfPackage interface{}) ([]types.PackageFinding, error) { + var findings []types.PackageFinding - result, err := gojsonschema.Validate(schemaLoader, documentLoader) + schemaErrors, err := runSchema(jsonSchema, untypedZarfPackage) if err != nil { - return err + return nil, err } - if !result.Valid() { - for _, desc := range result.Errors() { - validator.addError(validatorMessage{ - yqPath: makeFieldPathYqCompat(desc.Field()), - description: desc.Description(), - packageRelPath: ".", - packageName: validator.typedZarfPackage.Metadata.Name, + if len(schemaErrors) != 0 { + for _, schemaErr := range schemaErrors { + findings = append(findings, types.PackageFinding{ + YqPath: makeFieldPathYqCompat(schemaErr.Field()), + Description: schemaErr.Description(), + Severity: types.SevErr, }) } } - return err + return findings, err +} + +func runSchema(jsonSchema []byte, pkg interface{}) ([]gojsonschema.ResultError, error) { + schemaLoader := gojsonschema.NewBytesLoader(jsonSchema) + documentLoader := gojsonschema.NewGoLoader(pkg) + + result, err := gojsonschema.Validate(schemaLoader, documentLoader) + if err != nil { + return nil, err + } + + if !result.Valid() { + return result.Errors(), nil + } + return nil, nil } diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go index b259fd89ed..78d4c4102d 100644 --- a/src/pkg/packager/lint/lint_test.go +++ b/src/pkg/packager/lint/lint_test.go @@ -9,136 +9,239 @@ import ( "errors" "fmt" "os" - "path/filepath" "testing" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/packager/composer" + "github.com/defenseunicorns/zarf/src/pkg/variables" "github.com/defenseunicorns/zarf/src/types" goyaml "github.com/goccy/go-yaml" "github.com/stretchr/testify/require" ) -const badZarfPackage = ` +func TestZarfSchema(t *testing.T) { + t.Parallel() + zarfSchema, err := os.ReadFile("../../../../zarf.schema.json") + require.NoError(t, err) + + tests := []struct { + name string + pkg types.ZarfPackage + expectedSchemaStrings []string + }{ + { + name: "valid package", + pkg: types.ZarfPackage{ + Kind: types.ZarfInitConfig, + Metadata: types.ZarfMetadata{ + Name: "valid-name", + }, + Components: []types.ZarfComponent{ + { + Name: "valid-comp", + }, + }, + }, + expectedSchemaStrings: nil, + }, + { + name: "no comp or kind", + pkg: types.ZarfPackage{ + Metadata: types.ZarfMetadata{ + Name: "no-comp-or-kind", + }, + Components: []types.ZarfComponent{}, + }, + expectedSchemaStrings: []string{ + "kind: kind must be one of the following: \"ZarfInitConfig\", \"ZarfPackageConfig\"", + "components: Array must have at least 1 items", + }, + }, + { + name: "invalid package", + pkg: types.ZarfPackage{ + Kind: types.ZarfInitConfig, + Metadata: types.ZarfMetadata{ + Name: "-invalid-name", + }, + Components: []types.ZarfComponent{ + { + Name: "invalid-name", + Only: types.ZarfComponentOnlyTarget{ + LocalOS: "unsupportedOS", + }, + Import: types.ZarfComponentImport{ + Path: fmt.Sprintf("start%send", types.ZarfPackageTemplatePrefix), + URL: fmt.Sprintf("oci://start%send", types.ZarfPackageTemplatePrefix), + }, + }, + { + Name: "actions", + Actions: types.ZarfComponentActions{ + OnCreate: types.ZarfComponentActionSet{ + Before: []types.ZarfComponentAction{ + { + Cmd: "echo 'invalid setVariable'", + SetVariables: []variables.Variable{{Name: "not_uppercase"}}, + }, + }, + }, + OnRemove: types.ZarfComponentActionSet{ + OnSuccess: []types.ZarfComponentAction{ + { + Cmd: "echo 'invalid setVariable'", + SetVariables: []variables.Variable{{Name: "not_uppercase"}}, + }, + }, + }, + }, + }, + }, + Variables: []variables.InteractiveVariable{ + { + Variable: variables.Variable{Name: "not_uppercase"}, + }, + }, + Constants: []variables.Constant{ + { + Name: "not_uppercase", + }, + }, + }, + expectedSchemaStrings: []string{ + "metadata.name: Does not match pattern '^[a-z0-9][a-z0-9\\-]*$'", + "variables.0.name: Does not match pattern '^[A-Z0-9_]+$'", + "constants.0.name: Does not match pattern '^[A-Z0-9_]+$'", + "components.0.only.localOS: components.0.only.localOS must be one of the following: \"linux\", \"darwin\", \"windows\"", + "components.1.actions.onCreate.before.0.setVariables.0.name: Does not match pattern '^[A-Z0-9_]+$'", + "components.1.actions.onRemove.onSuccess.0.setVariables.0.name: Does not match pattern '^[A-Z0-9_]+$'", + "components.0.import.path: Must not validate the schema (not)", + "components.0.import.url: Must not validate the schema (not)", + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + findings, err := runSchema(zarfSchema, tt.pkg) + require.NoError(t, err) + var schemaStrings []string + for _, schemaErr := range findings { + schemaStrings = append(schemaStrings, schemaErr.String()) + } + require.ElementsMatch(t, tt.expectedSchemaStrings, schemaStrings) + }) + } + + t.Run("validate schema fail with errors not possible from object", func(t *testing.T) { + t.Parallel() + // When we want to test the absence of a field, an incorrect type, or an extra field + // we can't do it through a struct since non pointer fields will have a zero value of their type + const badZarfPackage = ` kind: ZarfInitConfig +extraField: whatever metadata: - name: init + name: invalid description: Testing bad yaml components: -- name: first-test-component - import: - not-path: packages/distros/k3s - name: import-test import: path: 123123 + charts: + - noWait: true + manifests: + - namespace: no-name-for-manifest ` - -const goodZarfPackage = ` -x-name: &name good-zarf-package - -kind: ZarfPackageConfig -metadata: - name: *name - x-description: Testing good yaml with yaml extension - -components: - - name: baseline - required: true - x-foo: bar - -` - -func readAndUnmarshalYaml[T interface{}](t *testing.T, yamlString string) T { - t.Helper() - var unmarshalledYaml T - err := goyaml.Unmarshal([]byte(yamlString), &unmarshalledYaml) - if err != nil { - t.Errorf("error unmarshalling yaml: %v", err) - } - return unmarshalledYaml -} - -func TestValidateSchema(t *testing.T) { - getZarfSchema := func(t *testing.T) []byte { - t.Helper() - file, err := os.ReadFile("../../../../zarf.schema.json") - if err != nil { - t.Errorf("error reading file: %v", err) + var unmarshalledYaml interface{} + err := goyaml.Unmarshal([]byte(badZarfPackage), &unmarshalledYaml) + require.NoError(t, err) + schemaErrs, err := runSchema(zarfSchema, unmarshalledYaml) + require.NoError(t, err) + var schemaStrings []string + for _, schemaErr := range schemaErrs { + schemaStrings = append(schemaStrings, schemaErr.String()) + } + expectedSchemaStrings := []string{ + "(root): Additional property extraField is not allowed", + "components.0.import.path: Invalid type. Expected: string, given: integer", + "components.0.charts.0: name is required", + "components.0.manifests.0: name is required", } - return file - } - t.Run("validate schema success", func(t *testing.T) { - unmarshalledYaml := readAndUnmarshalYaml[interface{}](t, goodZarfPackage) - validator := Validator{untypedZarfPackage: unmarshalledYaml, jsonSchema: getZarfSchema(t)} - err := validateSchema(&validator) - require.NoError(t, err) - require.Empty(t, validator.findings) + require.ElementsMatch(t, expectedSchemaStrings, schemaStrings) }) - t.Run("validate schema fail", func(t *testing.T) { - unmarshalledYaml := readAndUnmarshalYaml[interface{}](t, badZarfPackage) - validator := Validator{untypedZarfPackage: unmarshalledYaml, jsonSchema: getZarfSchema(t)} - err := validateSchema(&validator) + t.Run("test schema findings is created as expected", func(t *testing.T) { + t.Parallel() + findings, err := validateSchema(zarfSchema, types.ZarfPackage{ + Kind: types.ZarfInitConfig, + Metadata: types.ZarfMetadata{ + Name: "invalid", + }, + }) require.NoError(t, err) - config.NoColor = true - require.Equal(t, "Additional property not-path is not allowed", validator.findings[0].String()) - require.Equal(t, "Invalid type. Expected: string, given: integer", validator.findings[1].String()) - }) - - t.Run("Template in component import success", func(t *testing.T) { - unmarshalledYaml := readAndUnmarshalYaml[types.ZarfPackage](t, goodZarfPackage) - validator := Validator{typedZarfPackage: unmarshalledYaml} - for _, component := range validator.typedZarfPackage.Components { - lintComponent(&validator, &composer.Node{ZarfComponent: component}) + expected := []types.PackageFinding{ + { + Description: "Invalid type. Expected: array, given: null", + Severity: types.SevErr, + YqPath: ".components", + }, } - require.Empty(t, validator.findings) - }) - - t.Run("Path template in component import failure", func(t *testing.T) { - pathVar := "###ZARF_PKG_TMPL_PATH###" - pathComponent := types.ZarfComponent{Import: types.ZarfComponentImport{Path: pathVar}} - validator := Validator{typedZarfPackage: types.ZarfPackage{Components: []types.ZarfComponent{pathComponent}}} - checkForVarInComponentImport(&validator, &composer.Node{ZarfComponent: pathComponent}) - require.Equal(t, pathVar, validator.findings[0].item) + require.ElementsMatch(t, expected, findings) }) +} - t.Run("OCI template in component import failure", func(t *testing.T) { - ociPathVar := "oci://###ZARF_PKG_TMPL_PATH###" - URLComponent := types.ZarfComponent{Import: types.ZarfComponentImport{URL: ociPathVar}} - validator := Validator{typedZarfPackage: types.ZarfPackage{Components: []types.ZarfComponent{URLComponent}}} - checkForVarInComponentImport(&validator, &composer.Node{ZarfComponent: URLComponent}) - require.Equal(t, ociPathVar, validator.findings[0].item) - }) +func TestValidateComponent(t *testing.T) { + t.Parallel() t.Run("Unpinnned repo warning", func(t *testing.T) { - validator := Validator{} + t.Parallel() unpinnedRepo := "https://github.com/defenseunicorns/zarf-public-test.git" component := types.ZarfComponent{Repos: []string{ unpinnedRepo, - "https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1"}} - checkForUnpinnedRepos(&validator, &composer.Node{ZarfComponent: component}) - require.Equal(t, unpinnedRepo, validator.findings[0].item) - require.Len(t, validator.findings, 1) + "https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1", + }} + findings := checkForUnpinnedRepos(component, 0) + expected := []types.PackageFinding{ + { + Item: unpinnedRepo, + Description: "Unpinned repository", + Severity: types.SevWarn, + YqPath: ".components.[0].repos.[0]", + }, + } + require.Equal(t, expected, findings) }) t.Run("Unpinnned image warning", func(t *testing.T) { - validator := Validator{} + t.Parallel() unpinnedImage := "registry.com:9001/whatever/image:1.0.0" badImage := "badimage:badimage@@sha256:3fbc632167424a6d997e74f5" component := types.ZarfComponent{Images: []string{ unpinnedImage, "busybox:latest@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79", - badImage}} - checkForUnpinnedImages(&validator, &composer.Node{ZarfComponent: component}) - require.Equal(t, unpinnedImage, validator.findings[0].item) - require.Equal(t, badImage, validator.findings[1].item) - require.Len(t, validator.findings, 2) + badImage, + }} + findings := checkForUnpinnedImages(component, 0) + expected := []types.PackageFinding{ + { + Item: unpinnedImage, + Description: "Image not pinned with digest", + Severity: types.SevWarn, + YqPath: ".components.[0].images.[0]", + }, + { + Item: badImage, + Description: "Failed to parse image reference", + Severity: types.SevWarn, + YqPath: ".components.[0].images.[2]", + }, + } + require.Equal(t, expected, findings) }) t.Run("Unpinnned file warning", func(t *testing.T) { - validator := Validator{} + t.Parallel() fileURL := "http://example.com/file.zip" localFile := "local.txt" zarfFiles := []types.ZarfFile{ @@ -154,12 +257,21 @@ func TestValidateSchema(t *testing.T) { }, } component := types.ZarfComponent{Files: zarfFiles} - checkForUnpinnedFiles(&validator, &composer.Node{ZarfComponent: component}) - require.Equal(t, fileURL, validator.findings[0].item) - require.Len(t, validator.findings, 1) + findings := checkForUnpinnedFiles(component, 0) + expectedErr := []types.PackageFinding{ + { + Item: fileURL, + Description: "No shasum for remote file", + Severity: types.SevWarn, + YqPath: ".components.[0].files.[0]", + }, + } + require.Equal(t, expectedErr, findings) + require.Len(t, findings, 1) }) t.Run("Wrap standalone numbers in bracket", func(t *testing.T) { + t.Parallel() input := "components12.12.import.path" expected := ".components12.[12].import.path" actual := makeFieldPathYqCompat(input) @@ -167,29 +279,26 @@ func TestValidateSchema(t *testing.T) { }) t.Run("root doesn't change", func(t *testing.T) { + t.Parallel() input := "(root)" actual := makeFieldPathYqCompat(input) require.Equal(t, input, actual) }) - t.Run("Test composable components", func(t *testing.T) { - pathVar := "fake-path" - unpinnedImage := "unpinned:latest" - pathComponent := types.ZarfComponent{ - Import: types.ZarfComponentImport{Path: pathVar}, - Images: []string{unpinnedImage}} - validator := Validator{ - typedZarfPackage: types.ZarfPackage{Components: []types.ZarfComponent{pathComponent}, - Metadata: types.ZarfMetadata{Name: "test-zarf-package"}}} + t.Run("Test composable components with bad path", func(t *testing.T) { + t.Parallel() + zarfPackage := types.ZarfPackage{ + Components: []types.ZarfComponent{ + { + Import: types.ZarfComponentImport{Path: "bad-path"}, + }, + }, + Metadata: types.ZarfMetadata{Name: "test-zarf-package"}, + } createOpts := types.ZarfCreateOptions{Flavor: "", BaseDir: "."} - lintComponents(context.Background(), &validator, &createOpts) - // Require.contains rather than equals since the error message changes from linux to windows - require.Contains(t, validator.findings[0].description, fmt.Sprintf("open %s", filepath.Join("fake-path", "zarf.yaml"))) - require.Equal(t, ".components.[0].import.path", validator.findings[0].yqPath) - require.Equal(t, ".", validator.findings[0].packageRelPath) - require.Equal(t, unpinnedImage, validator.findings[1].item) - require.Equal(t, ".", validator.findings[1].packageRelPath) + _, err := lintComponents(context.Background(), zarfPackage, createOpts) + require.Error(t, err) }) t.Run("isImagePinned", func(t *testing.T) { diff --git a/src/pkg/packager/lint/validator.go b/src/pkg/packager/lint/validator.go deleted file mode 100644 index 532ea28a64..0000000000 --- a/src/pkg/packager/lint/validator.go +++ /dev/null @@ -1,151 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package lint contains functions for verifying zarf yaml files are valid -package lint - -import ( - "fmt" - "path/filepath" - - "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/types" - "github.com/fatih/color" -) - -type category int - -const ( - categoryError category = 1 - categoryWarning category = 2 -) - -type validatorMessage struct { - yqPath string - description string - item string - packageRelPath string - packageName string - category category -} - -func (c category) String() string { - if c == categoryError { - return message.ColorWrap("Error", color.FgRed) - } else if c == categoryWarning { - return message.ColorWrap("Warning", color.FgYellow) - } - return "" -} - -func (vm validatorMessage) String() string { - if vm.item != "" { - vm.item = fmt.Sprintf(" - %s", vm.item) - } - return fmt.Sprintf("%s%s", vm.description, vm.item) -} - -// Validator holds the warnings/errors and messaging that we get from validation -type Validator struct { - findings []validatorMessage - jsonSchema []byte - typedZarfPackage types.ZarfPackage - untypedZarfPackage interface{} - baseDir string -} - -// DisplayFormattedMessage message sent to user based on validator results -func (v Validator) DisplayFormattedMessage() { - if !v.hasFindings() { - message.Successf("0 findings for %q", v.typedZarfPackage.Metadata.Name) - } - v.printValidationTable() -} - -// IsSuccess returns true if there are not any errors -func (v Validator) IsSuccess() bool { - for _, finding := range v.findings { - if finding.category == categoryError { - return false - } - } - return true -} - -func (v Validator) packageRelPathToUser(vm validatorMessage) string { - if helpers.IsOCIURL(vm.packageRelPath) { - return vm.packageRelPath - } - return filepath.Join(v.baseDir, vm.packageRelPath) -} - -func (v Validator) printValidationTable() { - if !v.hasFindings() { - return - } - - mapOfFindingsByPath := make(map[string][]validatorMessage) - for _, finding := range v.findings { - mapOfFindingsByPath[finding.packageRelPath] = append(mapOfFindingsByPath[finding.packageRelPath], finding) - } - - header := []string{"Type", "Path", "Message"} - - for packageRelPath, findings := range mapOfFindingsByPath { - lintData := [][]string{} - for _, finding := range findings { - lintData = append(lintData, []string{finding.category.String(), finding.getPath(), finding.String()}) - } - message.Notef("Linting package %q at %s", findings[0].packageName, v.packageRelPathToUser(findings[0])) - message.Table(header, lintData) - message.Info(v.getFormattedFindingCount(packageRelPath, findings[0].packageName)) - } -} - -func (v Validator) getFormattedFindingCount(relPath string, packageName string) string { - warningCount := 0 - errorCount := 0 - for _, finding := range v.findings { - if finding.packageRelPath != relPath { - continue - } - if finding.category == categoryWarning { - warningCount++ - } - if finding.category == categoryError { - errorCount++ - } - } - wordWarning := "warnings" - if warningCount == 1 { - wordWarning = "warning" - } - wordError := "errors" - if errorCount == 1 { - wordError = "error" - } - return fmt.Sprintf("%d %s and %d %s in %q", - warningCount, wordWarning, errorCount, wordError, packageName) -} - -func (vm validatorMessage) getPath() string { - if vm.yqPath == "" { - return "" - } - return message.ColorWrap(vm.yqPath, color.FgCyan) -} - -func (v Validator) hasFindings() bool { - return len(v.findings) > 0 -} - -func (v *Validator) addWarning(vmessage validatorMessage) { - vmessage.category = categoryWarning - v.findings = helpers.Unique(append(v.findings, vmessage)) -} - -func (v *Validator) addError(vMessage validatorMessage) { - vMessage.category = categoryError - v.findings = helpers.Unique(append(v.findings, vMessage)) -} diff --git a/src/test/e2e/12_lint_test.go b/src/test/e2e/12_lint_test.go index cc6e513129..dc31217601 100644 --- a/src/test/e2e/12_lint_test.go +++ b/src/test/e2e/12_lint_test.go @@ -45,11 +45,8 @@ func TestLint(t *testing.T) { require.Contains(t, strippedStderr, ".components.[1].images.[0] | Image not pinned with digest - registry.com:9001/whatever/image:latest") // Testing import / compose + variables are working require.Contains(t, strippedStderr, ".components.[2].images.[3] | Image not pinned with digest - busybox:latest") - require.Contains(t, strippedStderr, ".components.[3].import.path | Zarf does not evaluate variables at component.x.import.path - ###ZARF_PKG_TMPL_PATH###") // Testing OCI imports get linted require.Contains(t, strippedStderr, ".components.[0].images.[0] | Image not pinned with digest - defenseunicorns/zarf-game:multi-tile-dark") - // Testing a bad path leads to a finding in lint - require.Contains(t, strippedStderr, fmt.Sprintf(".components.[3].import.path | open %s", filepath.Join("###ZARF_PKG_TMPL_PATH###", "zarf.yaml"))) // Check flavors require.NotContains(t, strippedStderr, "image-in-bad-flavor-component:unpinned") diff --git a/src/test/packages/12-lint/zarf.yaml b/src/test/packages/12-lint/zarf.yaml index 980fa78eeb..84f5b53c72 100644 --- a/src/test/packages/12-lint/zarf.yaml +++ b/src/test/packages/12-lint/zarf.yaml @@ -30,10 +30,6 @@ components: - source: file-without-shasum.txt target: src/ - - name: import - import: - path: "###ZARF_PKG_TMPL_PATH###" - - name: oci-games-url import: url: oci://🦄/dos-games:1.0.0 diff --git a/src/types/component.go b/src/types/component.go index a9997ac504..958d60e74d 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -8,6 +8,7 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/utils/exec" "github.com/defenseunicorns/zarf/src/pkg/variables" "github.com/defenseunicorns/zarf/src/types/extensions" + "github.com/invopop/jsonschema" ) // ZarfComponent is the primary functional grouping of assets to deploy by Zarf. @@ -121,7 +122,7 @@ type ZarfChart struct { RepoName string `json:"repoName,omitempty" jsonschema:"description=The name of a chart within a Helm repository (defaults to the Zarf name of the chart)"` GitPath string `json:"gitPath,omitempty" jsonschema:"description=(git repo only) The sub directory to the chart within a git repo,example=charts/your-chart"` LocalPath string `json:"localPath,omitempty" jsonschema:"description=The path to a local chart's folder or .tgz archive"` - Namespace string `json:"namespace" jsonschema:"description=The namespace to deploy the chart to"` + Namespace string `json:"namespace,omitempty" jsonschema:"description=The namespace to deploy the chart to"` ReleaseName string `json:"releaseName,omitempty" jsonschema:"description=The name of the Helm release to create (defaults to the Zarf name of the chart)"` NoWait bool `json:"noWait,omitempty" jsonschema:"description=Whether to not wait for chart resources to be ready before continuing"` ValuesFiles []string `json:"valuesFiles,omitempty" jsonschema:"description=List of local values file paths or remote URLs to include in the package; these will be merged together when deployed"` @@ -240,3 +241,16 @@ type ZarfComponentImport struct { // For further explanation see https://regex101.com/r/nxX8vx/1 URL string `json:"url,omitempty" jsonschema:"description=[beta] The URL to a Zarf package to import via OCI,pattern=^oci://.*$"` } + +// JSONSchemaExtend extends the generated json schema during `zarf internal gen-config-schema` +func (ZarfComponentImport) JSONSchemaExtend(schema *jsonschema.Schema) { + path, _ := schema.Properties.Get("path") + url, _ := schema.Properties.Get("url") + + notSchema := &jsonschema.Schema{ + Pattern: ZarfPackageTemplatePrefix, + } + + path.Not = notSchema + url.Not = notSchema +} diff --git a/src/types/package.go b/src/types/package.go index 5b7e734f3c..cff00da592 100644 --- a/src/types/package.go +++ b/src/types/package.go @@ -21,7 +21,7 @@ type ZarfPackage struct { Kind ZarfPackageKind `json:"kind" jsonschema:"description=The kind of Zarf package,enum=ZarfInitConfig,enum=ZarfPackageConfig,default=ZarfPackageConfig"` Metadata ZarfMetadata `json:"metadata,omitempty" jsonschema:"description=Package metadata"` Build ZarfBuildData `json:"build,omitempty" jsonschema:"description=Zarf-generated package build data"` - Components []ZarfComponent `json:"components" jsonschema:"description=List of components to deploy in this package"` + Components []ZarfComponent `json:"components" jsonschema:"description=List of components to deploy in this package,minItems=1"` Constants []variables.Constant `json:"constants,omitempty" jsonschema:"description=Constant template values applied on deploy for K8s resources"` Variables []variables.InteractiveVariable `json:"variables,omitempty" jsonschema:"description=Variable template values applied on deploy for K8s resources"` } diff --git a/src/types/runtime.go b/src/types/runtime.go index 24a466926e..b4a7d44ad2 100644 --- a/src/types/runtime.go +++ b/src/types/runtime.go @@ -144,3 +144,29 @@ type DifferentialData struct { DifferentialRepos map[string]bool DifferentialPackageVersion string } + +// PackageFinding is a struct that contains a finding about something wrong with a package +type PackageFinding struct { + // YqPath is the path to the key where the error originated from, this is sometimes empty in the case of a general error + YqPath string + Description string + // Item is the value of a key that is causing an error, for example a bad image name + Item string + // PackageNameOverride shows the name of the package that the error originated from + // If it is not set the base package will be used when displaying the error + PackageNameOverride string + // PackagePathOverride shows the path to the package that the error originated from + // If it is not set the base package will be used when displaying the error + PackagePathOverride string + Severity Severity +} + +// Severity is the type of package error +// Either Err or Warning +type Severity int + +// different severities of package errors +const ( + SevErr Severity = iota + 1 + SevWarn +) diff --git a/zarf.schema.json b/zarf.schema.json index c26bc550ff..3004a424b1 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -382,8 +382,7 @@ "additionalProperties": false, "type": "object", "required": [ - "name", - "namespace" + "name" ], "patternProperties": { "^x-": {} @@ -789,10 +788,16 @@ "description": "The name of the component to import from the referenced zarf.yaml" }, "path": { + "not": { + "pattern": "###ZARF_PKG_TMPL_" + }, "type": "string", "description": "The relative path to a directory containing a zarf.yaml to import from" }, "url": { + "not": { + "pattern": "###ZARF_PKG_TMPL_" + }, "type": "string", "pattern": "^oci://.*$", "description": "[beta] The URL to a Zarf package to import via OCI" @@ -1091,6 +1096,7 @@ "$ref": "#/$defs/ZarfComponent" }, "type": "array", + "minItems": 1, "description": "List of components to deploy in this package" }, "constants": { From 1ece79cfbcff27ed790fb6fb7c857eed32198590 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 17 Jun 2024 14:58:10 +0200 Subject: [PATCH 100/132] refactor: test and cleanup injector --- go.mod | 11 +- go.sum | 4 +- src/pkg/cluster/injector.go | 510 +++++++----------- src/pkg/cluster/injector_test.go | 173 ++++-- .../testdata/expected-injection-pod.json | 2 +- src/pkg/layout/package.go | 7 - src/pkg/packager/deploy.go | 4 +- 7 files changed, 324 insertions(+), 387 deletions(-) diff --git a/go.mod b/go.mod index 6195416cbf..30ec67759b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/anchore/syft v0.100.0 github.com/defenseunicorns/pkg/helpers v1.1.3 github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 - github.com/defenseunicorns/pkg/kubernetes v0.0.1 + github.com/defenseunicorns/pkg/kubernetes v0.2.0 github.com/defenseunicorns/pkg/oci v1.0.1 github.com/derailed/k9s v0.31.7 github.com/distribution/reference v0.5.0 @@ -63,6 +63,11 @@ require ( sigs.k8s.io/yaml v1.4.0 ) +require ( + github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect +) + require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect @@ -223,7 +228,6 @@ require ( github.com/emicklei/proto v1.12.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/evanphx/json-patch v5.7.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect github.com/facebookincubator/nvdtools v0.1.5 // indirect github.com/fatih/camelcase v1.0.0 // indirect @@ -245,7 +249,6 @@ require ( github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect - github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/analysis v0.22.0 // indirect github.com/go-openapi/errors v0.21.0 // indirect @@ -279,7 +282,7 @@ require ( github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.6.0 + github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/gookit/color v1.5.4 // indirect diff --git a/go.sum b/go.sum index 2cdb4bd07f..cdf76d18df 100644 --- a/go.sum +++ b/go.sum @@ -601,8 +601,8 @@ github.com/defenseunicorns/pkg/helpers v1.1.3 h1:EVVuniq02qfAouR//AT0eoCngLWfFOR github.com/defenseunicorns/pkg/helpers v1.1.3/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= -github.com/defenseunicorns/pkg/kubernetes v0.0.1 h1:HNQBV6XXFvlDvFdOCCWam0/LCgq67M+ggQKiRIoM2vU= -github.com/defenseunicorns/pkg/kubernetes v0.0.1/go.mod h1:AWB1iBbDO4VTmRO/E/8e0tVN0kkWbg+v8dhs9Hd9KXA= +github.com/defenseunicorns/pkg/kubernetes v0.2.0 h1:mVYZxmvpVa9zTC5U8wyNg1yoVAVkInJAg5qi9D0QoAQ= +github.com/defenseunicorns/pkg/kubernetes v0.2.0/go.mod h1:AWB1iBbDO4VTmRO/E/8e0tVN0kkWbg+v8dhs9Hd9KXA= github.com/defenseunicorns/pkg/oci v1.0.1 h1:WPrWRrae1L19X1vuhy6yYMR2zrTzgBbJHp3ImgUm4ZM= github.com/defenseunicorns/pkg/oci v1.0.1/go.mod h1:qZ3up/d0P81taW37fKR4lb19jJhQZJVtNOEJMu00dHQ= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 10b2778d7a..52824843bf 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -10,180 +10,137 @@ import ( "os" "path/filepath" "regexp" + "strings" "time" "github.com/google/go-containerregistry/pkg/crane" - "github.com/google/uuid" "github.com/mholt/archiver/v3" corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" - "sigs.k8s.io/cli-utils/pkg/object" + "k8s.io/apimachinery/pkg/util/wait" "github.com/defenseunicorns/pkg/helpers/v2" pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" ) -// The chunk size for the tarball chunks. -var payloadChunkSize = 1024 * 768 - -var ( - injectorRequestedCPU = resource.MustParse(".5") - injectorRequestedMemory = resource.MustParse("64Mi") - injectorLimitCPU = resource.MustParse("1") - injectorLimitMemory = resource.MustParse("256Mi") -) - -// imageNodeMap is a map of image/node pairs. -type imageNodeMap map[string][]string +// StartInjection initializes a Zarf injection into the cluster. +func (c *Cluster) StartInjection(ctx context.Context, tmpDir, imagesDir string, injectorSeedSrcs []string) error { + // Stop any previous running injection before starting. + err := c.StopInjection(ctx) + if err != nil { + return err + } -// StartInjectionMadness initializes a Zarf injection into the cluster. -func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imagesDir string, injectorSeedSrcs []string) error { spinner := message.NewProgressSpinner("Attempting to bootstrap the seed image into the cluster") defer spinner.Stop() - tmp := layout.InjectionMadnessPaths{ - SeedImagesDir: filepath.Join(tmpDir, "seed-images"), - // should already exist - InjectionBinary: filepath.Join(tmpDir, "zarf-injector"), - // gets created here - InjectorPayloadTarGz: filepath.Join(tmpDir, "payload.tar.gz"), - } - - if err := helpers.CreateDirectory(tmp.SeedImagesDir, helpers.ReadWriteExecuteUser); err != nil { - return fmt.Errorf("unable to create the seed images directory: %w", err) + resReq := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(".5"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, } - - var err error - var images imageNodeMap - var payloadConfigmaps []string - var sha256sum string - - findImagesCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) - defer cancel() - images, err = c.getImagesAndNodesForInjection(findImagesCtx) + injectorImage, injectorNodeName, err := c.getInjectorImageAndNode(ctx, resReq) if err != nil { return err } - if err = c.createInjectorConfigMap(ctx, tmp.InjectionBinary); err != nil { - return fmt.Errorf("unable to create the injector configmap: %w", err) - } - - service, err := c.createService(ctx) - if err != nil { - return fmt.Errorf("unable to create the injector service: %w", err) - } - config.ZarfSeedPort = fmt.Sprintf("%d", service.Spec.Ports[0].NodePort) - - _, err = c.loadSeedImages(imagesDir, tmp.SeedImagesDir, injectorSeedSrcs) + payloadCmNames, shasum, err := c.createPayloadConfigMaps(ctx, spinner, tmpDir, imagesDir, injectorSeedSrcs) if err != nil { - return fmt.Errorf("unable to load the injector seed image from the package: %w", err) - } - - if payloadConfigmaps, sha256sum, err = c.createPayloadConfigMaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { return fmt.Errorf("unable to generate the injector payload configmaps: %w", err) } - // https://regex101.com/r/eLS3at/1 - zarfImageRegex := regexp.MustCompile(`(?m)^127\.0\.0\.1:`) - - var injectorImage string - var injectorNode string - // Try to create an injector pod using an existing image in the cluster - for image, node := range images { - // Don't try to run against the seed image if this is a secondary zarf init run - if zarfImageRegex.MatchString(image) { - continue - } - spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) - injectorImage = image - injectorNode = node[0] - } - // Make sure the pod is not there first - // TODO: Explain why no grace period is given. - deleteGracePeriod := int64(0) - deletePolicy := metav1.DeletePropagationForeground - deleteOpts := metav1.DeleteOptions{ - GracePeriodSeconds: &deleteGracePeriod, - PropagationPolicy: &deletePolicy, - } - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "zarf-injector", - }, - }) + b, err := os.ReadFile(filepath.Join(tmpDir, "zarf-injector")) if err != nil { return err } - listOpts := metav1.ListOptions{ - LabelSelector: selector.String(), + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ZarfNamespaceName, + Name: "rust-binary", + }, + BinaryData: map[string][]byte{ + "zarf-injector": b, + }, } - err = c.Clientset.CoreV1().Pods(ZarfNamespaceName).DeleteCollection(ctx, deleteOpts, listOpts) + _, err = c.Clientset.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{}) if err != nil { return err } - pod, err := c.buildInjectionPod(injectorNode, injectorImage, payloadConfigmaps, sha256sum) + svc := &corev1.Service{ + TypeMeta: metav1.TypeMeta{ + APIVersion: corev1.SchemeGroupVersion.String(), + Kind: "Service", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: ZarfNamespaceName, + Name: "zarf-injector", + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + Port: int32(5000), + }, + }, + Selector: map[string]string{ + "app": "zarf-injector", + }, + }, + } + svc, err = c.Clientset.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("error making injection pod: %w", err) + return err } + // TODO: Remove use of passing data through global variables. + config.ZarfSeedPort = fmt.Sprintf("%d", svc.Spec.Ports[0].NodePort) - pod, err = c.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) + pod := buildInjectionPod(injectorNodeName, injectorImage, payloadCmNames, shasum, resReq) + _, err = c.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, metav1.CreateOptions{}) if err != nil { return fmt.Errorf("error creating pod in cluster: %w", err) } - objs := []object.ObjMetadata{ - { - GroupKind: schema.GroupKind{ - Kind: "Pod", - }, - Namespace: ZarfNamespaceName, - Name: pod.Name, - }, - } waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + err = pkgkubernetes.WaitForReadyRuntime(waitCtx, c.Watcher, []runtime.Object{pod}) if err != nil { return err } + spinner.Success() return nil - } -// StopInjectionMadness handles cleanup once the seed registry is up. -func (c *Cluster) StopInjectionMadness(ctx context.Context) error { - // Try to kill the injector pod now - selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "zarf-injector", - }, - }) - if err != nil { +// StopInjection handles cleanup once the seed registry is up. +func (c *Cluster) StopInjection(ctx context.Context) error { + err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Delete(ctx, "injector", metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { return err } - listOpts := metav1.ListOptions{ - LabelSelector: selector.String(), + err = c.Clientset.CoreV1().Services(ZarfNamespaceName).Delete(ctx, "zarf-injector", metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { + return err } - err = c.Clientset.CoreV1().Pods(ZarfNamespaceName).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts) - if err != nil { + err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, "rust-binary", metav1.DeleteOptions{}) + if err != nil && !kerrors.IsNotFound(err) { return err } - - // Remove the configmaps - selector, err = metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ + selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ "zarf-injector": "payload", }, @@ -191,7 +148,7 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { if err != nil { return err } - listOpts = metav1.ListOptions{ + listOpts := metav1.ListOptions{ LabelSelector: selector.String(), } err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).DeleteCollection(ctx, metav1.DeleteOptions{}, listOpts) @@ -199,89 +156,93 @@ func (c *Cluster) StopInjectionMadness(ctx context.Context) error { return err } - // Remove the injector service - err = c.Clientset.CoreV1().Services(ZarfNamespaceName).Delete(ctx, "zarf-injector", metav1.DeleteOptions{}) + // This is needed because labels were not present in payload config maps previously. + // Without this injector will fail if the config maps exist from a previous Zarf version. + cmList, err := c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + if err != nil { + return err + } + for _, cm := range cmList.Items { + if !strings.HasPrefix(cm.Name, "zarf-payload-") { + continue + } + err = c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, cm.Name, metav1.DeleteOptions{}) + if err != nil { + return err + } + } + + // TODO: Replace with wait package in the future. + err = wait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (bool, error) { + _, err := c.Clientset.CoreV1().Pods(ZarfNamespaceName).Get(ctx, "injector", metav1.GetOptions{}) + if kerrors.IsNotFound(err) { + return true, nil + } + return false, err + }) if err != nil { return err } return nil } -func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string) ([]transform.Image, error) { - seedImages := []transform.Image{} - localReferenceToDigest := make(map[string]string) +func (c *Cluster) createPayloadConfigMaps(ctx context.Context, spinner *message.Spinner, tmpDir, imagesDir string, injectorSeedSrcs []string) ([]string, string, error) { + tarPath := filepath.Join(tmpDir, "payload.tar.gz") + seedImagesDir := filepath.Join(tmpDir, "seed-images") + if err := helpers.CreateDirectory(seedImagesDir, helpers.ReadWriteExecuteUser); err != nil { + return nil, "", fmt.Errorf("unable to create the seed images directory: %w", err) + } - // Load the injector-specific images and save them as seed-images + localReferenceToDigest := map[string]string{} for _, src := range injectorSeedSrcs { ref, err := transform.ParseImageRef(src) if err != nil { - return nil, fmt.Errorf("failed to create ref for image %s: %w", src, err) + return nil, "", fmt.Errorf("failed to create ref for image %s: %w", src, err) } img, err := utils.LoadOCIImage(imagesDir, ref) if err != nil { - return nil, err + return nil, "", err } - if err := crane.SaveOCI(img, seedImagesDir); err != nil { - return nil, err + return nil, "", err } - - seedImages = append(seedImages, ref) - - // Get the image digest so we can set an annotation in the image.json later imgDigest, err := img.Digest() if err != nil { - return nil, err + return nil, "", err } - // This is done _without_ the domain (different from pull.go) since the injector only handles local images localReferenceToDigest[ref.Path+ref.TagOrDigest] = imgDigest.String() } - if err := utils.AddImageNameAnnotation(seedImagesDir, localReferenceToDigest); err != nil { - return nil, fmt.Errorf("unable to format OCI layout: %w", err) + return nil, "", fmt.Errorf("unable to format OCI layout: %w", err) } - return seedImages, nil -} - -func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { - var configMaps []string - // Chunk size has to accommodate base64 encoding & etcd 1MB limit tarFileList, err := filepath.Glob(filepath.Join(seedImagesDir, "*")) if err != nil { - return configMaps, "", err + return nil, "", err } - - // Create a tar archive of the injector payload if err := archiver.Archive(tarFileList, tarPath); err != nil { - return configMaps, "", err + return nil, "", err } - - chunks, sha256sum, err := helpers.ReadFileByChunks(tarPath, payloadChunkSize) + payloadChunkSize := 1024 * 768 + chunks, shasum, err := helpers.ReadFileByChunks(tarPath, payloadChunkSize) if err != nil { - return configMaps, "", err + return nil, "", err } - chunkCount := len(chunks) + cmNames := []string{} + for i, data := range chunks { + fileName := fmt.Sprintf("zarf-payload-%03d", i) - // Loop over all chunks and generate configmaps - for idx, data := range chunks { - // Create a cat-friendly filename - fileName := fmt.Sprintf("zarf-payload-%03d", idx) + spinner.Updatef("Adding archive binary configmap %d of %d to the cluster", i+1, len(chunks)) - spinner.Updatef("Adding archive binary configmap %d of %d to the cluster", idx+1, chunkCount) - - // Attempt to create the configmap in the cluster - // TODO: Replace with create or update. - err := c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, fileName, metav1.DeleteOptions{}) - if err != nil && !kerrors.IsNotFound(err) { - return nil, "", err - } cm := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ - Name: fileName, Namespace: ZarfNamespaceName, + Name: fileName, + Labels: map[string]string{ + "zarf-injector": "payload", + }, }, BinaryData: map[string][]byte{ fileName: data, @@ -291,94 +252,81 @@ func (c *Cluster) createPayloadConfigMaps(ctx context.Context, seedImagesDir, ta if err != nil { return nil, "", err } - - // Add the configmap to the configmaps slice for later usage in the pod - configMaps = append(configMaps, fileName) + cmNames = append(cmNames, fileName) // Give the control plane a 250ms buffer between each configmap time.Sleep(250 * time.Millisecond) } - - return configMaps, sha256sum, nil + return cmNames, shasum, nil } -func (c *Cluster) createInjectorConfigMap(ctx context.Context, binaryPath string) error { - name := "rust-binary" - // TODO: Replace with a create or update. - err := c.Clientset.CoreV1().ConfigMaps(ZarfNamespaceName).Delete(ctx, name, metav1.DeleteOptions{}) - if err != nil && !kerrors.IsNotFound(err) { - return err - } - b, err := os.ReadFile(binaryPath) +// getImagesAndNodesForInjection checks for images on schedulable nodes within a cluster. +func (c *Cluster) getInjectorImageAndNode(ctx context.Context, resReq corev1.ResourceRequirements) (string, string, error) { + // Regex for Zarf seed image + zarfImageRegex, err := regexp.Compile(`(?m)^127\.0\.0\.1:`) if err != nil { - return err + return "", "", err } - configData := map[string][]byte{ - "zarf-injector": b, - } - configMap := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: ZarfNamespaceName, - }, - BinaryData: configData, + listOpts := metav1.ListOptions{ + FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), } - _, err = c.Clientset.CoreV1().ConfigMaps(configMap.Namespace).Create(ctx, configMap, metav1.CreateOptions{}) + podList, err := c.Clientset.CoreV1().Pods(corev1.NamespaceAll).List(ctx, listOpts) if err != nil { - return err + return "", "", err } - return nil + for _, pod := range podList.Items { + nodeDetails, err := c.Clientset.CoreV1().Nodes().Get(ctx, pod.Spec.NodeName, metav1.GetOptions{}) + if err != nil { + return "", "", err + } + if nodeDetails.Status.Allocatable.Cpu().Cmp(resReq.Requests[corev1.ResourceCPU]) < 0 || + nodeDetails.Status.Allocatable.Memory().Cmp(resReq.Requests[corev1.ResourceMemory]) < 0 { + continue + } + if hasBlockingTaints(nodeDetails.Spec.Taints) { + continue + } + for _, container := range pod.Spec.Containers { + if zarfImageRegex.MatchString(container.Image) { + continue + } + return container.Image, pod.Spec.NodeName, nil + } + for _, container := range pod.Spec.InitContainers { + if zarfImageRegex.MatchString(container.Image) { + continue + } + return container.Image, pod.Spec.NodeName, nil + } + for _, container := range pod.Spec.EphemeralContainers { + if zarfImageRegex.MatchString(container.Image) { + continue + } + return container.Image, pod.Spec.NodeName, nil + } + } + return "", "", fmt.Errorf("no suitable injector image or node exists") } -func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { - svc := &corev1.Service{ - TypeMeta: metav1.TypeMeta{ - APIVersion: corev1.SchemeGroupVersion.String(), - Kind: "Service", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "zarf-injector", - Namespace: ZarfNamespaceName, - }, - Spec: corev1.ServiceSpec{ - Type: corev1.ServiceTypeNodePort, - Ports: []corev1.ServicePort{ - { - Port: int32(5000), - }, - }, - Selector: map[string]string{ - "app": "zarf-injector", - }, - }, - } - // TODO: Replace with create or update - err := c.Clientset.CoreV1().Services(svc.Namespace).Delete(ctx, svc.Name, metav1.DeleteOptions{}) - if err != nil && !kerrors.IsNotFound(err) { - return nil, err - } - svc, err = c.Clientset.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}) - if err != nil { - return nil, err +func hasBlockingTaints(taints []corev1.Taint) bool { + for _, taint := range taints { + if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { + return true + } } - return svc, nil + return false } -// buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. -func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []string, payloadShasum string) (*corev1.Pod, error) { +func buildInjectionPod(nodeName, image string, payloadCmNames []string, shasum string, resReq corev1.ResourceRequirements) *corev1.Pod { executeMode := int32(0777) - // Generate a UUID to append to the pod name. - // This prevents collisions where `zarf init` is ran back to back and a previous injector pod still exists. - uuid := uuid.New().String()[:16] - pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), Kind: "Pod", }, ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("injector-%s", uuid), + Name: "injector", Namespace: ZarfNamespaceName, Labels: map[string]string{ "app": "zarf-injector", @@ -386,26 +334,16 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri }, }, Spec: corev1.PodSpec{ - NodeName: node, - // Do not try to restart the pod as it will be deleted/re-created instead + NodeName: nodeName, + // Do not try to restart the pod as it will be deleted/re-created instead. RestartPolicy: corev1.RestartPolicyNever, Containers: []corev1.Container{ { - Name: "injector", - - // An existing image already present on the cluster - Image: image, - - // PullIfNotPresent because some distros provide a way (even in airgap) to pull images from local or direct-connected registries + Name: "injector", + Image: image, ImagePullPolicy: corev1.PullIfNotPresent, - - // This directory's contents come from the init container output - WorkingDir: "/zarf-init", - - // Call the injector with shasum of the tarball - Command: []string{"/zarf-init/zarf-injector", payloadShasum}, - - // Shared mount between the init and regular containers + WorkingDir: "/zarf-init", + Command: []string{"/zarf-init/zarf-injector", shasum}, VolumeMounts: []corev1.VolumeMount{ { Name: "init", @@ -417,31 +355,18 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri MountPath: "/zarf-seed", }, }, - - // Readiness probe to optimize the pod startup time ReadinessProbe: &corev1.Probe{ PeriodSeconds: 2, SuccessThreshold: 1, FailureThreshold: 10, ProbeHandler: corev1.ProbeHandler{ HTTPGet: &corev1.HTTPGetAction{ - Path: "/v2/", // path to health check - Port: intstr.FromInt(5000), // port to health check + Path: "/v2/", + Port: intstr.FromInt(5000), }, }, }, - - // Keep resources as light as possible as we aren't actually running the container's other binaries - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: injectorRequestedCPU, - corev1.ResourceMemory: injectorRequestedMemory, - }, - Limits: corev1.ResourceList{ - corev1.ResourceCPU: injectorLimitCPU, - corev1.ResourceMemory: injectorLimitMemory, - }, - }, + Resources: resReq, }, }, Volumes: []corev1.Volume{ @@ -468,9 +393,7 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri }, } - // Iterate over all the payload configmaps and add their mounts. - for _, filename := range payloadConfigmaps { - // Create the configmap volume from the given filename. + for _, filename := range payloadCmNames { pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ Name: filename, VolumeSource: corev1.VolumeSource{ @@ -481,8 +404,6 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri }, }, }) - - // Create the volume mount to place the new volume in the working directory pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ Name: filename, MountPath: fmt.Sprintf("/zarf-init/%s", filename), @@ -490,72 +411,5 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri }) } - return pod, nil -} - -// getImagesAndNodesForInjection checks for images on schedulable nodes within a cluster. -func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeMap, error) { - result := make(imageNodeMap) - - timer := time.NewTimer(0) - defer timer.Stop() - - for { - select { - case <-ctx.Done(): - return nil, fmt.Errorf("get image list timed-out: %w", ctx.Err()) - case <-timer.C: - listOpts := metav1.ListOptions{ - FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), - } - podList, err := c.Clientset.CoreV1().Pods(corev1.NamespaceAll).List(ctx, listOpts) - if err != nil { - return nil, fmt.Errorf("unable to get the list of %q pods in the cluster: %w", corev1.PodRunning, err) - } - - for _, pod := range podList.Items { - nodeName := pod.Spec.NodeName - - nodeDetails, err := c.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("unable to get the node %q: %w", nodeName, err) - } - - if nodeDetails.Status.Allocatable.Cpu().Cmp(injectorRequestedCPU) < 0 || - nodeDetails.Status.Allocatable.Memory().Cmp(injectorRequestedMemory) < 0 { - continue - } - - if hasBlockingTaints(nodeDetails.Spec.Taints) { - continue - } - - for _, container := range pod.Spec.InitContainers { - result[container.Image] = append(result[container.Image], nodeName) - } - for _, container := range pod.Spec.Containers { - result[container.Image] = append(result[container.Image], nodeName) - } - for _, container := range pod.Spec.EphemeralContainers { - result[container.Image] = append(result[container.Image], nodeName) - } - } - - if len(result) > 0 { - return result, nil - } - - message.Debug("No images found on any node. Retrying...") - timer.Reset(2 * time.Second) - } - } -} - -func hasBlockingTaints(taints []corev1.Taint) bool { - for _, taint := range taints { - if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { - return true - } - } - return false + return pod } diff --git a/src/pkg/cluster/injector_test.go b/src/pkg/cluster/injector_test.go index 291eb092ef..07abc3bb6c 100644 --- a/src/pkg/cluster/injector_test.go +++ b/src/pkg/cluster/injector_test.go @@ -11,69 +11,152 @@ import ( "path/filepath" "strings" "testing" - "time" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/random" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" -) - -func TestCreateInjectorConfigMap(t *testing.T) { - t.Parallel() + k8stesting "k8s.io/client-go/testing" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" - binData := []byte("foobar") - binPath := filepath.Join(t.TempDir(), "bin") - err := os.WriteFile(binPath, binData, 0o644) - require.NoError(t, err) + pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" +) +func TestInjector(t *testing.T) { + ctx := context.Background() cs := fake.NewSimpleClientset() c := &Cluster{ Clientset: cs, + Watcher: pkgkubernetes.NewImmediateWatcher(status.CurrentStatus), } + cs.PrependReactor("delete-collection", "configmaps", func(action k8stesting.Action) (bool, runtime.Object, error) { + delAction := action.(k8stesting.DeleteCollectionActionImpl) + if delAction.GetListRestrictions().Labels.String() != "zarf-injector=payload" { + return false, nil, nil + } + gvr := delAction.Resource + gvk := delAction.Resource.GroupVersion().WithKind("ConfigMap") + list, err := cs.Tracker().List(gvr, gvk, delAction.Namespace) + require.NoError(t, err) + for _, cm := range list.(*corev1.ConfigMapList).Items { + v, ok := cm.Labels["zarf-injector"] + if !ok { + continue + } + if v != "payload" { + continue + } + err = cs.Tracker().Delete(gvr, delAction.Namespace, cm.Name) + require.NoError(t, err) + } + return true, nil, nil + }) - ctx := context.Background() - for i := 0; i < 2; i++ { - err = c.createInjectorConfigMap(ctx, binPath) + // Setup nodes and pods with images + node := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Allocatable: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("10"), + corev1.ResourceMemory: resource.MustParse("100Gi"), + }, + }, + } + _, err := cs.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}) + require.NoError(t, err) + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "good", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + NodeName: "node1", + Containers: []corev1.Container{ + { + Image: "ubuntu:latest", + }, + }, + }, + } + _, err = cs.CoreV1().Pods(pod.ObjectMeta.Namespace).Create(ctx, pod, metav1.CreateOptions{}) + require.NoError(t, err) + + err = c.StopInjection(ctx) + require.NoError(t, err) + + for range 2 { + tmpDir := t.TempDir() + binData := []byte("foobar") + err := os.WriteFile(filepath.Join(tmpDir, "zarf-injector"), binData, 0o644) require.NoError(t, err) - cm, err := cs.CoreV1().ConfigMaps(ZarfNamespaceName).Get(ctx, "rust-binary", metav1.GetOptions{}) + + idx, err := random.Index(1, 1, 1) + require.NoError(t, err) + _, err = layout.Write(filepath.Join(tmpDir, "seed-images"), idx) require.NoError(t, err) - require.Equal(t, binData, cm.BinaryData["zarf-injector"]) - } -} -func TestCreateService(t *testing.T) { - t.Parallel() + err = c.StartInjection(ctx, tmpDir, t.TempDir(), nil) + require.NoError(t, err) - cs := fake.NewSimpleClientset() - c := &Cluster{ - Clientset: cs, - } + podList, err := cs.CoreV1().Pods(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, podList.Items, 1) + require.Equal(t, "injector", podList.Items[0].ObjectMeta.Name) - expected, err := os.ReadFile("./testdata/expected-injection-service.json") - require.NoError(t, err) - ctx := context.Background() - for i := 0; i < 2; i++ { - _, err := c.createService(ctx) + svcList, err := cs.CoreV1().Services(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, svcList.Items, 1) + expected, err := os.ReadFile("./testdata/expected-injection-service.json") require.NoError(t, err) svc, err := cs.CoreV1().Services(ZarfNamespaceName).Get(ctx, "zarf-injector", metav1.GetOptions{}) require.NoError(t, err) b, err := json.Marshal(svc) require.NoError(t, err) require.Equal(t, strings.TrimSpace(string(expected)), string(b)) + + cmList, err := cs.CoreV1().ConfigMaps(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Len(t, cmList.Items, 2) + cm, err := cs.CoreV1().ConfigMaps(ZarfNamespaceName).Get(ctx, "rust-binary", metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, binData, cm.BinaryData["zarf-injector"]) } + + err = c.StopInjection(ctx) + require.NoError(t, err) + + podList, err := cs.CoreV1().Pods(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Empty(t, podList.Items) + svcList, err := cs.CoreV1().Services(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Empty(t, svcList.Items) + cmList, err := cs.CoreV1().ConfigMaps(ZarfNamespaceName).List(ctx, metav1.ListOptions{}) + require.NoError(t, err) + require.Empty(t, cmList.Items) } func TestBuildInjectionPod(t *testing.T) { t.Parallel() - c := &Cluster{} - pod, err := c.buildInjectionPod("injection-node", "docker.io/library/ubuntu:latest", []string{"foo", "bar"}, "shasum") - require.NoError(t, err) - require.Contains(t, pod.Name, "injector-") - // Replace the random UUID in the pod name with a fixed placeholder for consistent comparison. - pod.ObjectMeta.Name = "injector-UUID" + resReq := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(".5"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, + } + pod := buildInjectionPod("injection-node", "docker.io/library/ubuntu:latest", []string{"foo", "bar"}, "shasum", resReq) + require.Equal(t, "injector", pod.Name) b, err := json.Marshal(pod) require.NoError(t, err) expected, err := os.ReadFile("./testdata/expected-injection-pod.json") @@ -81,7 +164,7 @@ func TestBuildInjectionPod(t *testing.T) { require.Equal(t, strings.TrimSpace(string(expected)), string(b)) } -func TestImagesAndNodesForInjection(t *testing.T) { +func TestGetInjectorImageAndNode(t *testing.T) { t.Parallel() ctx := context.Background() @@ -185,14 +268,18 @@ func TestImagesAndNodesForInjection(t *testing.T) { require.NoError(t, err) } - getCtx, getCancel := context.WithTimeout(ctx, 1*time.Second) - defer getCancel() - result, err := c.getImagesAndNodesForInjection(getCtx) - require.NoError(t, err) - expected := imageNodeMap{ - "pod-2-init": []string{"good"}, - "pod-2-container": []string{"good"}, - "pod-2-ephemeral": []string{"good"}, + resReq := corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(".5"), + corev1.ResourceMemory: resource.MustParse("64Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("1"), + corev1.ResourceMemory: resource.MustParse("256Mi"), + }, } - require.Equal(t, expected, result) + image, node, err := c.getInjectorImageAndNode(ctx, resReq) + require.NoError(t, err) + require.Equal(t, "pod-2-container", image) + require.Equal(t, "good", node) } diff --git a/src/pkg/cluster/testdata/expected-injection-pod.json b/src/pkg/cluster/testdata/expected-injection-pod.json index a0c41072d3..30f2e5b1f1 100644 --- a/src/pkg/cluster/testdata/expected-injection-pod.json +++ b/src/pkg/cluster/testdata/expected-injection-pod.json @@ -1 +1 @@ -{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector-UUID","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} +{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go index decc7a82e1..e68f77b551 100644 --- a/src/pkg/layout/package.go +++ b/src/pkg/layout/package.go @@ -38,13 +38,6 @@ type PackagePaths struct { isLegacyLayout bool } -// InjectionMadnessPaths contains paths for injection madness. -type InjectionMadnessPaths struct { - InjectionBinary string - SeedImagesDir string - InjectorPayloadTarGz string -} - // New returns a new PackagePaths struct. func New(baseDir string) *PackagePaths { return &PackagePaths{ diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 74661b753f..867706d957 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -253,7 +253,7 @@ func (p *Packager) deployInitComponent(ctx context.Context, component types.Zarf // Before deploying the seed registry, start the injector if isSeedRegistry { - err := p.cluster.StartInjectionMadness(ctx, p.layout.Base, p.layout.Images.Base, component.Images) + err := p.cluster.StartInjection(ctx, p.layout.Base, p.layout.Images.Base, component.Images) if err != nil { return nil, err } @@ -266,7 +266,7 @@ func (p *Packager) deployInitComponent(ctx context.Context, component types.Zarf // Do cleanup for when we inject the seed registry during initialization if isSeedRegistry { - if err := p.cluster.StopInjectionMadness(ctx); err != nil { + if err := p.cluster.StopInjection(ctx); err != nil { return nil, fmt.Errorf("unable to seed the Zarf Registry: %w", err) } } From 425da091cb9ef5f1293455318a4b2153dcc7a5ca Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 28 Jun 2024 15:30:41 +0000 Subject: [PATCH 101/132] remove helpers v1 --- go.mod | 1 - go.sum | 2 -- src/internal/agent/hooks/flux-helmrepo.go | 2 +- src/internal/agent/hooks/flux-ocirepo.go | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 6195416cbf..403f20d511 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,6 @@ require ( github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f github.com/anchore/stereoscope v0.0.1 github.com/anchore/syft v0.100.0 - github.com/defenseunicorns/pkg/helpers v1.1.3 github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 github.com/defenseunicorns/pkg/kubernetes v0.0.1 github.com/defenseunicorns/pkg/oci v1.0.1 diff --git a/go.sum b/go.sum index 2cdb4bd07f..16f4f5dfc5 100644 --- a/go.sum +++ b/go.sum @@ -597,8 +597,6 @@ github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gwevOZ0fxT2nzM9hrtdPbsiOHjFqDRIYMzJHba3/G6Q= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= -github.com/defenseunicorns/pkg/helpers v1.1.3 h1:EVVuniq02qfAouR//AT0eoCngLWfFORj8H6+pI8M7uo= -github.com/defenseunicorns/pkg/helpers v1.1.3/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= github.com/defenseunicorns/pkg/kubernetes v0.0.1 h1:HNQBV6XXFvlDvFdOCCWam0/LCgq67M+ggQKiRIoM2vU= diff --git a/src/internal/agent/hooks/flux-helmrepo.go b/src/internal/agent/hooks/flux-helmrepo.go index f4514b639d..750eeb0406 100644 --- a/src/internal/agent/hooks/flux-helmrepo.go +++ b/src/internal/agent/hooks/flux-helmrepo.go @@ -10,7 +10,7 @@ import ( "fmt" "strings" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" diff --git a/src/internal/agent/hooks/flux-ocirepo.go b/src/internal/agent/hooks/flux-ocirepo.go index 1a811c3319..c026b2e7cc 100644 --- a/src/internal/agent/hooks/flux-ocirepo.go +++ b/src/internal/agent/hooks/flux-ocirepo.go @@ -9,7 +9,7 @@ import ( "encoding/json" "fmt" - "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent/operations" From 26aad35b2377617597e590189ad7ed63c9faee69 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 25 Jun 2024 17:46:53 +0200 Subject: [PATCH 102/132] refactor: remove use message.Fatal in cmd package --- src/cmd/common/utils.go | 18 --- src/cmd/connect.go | 29 ++-- src/cmd/destroy.go | 18 ++- src/cmd/dev.go | 87 +++++----- src/cmd/initialize.go | 24 ++- src/cmd/internal.go | 63 +++++--- src/cmd/package.go | 148 ++++++++++-------- src/cmd/tools/zarf.go | 91 +++++++---- src/config/lang/english.go | 67 ++------ src/pkg/cluster/cluster.go | 8 +- src/pkg/cluster/state.go | 4 - src/test/e2e/02_component_actions_test.go | 2 +- src/test/e2e/09_component_compose_test.go | 2 +- src/test/e2e/22_git_and_gitops_test.go | 18 ++- .../e2e/31_checksum_and_signature_test.go | 2 +- 15 files changed, 294 insertions(+), 287 deletions(-) diff --git a/src/cmd/common/utils.go b/src/cmd/common/utils.go index 26f2ce6707..1da7e456ee 100644 --- a/src/cmd/common/utils.go +++ b/src/cmd/common/utils.go @@ -4,13 +4,6 @@ // Package common handles command configuration across all commands package common -import ( - "context" - - "github.com/defenseunicorns/zarf/src/pkg/cluster" - "github.com/defenseunicorns/zarf/src/pkg/message" -) - // SetBaseDirectory sets the base directory. This is a directory with a zarf.yaml. func SetBaseDirectory(args []string) string { if len(args) > 0 { @@ -18,14 +11,3 @@ func SetBaseDirectory(args []string) string { } return "." } - -// NewClusterOrDie creates a new Cluster instance and waits for the cluster to be ready or throws a fatal error. -func NewClusterOrDie(ctx context.Context) *cluster.Cluster { - timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) - if err != nil { - message.Fatalf(err, "Failed to connect to cluster") - } - return c -} diff --git a/src/cmd/connect.go b/src/cmd/connect.go index e2f3884183..7111abf813 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -5,10 +5,9 @@ package cmd import ( + "context" "fmt" - "os" - "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -29,15 +28,16 @@ var ( Aliases: []string{"c"}, Short: lang.CmdConnectShort, Long: lang.CmdConnectLong, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var target string if len(args) > 0 { target = args[0] } spinner := message.NewProgressSpinner(lang.CmdConnectPreparingTunnel, target) + defer spinner.Stop() c, err := cluster.NewCluster() if err != nil { - spinner.Fatalf(err, lang.CmdConnectErrCluster, err.Error()) + return err } ctx := cmd.Context() @@ -50,7 +50,7 @@ var ( tunnel, err = c.Connect(ctx, target) } if err != nil { - spinner.Fatalf(err, lang.CmdConnectErrService, err.Error()) + return fmt.Errorf("unable to connect to the service: %w", err) } defer tunnel.Close() @@ -74,9 +74,9 @@ var ( case <-ctx.Done(): spinner.Successf(lang.CmdConnectTunnelClosed, url) case err = <-tunnel.ErrChan(): - spinner.Fatalf(err, lang.CmdConnectErrService, err.Error()) + return fmt.Errorf("lost connection to the service: %w", err) } - os.Exit(0) + return nil }, } @@ -84,11 +84,18 @@ var ( Use: "list", Aliases: []string{"l"}, Short: lang.CmdConnectListShort, - Run: func(cmd *cobra.Command, _ []string) { - ctx := cmd.Context() - if err := common.NewClusterOrDie(ctx).PrintConnectTable(ctx); err != nil { - message.Fatal(err, err.Error()) + RunE: func(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + err = c.PrintConnectTable(cmd.Context()) + if err != nil { + return err } + return nil }, } ) diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index 0034186089..0faa1d5c3e 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -5,15 +5,17 @@ package cmd import ( + "context" "errors" + "fmt" "os" "regexp" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/helm" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" @@ -28,9 +30,14 @@ var destroyCmd = &cobra.Command{ Aliases: []string{"d"}, Short: lang.CmdDestroyShort, Long: lang.CmdDestroyLong, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() - c := common.NewClusterOrDie(ctx) + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } // NOTE: If 'zarf init' failed to deploy the k3s component (or if we're looking at the wrong kubeconfig) // there will be no zarf-state to load and the struct will be empty. In these cases, if we can find @@ -45,7 +52,7 @@ var destroyCmd = &cobra.Command{ // Check if we have the scripts to destroy everything fileInfo, err := os.Stat(config.ZarfCleanupScriptsPath) if errors.Is(err, os.ErrNotExist) || !fileInfo.IsDir() { - message.Fatalf(lang.CmdDestroyErrNoScriptPath, config.ZarfCleanupScriptsPath) + return fmt.Errorf("unable to find the folder %s which has the scripts to cleanup the cluster. Please double-check you have the right kube-context", config.ZarfCleanupScriptsPath) } // Run all the scripts! @@ -73,12 +80,13 @@ var destroyCmd = &cobra.Command{ // If Zarf didn't deploy the cluster, only delete the ZarfNamespace if err := c.DeleteZarfNamespace(ctx); err != nil { - message.Fatal(err, err.Error()) + return err } // Remove zarf agent labels and secrets from namespaces Zarf doesn't manage c.StripZarfLabelsAndSecretsFromNamespaces(ctx) } + return nil }, } diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 3714759c0d..7560b43651 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -5,6 +5,7 @@ package cmd import ( + "errors" "fmt" "io" "os" @@ -39,7 +40,7 @@ var devDeployCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevDeployShort, Long: lang.CmdDevDeployLong, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() @@ -53,8 +54,9 @@ var devDeployCmd = &cobra.Command{ defer pkgClient.ClearTempPaths() if err := pkgClient.DevDeploy(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdDevDeployErr, err.Error()) + return fmt.Errorf("failed to dev deploy: %w", err) } + return nil }, } @@ -64,7 +66,7 @@ var devGenerateCmd = &cobra.Command{ Args: cobra.ExactArgs(1), Short: lang.CmdDevGenerateShort, Example: lang.CmdDevGenerateExample, - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { pkgConfig.GenerateOpts.Name = args[0] pkgConfig.CreateOpts.BaseDir = "." @@ -73,9 +75,11 @@ var devGenerateCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if err := pkgClient.Generate(); err != nil { - message.Fatalf(err, err.Error()) + err := pkgClient.Generate() + if err != nil { + return err } + return nil }, } @@ -84,13 +88,13 @@ var devTransformGitLinksCmd = &cobra.Command{ Aliases: []string{"p"}, Short: lang.CmdDevPatchGitShort, Args: cobra.ExactArgs(2), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { host, fileName := args[0], args[1] // Read the contents of the given file content, err := os.ReadFile(fileName) if err != nil { - message.Fatalf(err, lang.CmdDevPatchGitFileReadErr, fileName) + return fmt.Errorf("unable to read the file %s: %w", fileName, err) } pkgConfig.InitOpts.GitServer.Address = host @@ -108,17 +112,17 @@ var devTransformGitLinksCmd = &cobra.Command{ Message: fmt.Sprintf(lang.CmdDevPatchGitOverwritePrompt, fileName), } if err := survey.AskOne(prompt, &confirm); err != nil { - message.Fatalf(nil, lang.CmdDevPatchGitOverwriteErr, err.Error()) + return fmt.Errorf("confirm overwrite canceled: %w", err) } if confirm { // Overwrite the file err = os.WriteFile(fileName, []byte(processedText), helpers.ReadAllWriteUser) if err != nil { - message.Fatal(err, lang.CmdDevPatchGitFileWriteErr) + return fmt.Errorf("unable to write the changes back to the file: %w", err) } } - + return nil }, } @@ -127,7 +131,9 @@ var devSha256SumCmd = &cobra.Command{ Aliases: []string{"s"}, Short: lang.CmdDevSha256sumShort, Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { + hashErr := errors.New("unable to compute the SHA256SUM hash") + fileName := args[0] var tmp string @@ -139,7 +145,7 @@ var devSha256SumCmd = &cobra.Command{ fileBase, err := helpers.ExtractBasePathFromURL(fileName) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } if fileBase == "" { @@ -148,13 +154,13 @@ var devSha256SumCmd = &cobra.Command{ tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } downloadPath := filepath.Join(tmp, fileBase) err = utils.DownloadToFile(fileName, downloadPath, "") if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } fileName = downloadPath @@ -166,7 +172,7 @@ var devSha256SumCmd = &cobra.Command{ if tmp == "" { tmp, err = utils.MakeTempDir(config.CommonOptions.TempDirectory) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } defer os.RemoveAll(tmp) } @@ -175,7 +181,7 @@ var devSha256SumCmd = &cobra.Command{ err = archiver.Extract(fileName, extractPath, tmp) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } fileName = extractedFile @@ -183,17 +189,16 @@ var devSha256SumCmd = &cobra.Command{ data, err = os.Open(fileName) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) + return errors.Join(hashErr, err) } defer data.Close() - var hash string - hash, err = helpers.GetSHA256Hash(data) + hash, err := helpers.GetSHA256Hash(data) if err != nil { - message.Fatalf(err, lang.CmdDevSha256sumHashErr, err.Error()) - } else { - fmt.Println(hash) + return errors.Join(hashErr, err) } + fmt.Println(hash) + return nil }, } @@ -203,7 +208,7 @@ var devFindImagesCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevFindImagesShort, Long: lang.CmdDevFindImagesLong, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() @@ -216,8 +221,9 @@ var devFindImagesCmd = &cobra.Command{ defer pkgClient.ClearTempPaths() if _, err := pkgClient.FindImages(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdDevFindImagesErr, err.Error()) + return fmt.Errorf("unable to find images: %w", err) } + return nil }, } @@ -227,18 +233,18 @@ var devGenConfigFileCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevGenerateConfigShort, Long: lang.CmdDevGenerateConfigLong, - Run: func(_ *cobra.Command, args []string) { - fileName := "zarf-config.toml" - + RunE: func(_ *cobra.Command, args []string) error { // If a filename was provided, use that + fileName := "zarf-config.toml" if len(args) > 0 { fileName = args[0] } v := common.GetViper() if err := v.SafeWriteConfigAs(fileName); err != nil { - message.Fatalf(err, lang.CmdDevGenerateConfigErr, fileName) + return fmt.Errorf("unable to write the config file %s, make sure the file doesn't already exist: %w", fileName, err) } + return nil }, } @@ -285,14 +291,8 @@ func init() { // use the package create config for this and reset it here to avoid overwriting the config.CreateOptions.SetVariables devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) - err := devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") - if err != nil { - message.Fatal(err, err.Error()) - } - err = devFindImagesCmd.Flags().MarkHidden("set") - if err != nil { - message.Fatal(err, err.Error()) - } + devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") + devFindImagesCmd.Flags().MarkHidden("set") devFindImagesCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) @@ -340,16 +340,7 @@ func bindDevGenerateFlags(_ *viper.Viper) { generateFlags.StringVar(&pkgConfig.GenerateOpts.Output, "output-directory", "", "Output directory for the generated zarf.yaml") generateFlags.StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) - err := devGenerateCmd.MarkFlagRequired("url") - if err != nil { - message.Fatal(err, err.Error()) - } - err = devGenerateCmd.MarkFlagRequired("version") - if err != nil { - message.Fatal(err, err.Error()) - } - err = devGenerateCmd.MarkFlagRequired("output-directory") - if err != nil { - message.Fatal(err, err.Error()) - } + devGenerateCmd.MarkFlagRequired("url") + devGenerateCmd.MarkFlagRequired("version") + devGenerateCmd.MarkFlagRequired("output-directory") } diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index 75a8a049e0..5a680b7836 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -36,12 +36,12 @@ var initCmd = &cobra.Command{ Short: lang.CmdInitShort, Long: lang.CmdInitLong, Example: lang.CmdInitExample, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { zarfLogo := message.GetLogo() _, _ = fmt.Fprintln(os.Stderr, zarfLogo) if err := validateInitFlags(); err != nil { - message.Fatal(err, lang.CmdInitErrFlags) + return fmt.Errorf("invalid command flags were provided: %w", err) } // Continue running package deploy for all components like any other package @@ -51,12 +51,12 @@ var initCmd = &cobra.Command{ // Try to use an init-package in the executable directory if none exist in current working directory var err error if pkgConfig.PkgOpts.PackageSource, err = findInitPackage(cmd.Context(), initPackageName); err != nil { - message.Fatal(err, err.Error()) + return err } src, err := sources.New(&pkgConfig.PkgOpts) if err != nil { - message.Fatal(err, err.Error()) + return err } v := common.GetViper() @@ -66,12 +66,11 @@ var initCmd = &cobra.Command{ pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - ctx := cmd.Context() - - err = pkgClient.Deploy(ctx) + err = pkgClient.Deploy(cmd.Context()) if err != nil { - message.Fatal(err, err.Error()) + return err } + return nil }, } @@ -94,7 +93,7 @@ func findInitPackage(ctx context.Context, initPackageName string) (string, error // Create the cache directory if it doesn't exist if helpers.InvalidPath(config.GetAbsCachePath()) { if err := helpers.CreateDirectory(config.GetAbsCachePath(), helpers.ReadExecuteAllWriteUser); err != nil { - message.Fatalf(err, lang.CmdInitErrUnableCreateCache, config.GetAbsCachePath()) + return "", fmt.Errorf("unable to create the cache directory %s: %w", config.GetAbsCachePath(), err) } } @@ -107,10 +106,9 @@ func findInitPackage(ctx context.Context, initPackageName string) (string, error downloadCacheTarget, err := downloadInitPackage(ctx, config.GetAbsCachePath()) if err != nil { if errors.Is(err, lang.ErrInitNotFound) { - message.Fatal(err, err.Error()) - } else { - message.Fatalf(err, lang.CmdInitErrDownload, err.Error()) + return "", err } + return "", fmt.Errorf("failed to download the init package: %w", err) } return downloadCacheTarget, nil } @@ -134,7 +132,7 @@ func downloadInitPackage(ctx context.Context, cacheDirectory string) (string, er Message: lang.CmdInitPullConfirm, } if err := survey.AskOne(prompt, &confirmDownload); err != nil { - return "", fmt.Errorf(lang.ErrConfirmCancel, err.Error()) + return "", fmt.Errorf("confirm download canceled: %w", err) } } diff --git a/src/cmd/internal.go b/src/cmd/internal.go index ceca2d612e..b385564151 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "encoding/json" "fmt" "os" @@ -16,6 +17,7 @@ import ( "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent" "github.com/defenseunicorns/zarf/src/internal/packager/git" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" "github.com/invopop/jsonschema" @@ -55,7 +57,7 @@ var httpProxyCmd = &cobra.Command{ var genCLIDocs = &cobra.Command{ Use: "gen-cli-docs", Short: lang.CmdInternalGenerateCliDocsShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { // Don't include the datestamp in the output rootCmd.DisableAutoGenTag = true @@ -114,10 +116,10 @@ var genCLIDocs = &cobra.Command{ } if err := os.RemoveAll("./site/src/content/docs/commands"); err != nil { - message.Fatalf(lang.CmdInternalGenerateCliDocsErr, err.Error()) + return err } if err := os.Mkdir("./site/src/content/docs/commands", 0775); err != nil { - message.Fatalf(lang.CmdInternalGenerateCliDocsErr, err.Error()) + return err } var prependTitle = func(s string) string { @@ -147,10 +149,10 @@ tableOfContents: false } if err := doc.GenMarkdownTreeCustom(rootCmd, "./site/src/content/docs/commands", prependTitle, linkHandler); err != nil { - message.Fatalf(lang.CmdInternalGenerateCliDocsErr, err.Error()) - } else { - message.Success(lang.CmdInternalGenerateCliDocsSuccess) + return err } + message.Success(lang.CmdInternalGenerateCliDocsSuccess) + return nil }, } @@ -158,14 +160,15 @@ var genConfigSchemaCmd = &cobra.Command{ Use: "gen-config-schema", Aliases: []string{"gc"}, Short: lang.CmdInternalConfigSchemaShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { reflector := jsonschema.Reflector(jsonschema.Reflector{ExpandedStruct: true}) schema := reflector.Reflect(&types.ZarfPackage{}) output, err := json.MarshalIndent(schema, "", " ") if err != nil { - message.Fatal(err, lang.CmdInternalConfigSchemaErr) + return fmt.Errorf("unable to generate the Zarf config schema: %w", err) } fmt.Print(string(output) + "\n") + return nil }, } @@ -179,13 +182,14 @@ var genTypesSchemaCmd = &cobra.Command{ Use: "gen-types-schema", Aliases: []string{"gt"}, Short: lang.CmdInternalTypesSchemaShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { schema := jsonschema.Reflect(&zarfTypes{}) output, err := json.MarshalIndent(schema, "", " ") if err != nil { - message.Fatal(err, lang.CmdInternalTypesSchemaErr) + return fmt.Errorf("unable to generate the JSON schema for the Zarf types DeployedPackage, ZarfPackage, and ZarfState: %w", err) } fmt.Print(string(output) + "\n") + return nil }, } @@ -193,19 +197,23 @@ var createReadOnlyGiteaUser = &cobra.Command{ Use: "create-read-only-gitea-user", Short: lang.CmdInternalCreateReadOnlyGiteaUserShort, Long: lang.CmdInternalCreateReadOnlyGiteaUserLong, - Run: func(cmd *cobra.Command, _ []string) { - ctx := cmd.Context() - + RunE: func(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } // Load the state so we can get the credentials for the admin git user - state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) + state, err := c.LoadZarfState(cmd.Context()) if err != nil { message.WarnErr(err, lang.ErrLoadState) } - // Create the non-admin user - if err = git.New(state.GitServer).CreateReadOnlyUser(ctx); err != nil { + if err = git.New(state.GitServer).CreateReadOnlyUser(cmd.Context()); err != nil { message.WarnErr(err, lang.CmdInternalCreateReadOnlyGiteaUserErr) } + return nil }, } @@ -213,9 +221,15 @@ var createPackageRegistryToken = &cobra.Command{ Use: "create-artifact-registry-token", Short: lang.CmdInternalArtifactRegistryGiteaTokenShort, Long: lang.CmdInternalArtifactRegistryGiteaTokenLong, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + ctx := cmd.Context() - c := common.NewClusterOrDie(ctx) state, err := c.LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) @@ -231,9 +245,10 @@ var createPackageRegistryToken = &cobra.Command{ state.ArtifactServer.PushToken = token.Sha1 if err := c.SaveZarfState(ctx, state); err != nil { - message.Fatal(err, err.Error()) + return err } } + return nil }, } @@ -257,11 +272,12 @@ var updateGiteaPVC = &cobra.Command{ var isValidHostname = &cobra.Command{ Use: "is-valid-hostname", Short: lang.CmdInternalIsValidHostnameShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { if valid := helpers.IsValidHostName(); !valid { hostname, _ := os.Hostname() - message.Fatalf(nil, lang.CmdInternalIsValidHostnameErr, hostname) + return fmt.Errorf("the hostname %s is not valid. Ensure the hostname meets RFC1123 requirements https://www.rfc-editor.org/rfc/rfc1123.html", hostname) } + return nil }, } @@ -298,9 +314,6 @@ func addHiddenDummyFlag(cmd *cobra.Command, flagDummy string) { if cmd.PersistentFlags().Lookup(flagDummy) == nil { var dummyStr string cmd.PersistentFlags().StringVar(&dummyStr, flagDummy, "", "") - err := cmd.PersistentFlags().MarkHidden(flagDummy) - if err != nil { - message.Fatal(err, err.Error()) - } + cmd.PersistentFlags().MarkHidden(flagDummy) } } diff --git a/src/cmd/package.go b/src/cmd/package.go index b94fccee23..b5b28cff41 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -5,6 +5,8 @@ package cmd import ( + "context" + "errors" "fmt" "path/filepath" "regexp" @@ -39,7 +41,7 @@ var packageCreateCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdPackageCreateShort, Long: lang.CmdPackageCreateLong, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) var isCleanPathRegex = regexp.MustCompile(`^[a-zA-Z0-9\_\-\/\.\~\\:]+$`) @@ -56,8 +58,9 @@ var packageCreateCmd = &cobra.Command{ defer pkgClient.ClearTempPaths() if err := pkgClient.Create(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdPackageCreateErr, err.Error()) + return fmt.Errorf("failed to create package: %w", err) } + return nil }, } @@ -67,8 +70,12 @@ var packageDeployCmd = &cobra.Command{ Short: lang.CmdPackageDeployShort, Long: lang.CmdPackageDeployLong, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - pkgConfig.PkgOpts.PackageSource = choosePackage(args) + RunE: func(cmd *cobra.Command, args []string) error { + packageSource, err := choosePackage(args) + if err != nil { + return err + } + pkgConfig.PkgOpts.PackageSource = packageSource v := common.GetViper() pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( @@ -80,8 +87,9 @@ var packageDeployCmd = &cobra.Command{ ctx := cmd.Context() if err := pkgClient.Deploy(ctx); err != nil { - message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) + return fmt.Errorf("failed to deploy package: %w", err) } + return nil }, } @@ -92,17 +100,18 @@ var packageMirrorCmd = &cobra.Command{ Long: lang.CmdPackageMirrorLong, Example: lang.CmdPackageMirrorExample, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - pkgConfig.PkgOpts.PackageSource = choosePackage(args) - + RunE: func(cmd *cobra.Command, args []string) error { + packageSource, err := choosePackage(args) + if err != nil { + return err + } + pkgConfig.PkgOpts.PackageSource = packageSource pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - - ctx := cmd.Context() - - if err := pkgClient.Mirror(ctx); err != nil { - message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) + if err := pkgClient.Mirror(cmd.Context()); err != nil { + return fmt.Errorf("failed to mirror package: %w", err) } + return nil }, } @@ -112,17 +121,22 @@ var packageInspectCmd = &cobra.Command{ Short: lang.CmdPackageInspectShort, Long: lang.CmdPackageInspectLong, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - pkgConfig.PkgOpts.PackageSource = choosePackage(args) - - src := identifyAndFallbackToClusterSource() - + RunE: func(cmd *cobra.Command, args []string) error { + packageSource, err := choosePackage(args) + if err != nil { + return err + } + pkgConfig.PkgOpts.PackageSource = packageSource + src, err := identifyAndFallbackToClusterSource() + if err != nil { + return err + } pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - if err := pkgClient.Inspect(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdPackageInspectErr, err.Error()) + return fmt.Errorf("failed to inspect package: %w", err) } + return nil }, ValidArgsFunction: getPackageCompletionArgs, } @@ -131,11 +145,18 @@ var packageListCmd = &cobra.Command{ Use: "list", Aliases: []string{"l", "ls"}, Short: lang.CmdPackageListShort, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { + timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + ctx := cmd.Context() - deployedZarfPackages, err := common.NewClusterOrDie(ctx).GetDeployedZarfPackages(ctx) + deployedZarfPackages, err := c.GetDeployedZarfPackages(ctx) if err != nil && len(deployedZarfPackages) == 0 { - message.Fatalf(err, lang.CmdPackageListNoPackageWarn) + return fmt.Errorf("unable to get the packages deployed to the cluster: %w", err) } // Populate a matrix of all the deployed packages @@ -158,8 +179,9 @@ var packageListCmd = &cobra.Command{ // Print out any unmarshalling errors if err != nil { - message.Fatalf(err, lang.CmdPackageListUnmarshalErr) + return fmt.Errorf("unable to read all of the packages deployed to the cluster: %w", err) } + return nil }, } @@ -168,19 +190,22 @@ var packageRemoveCmd = &cobra.Command{ Aliases: []string{"u", "rm"}, Args: cobra.MaximumNArgs(1), Short: lang.CmdPackageRemoveShort, - Run: func(cmd *cobra.Command, args []string) { - pkgConfig.PkgOpts.PackageSource = choosePackage(args) - - src := identifyAndFallbackToClusterSource() - + RunE: func(cmd *cobra.Command, args []string) error { + packageSource, err := choosePackage(args) + if err != nil { + return err + } + pkgConfig.PkgOpts.PackageSource = packageSource + src, err := identifyAndFallbackToClusterSource() + if err != nil { + return err + } pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - - ctx := cmd.Context() - - if err := pkgClient.Remove(ctx); err != nil { - message.Fatalf(err, lang.CmdPackageRemoveErr, err.Error()) + if err := pkgClient.Remove(cmd.Context()); err != nil { + return fmt.Errorf("unable to remove the package with an error of: %w", err) } + return nil }, ValidArgsFunction: getPackageCompletionArgs, } @@ -190,11 +215,11 @@ var packagePublishCmd = &cobra.Command{ Short: lang.CmdPackagePublishShort, Example: lang.CmdPackagePublishExample, Args: cobra.ExactArgs(2), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.PkgOpts.PackageSource = args[0] if !helpers.IsOCIURL(args[1]) { - message.Fatal(nil, lang.CmdPackageRegistryPrefixErr) + return errors.New("Registry must be prefixed with 'oci://'") } parts := strings.Split(strings.TrimPrefix(args[1], helpers.OCIURLPrefix), "/") ref := registry.Reference{ @@ -203,7 +228,7 @@ var packagePublishCmd = &cobra.Command{ } err := ref.ValidateRegistry() if err != nil { - message.Fatalf(nil, "%s", err.Error()) + return err } if helpers.IsDir(pkgConfig.PkgOpts.PackageSource) { @@ -217,8 +242,9 @@ var packagePublishCmd = &cobra.Command{ defer pkgClient.ClearTempPaths() if err := pkgClient.Publish(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdPackagePublishErr, err.Error()) + return fmt.Errorf("failed to publish package: %w", err) } + return nil }, } @@ -227,21 +253,20 @@ var packagePullCmd = &cobra.Command{ Short: lang.CmdPackagePullShort, Example: lang.CmdPackagePullExample, Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.PkgOpts.PackageSource = args[0] - pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - if err := pkgClient.Pull(cmd.Context()); err != nil { - message.Fatalf(err, lang.CmdPackagePullErr, err.Error()) + return fmt.Errorf("failed to pull package: %w", err) } + return nil }, } -func choosePackage(args []string) string { +func choosePackage(args []string) (string, error) { if len(args) > 0 { - return args[0] + return args[0], nil } var path string prompt := &survey.Input{ @@ -258,23 +283,24 @@ func choosePackage(args []string) string { } if err := survey.AskOne(prompt, &path, survey.WithValidator(survey.Required)); err != nil { - message.Fatalf(nil, lang.CmdPackageChooseErr, err.Error()) + return "", fmt.Errorf("package path selection canceled: %w", err) } - return path + return path, nil } -func identifyAndFallbackToClusterSource() (src sources.PackageSource) { - var err error +// TODO: This code does not seem to do what it was intended. +func identifyAndFallbackToClusterSource() (sources.PackageSource, error) { identifiedSrc := sources.Identify(pkgConfig.PkgOpts.PackageSource) if identifiedSrc == "" { message.Debugf(lang.CmdPackageClusterSourceFallback, pkgConfig.PkgOpts.PackageSource) - src, err = sources.NewClusterSource(&pkgConfig.PkgOpts) + src, err := sources.NewClusterSource(&pkgConfig.PkgOpts) if err != nil { - message.Fatalf(err, lang.CmdPackageInvalidSource, pkgConfig.PkgOpts.PackageSource, err.Error()) + return nil, fmt.Errorf("unable to identify source from %s: %w", pkgConfig.PkgOpts.PackageSource, err) } + return src, nil } - return src + return nil, nil } func getPackageCompletionArgs(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { @@ -356,18 +382,9 @@ func bindCreateFlags(v *viper.Viper) { createFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - err := createFlags.MarkHidden("output-directory") - if err != nil { - message.Fatal(err, err.Error()) - } - err = createFlags.MarkHidden("key") - if err != nil { - message.Fatal(err, err.Error()) - } - err = createFlags.MarkHidden("key-pass") - if err != nil { - message.Fatal(err, err.Error()) - } + createFlags.MarkHidden("output-directory") + createFlags.MarkHidden("key") + createFlags.MarkHidden("key-pass") } func bindDeployFlags(v *viper.Viper) { @@ -387,10 +404,7 @@ func bindDeployFlags(v *viper.Viper) { deployFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", v.GetString(common.VPkgDeployShasum), lang.CmdPackageDeployFlagShasum) deployFlags.StringVar(&pkgConfig.PkgOpts.SGetKeyPath, "sget", v.GetString(common.VPkgDeploySget), lang.CmdPackageDeployFlagSget) - err := deployFlags.MarkHidden("sget") - if err != nil { - message.Fatal(err, err.Error()) - } + deployFlags.MarkHidden("sget") } func bindMirrorFlags(v *viper.Viper) { diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 84395d0e82..eafa9cbb47 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -5,6 +5,8 @@ package tools import ( + "context" + "errors" "fmt" "os" "slices" @@ -52,12 +54,23 @@ var getCredsCmd = &cobra.Command{ Example: lang.CmdToolsGetCredsExample, Aliases: []string{"gc"}, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) - if err != nil || state.Distro == "" { - // If no distro the zarf secret did not load properly - message.Fatalf(nil, lang.ErrLoadState) + + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + + state, err := c.LoadZarfState(ctx) + if err != nil { + return err + } + // TODO: Determine if this is actually needed. + if state.Distro == "" { + return errors.New("Zarf state secret did not load properly") } if len(args) > 0 { @@ -66,6 +79,7 @@ var getCredsCmd = &cobra.Command{ } else { message.PrintCredentialTable(state, nil) } + return nil }, } @@ -76,27 +90,37 @@ var updateCredsCmd = &cobra.Command{ Example: lang.CmdToolsUpdateCredsExample, Aliases: []string{"uc"}, Args: cobra.MaximumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { validKeys := []string{message.RegistryKey, message.GitKey, message.ArtifactKey, message.AgentKey} if len(args) == 0 { args = validKeys } else { if !slices.Contains(validKeys, args[0]) { cmd.Help() - message.Fatalf(nil, lang.CmdToolsUpdateCredsInvalidServiceErr, message.RegistryKey, message.GitKey, message.ArtifactKey) + return fmt.Errorf("invalid service key specified, valid keys are: %s, %s, and %s", message.RegistryKey, message.GitKey, message.ArtifactKey) } } ctx := cmd.Context() - c := common.NewClusterOrDie(ctx) + + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + return err + } + oldState, err := c.LoadZarfState(ctx) - if err != nil || oldState.Distro == "" { - // If no distro the zarf secret did not load properly - message.Fatalf(nil, lang.ErrLoadState) + if err != nil { + return err + } + // TODO: Determine if this is actually needed. + if oldState.Distro == "" { + return errors.New("Zarf state secret did not load properly") } newState, err := cluster.MergeZarfState(oldState, updateCredsInitOpts, args) if err != nil { - message.Fatal(err, lang.CmdToolsUpdateCredsUnableUpdateCreds) + return fmt.Errorf("unable to update Zarf credentials: %w", err) } message.PrintCredentialUpdates(oldState, newState, args) @@ -110,7 +134,7 @@ var updateCredsCmd = &cobra.Command{ Message: lang.CmdToolsUpdateCredsConfirmContinue, } if err := survey.AskOne(prompt, &confirm); err != nil { - message.Fatalf(nil, lang.ErrConfirmCancel, err) + return fmt.Errorf("confirm selection canceled: %w", err) } } @@ -138,7 +162,7 @@ var updateCredsCmd = &cobra.Command{ // Save the final Zarf State err = c.SaveZarfState(ctx, newState) if err != nil { - message.Fatalf(err, lang.ErrSaveState) + return fmt.Errorf("failed to save the Zarf State to the cluster: %w", err) } // Update Zarf 'init' component Helm releases if present @@ -167,6 +191,7 @@ var updateCredsCmd = &cobra.Command{ } } } + return nil }, } @@ -174,32 +199,31 @@ var clearCacheCmd = &cobra.Command{ Use: "clear-cache", Aliases: []string{"c"}, Short: lang.CmdToolsClearCacheShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { message.Notef(lang.CmdToolsClearCacheDir, config.GetAbsCachePath()) if err := os.RemoveAll(config.GetAbsCachePath()); err != nil { - message.Fatalf(err, lang.CmdToolsClearCacheErr, config.GetAbsCachePath()) + return fmt.Errorf("unable to clear the cache directory %s: %w", config.GetAbsCachePath(), err) } message.Successf(lang.CmdToolsClearCacheSuccess, config.GetAbsCachePath()) + return nil }, } var downloadInitCmd = &cobra.Command{ Use: "download-init", Short: lang.CmdToolsDownloadInitShort, - Run: func(cmd *cobra.Command, _ []string) { + RunE: func(cmd *cobra.Command, _ []string) error { url := zoci.GetInitPackageURL(config.CLIVersion) - remote, err := zoci.NewRemote(url, oci.PlatformForArch(config.GetArch())) if err != nil { - message.Fatalf(err, lang.CmdToolsDownloadInitErr, err.Error()) + return fmt.Errorf("unable to download the init package: %w", err) } - source := &sources.OCISource{Remote: remote} - _, err = source.Collect(cmd.Context(), outputDirectory) if err != nil { - message.Fatalf(err, lang.CmdToolsDownloadInitErr, err.Error()) + return fmt.Errorf("unable to download the init package: %w", err) } + return nil }, } @@ -208,18 +232,19 @@ var generatePKICmd = &cobra.Command{ Aliases: []string{"pki"}, Short: lang.CmdToolsGenPkiShort, Args: cobra.ExactArgs(1), - Run: func(_ *cobra.Command, args []string) { + RunE: func(_ *cobra.Command, args []string) error { pki := pki.GeneratePKI(args[0], subAltNames...) if err := os.WriteFile("tls.ca", pki.CA, helpers.ReadAllWriteUser); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.ca", err.Error()) + return err } if err := os.WriteFile("tls.crt", pki.Cert, helpers.ReadAllWriteUser); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.crt", err.Error()) + return err } if err := os.WriteFile("tls.key", pki.Key, helpers.ReadWriteUser); err != nil { - message.Fatalf(err, lang.ErrWritingFile, "tls.key", err.Error()) + return err } message.Successf(lang.CmdToolsGenPkiSuccess, args[0]) + return nil }, } @@ -227,7 +252,7 @@ var generateKeyCmd = &cobra.Command{ Use: "gen-key", Aliases: []string{"key"}, Short: lang.CmdToolsGenKeyShort, - Run: func(_ *cobra.Command, _ []string) { + RunE: func(_ *cobra.Command, _ []string) error { // Utility function to prompt the user for the password to the private key passwordFunc := func(bool) ([]byte, error) { // perform the first prompt @@ -259,7 +284,7 @@ var generateKeyCmd = &cobra.Command{ // Use cosign to generate the keypair keyBytes, err := cosign.GenerateKeyPair(passwordFunc) if err != nil { - message.Fatalf(err, lang.CmdToolsGenKeyErrUnableToGenKeypair, err.Error()) + return fmt.Errorf("unable to generate key pair: %w", err) } prvKeyFileName := "cosign.key" @@ -275,23 +300,23 @@ var generateKeyCmd = &cobra.Command{ } err := survey.AskOne(confirmOverwritePrompt, &confirm) if err != nil { - message.Fatalf(err, lang.CmdToolsGenKeyErrNoConfirmOverwrite) + return err } - if !confirm { - message.Fatal(nil, lang.CmdToolsGenKeyErrNoConfirmOverwrite) + return errors.New("did not receive confirmation for overwriting key file(s)") } } // Write the key file contents to disk if err := os.WriteFile(prvKeyFileName, keyBytes.PrivateBytes, helpers.ReadWriteUser); err != nil { - message.Fatalf(err, lang.ErrWritingFile, prvKeyFileName, err.Error()) + return err } if err := os.WriteFile(pubKeyFileName, keyBytes.PublicBytes, helpers.ReadAllWriteUser); err != nil { - message.Fatalf(err, lang.ErrWritingFile, pubKeyFileName, err.Error()) + return err } message.Successf(lang.CmdToolsGenKeySuccess, prvKeyFileName, pubKeyFileName) + return nil }, } diff --git a/src/config/lang/english.go b/src/config/lang/english.go index d4f2e4d4d3..df39428f48 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -18,7 +18,6 @@ import ( // Include sprintf formatting directives in the string if needed. const ( ErrLoadState = "Failed to load the Zarf State from the cluster." - ErrSaveState = "Failed to save the Zarf State to the cluster." ErrUnmarshal = "failed to unmarshal file: %w" ErrWritingFile = "failed to write file %s: %s" ErrDownloading = "failed to download %s: %s" @@ -85,8 +84,6 @@ const ( CmdConnectFlagCliOnly = "Disable browser auto-open" CmdConnectPreparingTunnel = "Preparing a tunnel to connect to %s" - CmdConnectErrCluster = "Unable to connect to the cluster: %s" - CmdConnectErrService = "Unable to connect to the service: %s" CmdConnectEstablishedCLI = "Tunnel established at %s, waiting for user to interrupt (ctrl-c to end)" CmdConnectEstablishedWeb = "Tunnel established at %s, opening your default web browser (ctrl-c to end)" CmdConnectTunnelClosed = "Tunnel to %s successfully closed due to user interrupt" @@ -107,7 +104,6 @@ const ( CmdDestroyFlagConfirm = "REQUIRED. Confirm the destroy action to prevent accidental deletions" CmdDestroyFlagRemoveComponents = "Also remove any installed components outside the zarf namespace" - CmdDestroyErrNoScriptPath = "Unable to find the folder (%s) which has the scripts to cleanup the cluster. Please double-check you have the right kube-context" CmdDestroyErrScriptPermissionDenied = "Received 'permission denied' when trying to execute the script (%s). Please double-check you have the correct kube-context." // zarf init @@ -144,12 +140,9 @@ $ zarf init --artifact-push-password={PASSWORD} --artifact-push-username={USERNA # NOTE: Not specifying a pull username/password will use the push user for pulling as well. ` - CmdInitErrFlags = "Invalid command flags were provided." - CmdInitErrDownload = "failed to download the init package: %s" - CmdInitErrValidateGit = "the 'git-push-username' and 'git-push-password' flags must be provided if the 'git-url' flag is provided" - CmdInitErrValidateRegistry = "the 'registry-push-username' and 'registry-push-password' flags must be provided if the 'registry-url' flag is provided" - CmdInitErrValidateArtifact = "the 'artifact-push-username' and 'artifact-push-token' flags must be provided if the 'artifact-url' flag is provided" - CmdInitErrUnableCreateCache = "Unable to create the cache directory: %s" + CmdInitErrValidateGit = "the 'git-push-username' and 'git-push-password' flags must be provided if the 'git-url' flag is provided" + CmdInitErrValidateRegistry = "the 'registry-push-username' and 'registry-push-password' flags must be provided if the 'registry-url' flag is provided" + CmdInitErrValidateArtifact = "the 'artifact-push-username' and 'artifact-push-token' flags must be provided if the 'artifact-url' flag is provided" CmdInitPullAsk = "It seems the init package could not be found locally, but can be pulled from oci://%s" CmdInitPullNote = "Note: This will require an internet connection." @@ -195,13 +188,10 @@ $ zarf init --artifact-push-password={PASSWORD} --artifact-push-username={USERNA CmdInternalGenerateCliDocsShort = "Creates auto-generated markdown of all the commands for the CLI" CmdInternalGenerateCliDocsSuccess = "Successfully created the CLI documentation" - CmdInternalGenerateCliDocsErr = "Unable to generate the CLI documentation: %s" CmdInternalConfigSchemaShort = "Generates a JSON schema for the zarf.yaml configuration" - CmdInternalConfigSchemaErr = "Unable to generate the zarf config schema" CmdInternalTypesSchemaShort = "Generates a JSON schema for the Zarf types (DeployedPackage ZarfPackage ZarfState)" - CmdInternalTypesSchemaErr = "Unable to generate the JSON schema for the Zarf types (DeployedPackage ZarfPackage ZarfState)" CmdInternalCreateReadOnlyGiteaUserShort = "Creates a read-only user in Gitea" CmdInternalCreateReadOnlyGiteaUserLong = "Creates a read-only user in Gitea by using the Gitea API. " + @@ -220,7 +210,6 @@ $ zarf init --artifact-push-password={PASSWORD} --artifact-push-username={USERNA CmdInternalFlagUpdateGiteaPVCRollback = "Roll back previous Gitea persistent volume claim updates." CmdInternalIsValidHostnameShort = "Checks if the current machine's hostname is RFC1123 compliant" - CmdInternalIsValidHostnameErr = "The hostname '%s' is not valid. Ensure the hostname meets RFC1123 requirements https://www.rfc-editor.org/rfc/rfc1123.html." CmdInternalCrc32Short = "Generates a decimal CRC32 for the given text" @@ -267,7 +256,6 @@ $ zarf package mirror-resources \ CmdPackageListShort = "Lists out all of the packages that have been deployed to the cluster (runs offline)" CmdPackageListNoPackageWarn = "Unable to get the packages deployed to the cluster" - CmdPackageListUnmarshalErr = "Unable to read all of the packages deployed to the cluster" CmdPackageCreateFlagConfirm = "Confirm package creation without prompting" CmdPackageCreateFlagSet = "Specify package variables to set on the command line (KEY=value)" @@ -284,7 +272,6 @@ $ zarf package mirror-resources \ CmdPackageCreateFlagRegistryOverride = "Specify a map of domains to override on package create when pulling images (e.g. --registry-override docker.io=dockerio-reg.enterprise.intranet)" CmdPackageCreateFlagFlavor = "The flavor of components to include in the resulting package (i.e. have a matching or empty \"only.flavor\" key)" CmdPackageCreateCleanPathErr = "Invalid characters in Zarf cache path, defaulting to %s" - CmdPackageCreateErr = "Failed to create package: %s" CmdPackageDeployFlagConfirm = "Confirms package deployment without prompting. ONLY use with packages you trust. Skips prompts to review SBOM, configure variables, select optional components and review potential breaking changes." CmdPackageDeployFlagAdoptExistingResources = "Adopts any pre-existing K8s resources into the Helm charts managed by Zarf. ONLY use when you have existing deployments you want Zarf to takeover." @@ -297,7 +284,6 @@ $ zarf package mirror-resources \ CmdPackageDeployValidateArchitectureErr = "this package architecture is %s, but the target cluster only has the %s architecture(s). These architectures must be compatible when \"images\" are present" CmdPackageDeployValidateLastNonBreakingVersionWarn = "The version of this Zarf binary '%s' is less than the LastNonBreakingVersion of '%s'. You may need to upgrade your Zarf version to at least '%s' to deploy this package" CmdPackageDeployInvalidCLIVersionWarn = "CLIVersion is set to '%s' which can cause issues with package creation and deployment. To avoid such issues, please set the value to the valid semantic version for this version of Zarf." - CmdPackageDeployErr = "Failed to deploy package: %s" CmdPackageMirrorFlagComponents = "Comma-separated list of components to mirror. This list will be respected regardless of a component's 'required' or 'default' status. Globbing component names with '*' and deselecting components with a leading '-' are also supported." CmdPackageMirrorFlagNoChecksum = "Turns off the addition of a checksum to image tags (as would be used by the Zarf Agent) while mirroring images." @@ -305,14 +291,10 @@ $ zarf package mirror-resources \ CmdPackageInspectFlagSbom = "View SBOM contents while inspecting the package" CmdPackageInspectFlagSbomOut = "Specify an output directory for the SBOMs from the inspected Zarf package" CmdPackageInspectFlagListImages = "List images in the package (prints to stdout)" - CmdPackageInspectErr = "Failed to inspect package: %s" CmdPackageRemoveShort = "Removes a Zarf package that has been deployed already (runs offline)" CmdPackageRemoveFlagConfirm = "REQUIRED. Confirm the removal action to prevent accidental deletions" CmdPackageRemoveFlagComponents = "Comma-separated list of components to remove. This list will be respected regardless of a component's 'required' or 'default' status. Globbing component names with '*' and deselecting components with a leading '-' are also supported." - CmdPackageRemoveErr = "Unable to remove the package with an error of: %s" - - CmdPackageRegistryPrefixErr = "Registry must be prefixed with 'oci://'" CmdPackagePublishShort = "Publishes a Zarf package to a remote registry" CmdPackagePublishExample = ` @@ -324,7 +306,6 @@ $ zarf package publish ./path/to/dir oci://my-registry.com/my-namespace ` CmdPackagePublishFlagSigningKey = "Path to a private key file for signing or re-signing packages with a new key" CmdPackagePublishFlagSigningKeyPassword = "Password to the private key file used for publishing packages" - CmdPackagePublishErr = "Failed to publish package: %s" CmdPackagePullShort = "Pulls a Zarf package from a remote registry and save to the local file system" CmdPackagePullExample = ` @@ -337,10 +318,8 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a ar # Pull a skeleton package $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a skeleton` CmdPackagePullFlagOutputDirectory = "Specify the output directory for the pulled Zarf package" - CmdPackagePullErr = "Failed to pull package: %s" CmdPackageChoose = "Choose or type the package file" - CmdPackageChooseErr = "Package path selection canceled: %s" CmdPackageClusterSourceFallback = "%q does not satisfy any current sources, assuming it is a package deployed to a cluster" CmdPackageInvalidSource = "Unable to identify source from %q: %s" @@ -350,7 +329,6 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a sk CmdDevDeployShort = "[beta] Creates and deploys a Zarf package from a given directory" CmdDevDeployLong = "[beta] Creates and deploys a Zarf package from a given directory, setting options like YOLO mode for faster iteration." CmdDevDeployFlagNoYolo = "Disable the YOLO mode default override and create / deploy the package as-defined" - CmdDevDeployErr = "Failed to dev deploy: %s" CmdDevGenerateShort = "[alpha] Creates a zarf.yaml automatically from a given remote (git) Helm chart" CmdDevGenerateExample = "zarf dev generate podinfo --url https://github.com/stefanprodan/podinfo.git --version 6.4.0 --gitPath charts/podinfo" @@ -358,25 +336,19 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a sk CmdDevPatchGitShort = "Converts all .git URLs to the specified Zarf HOST and with the Zarf URL pattern in a given FILE. NOTE:\n" + "This should only be used for manifests that are not mutated by the Zarf Agent Mutating Webhook." CmdDevPatchGitOverwritePrompt = "Overwrite the file %s with these changes?" - CmdDevPatchGitOverwriteErr = "Confirm overwrite canceled: %s" - CmdDevPatchGitFileReadErr = "Unable to read the file %s" - CmdDevPatchGitFileWriteErr = "Unable to write the changes back to the file" CmdDevSha256sumShort = "Generates a SHA256SUM for the given file" CmdDevSha256sumRemoteWarning = "This is a remote source. If a published checksum is available you should use that rather than calculating it directly from the remote link." - CmdDevSha256sumHashErr = "Unable to compute the SHA256SUM hash: %s" CmdDevFindImagesShort = "Evaluates components in a Zarf file to identify images specified in their helm charts and manifests" CmdDevFindImagesLong = "Evaluates components in a Zarf file to identify images specified in their helm charts and manifests.\n\n" + "Components that have repos that host helm charts can be processed by providing the --repo-chart-path." - CmdDevFindImagesErr = "Unable to find images: %s" CmdDevGenerateConfigShort = "Generates a config file for Zarf" CmdDevGenerateConfigLong = "Generates a Zarf config file for controlling how the Zarf CLI operates. Optionally accepts a filename to write the config to.\n\n" + "The extension will determine the format of the config file, e.g. env-1.yaml, env-2.json, env-3.toml etc.\n" + "Accepted extensions are json, toml, yaml.\n\n" + "NOTE: This file must not already exist. If no filename is provided, the config will be written to the current working directory as zarf-config.toml." - CmdDevGenerateConfigErr = "Unable to write the config file %s, make sure the file doesn't already exist" CmdDevFlagExtractPath = `The path inside of an archive to use to calculate the sha256sum (i.e. for use with "files.extractPath")` CmdDevFlagSet = "Specify package variables to set on the command line (KEY=value). Note, if using a config file, this will be set by [package.create.set]." @@ -395,11 +367,7 @@ $ zarf package pull oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0 -a sk CmdToolsArchiverShort = "Compresses/Decompresses generic archives, including Zarf packages" CmdToolsArchiverCompressShort = "Compresses a collection of sources based off of the destination file extension." - CmdToolsArchiverCompressErr = "Unable to perform compression: %s" CmdToolsArchiverDecompressShort = "Decompresses an archive or Zarf package based off of the source file extension." - CmdToolsArchiverDecompressErr = "Unable to perform decompression: %s" - - CmdToolsArchiverUnarchiveAllErr = "Unable to unarchive all nested tarballs: %s" CmdToolsRegistryShort = "Tools for working with container registries using go-containertools" CmdToolsRegistryZarfState = "Retrieving registry information from Zarf state" @@ -461,11 +429,10 @@ $ zarf tools registry digest reg.example.com/stefanprodan/podinfo:6.4.0 CmdToolsRegistryPruneCalculate = "Calculating images to prune" CmdToolsRegistryPruneDelete = "Deleting unused images" - CmdToolsRegistryInvalidPlatformErr = "Invalid platform '%s': %s" - CmdToolsRegistryFlagVerbose = "Enable debug logs" - CmdToolsRegistryFlagInsecure = "Allow image references to be fetched without TLS" - CmdToolsRegistryFlagNonDist = "Allow pushing non-distributable (foreign) layers" - CmdToolsRegistryFlagPlatform = "Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64)." + CmdToolsRegistryFlagVerbose = "Enable debug logs" + CmdToolsRegistryFlagInsecure = "Allow image references to be fetched without TLS" + CmdToolsRegistryFlagNonDist = "Allow pushing non-distributable (foreign) layers" + CmdToolsRegistryFlagPlatform = "Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64)." CmdToolsGetGitPasswdShort = "[Deprecated] Returns the push user's password for the Git server" CmdToolsGetGitPasswdLong = "[Deprecated] Reads the password for a user with push access to the configured Git server in Zarf State. Note that this command has been replaced by 'zarf tools get-creds git' and will be removed in Zarf v1.0.0." @@ -521,30 +488,25 @@ zarf tools yq e '.a.b = "cool"' -i file.yaml CmdToolsClearCacheShort = "Clears the configured git and image cache directory" CmdToolsClearCacheDir = "Cache directory set to: %s" - CmdToolsClearCacheErr = "Unable to clear the cache directory %s" CmdToolsClearCacheSuccess = "Successfully cleared the cache from %s" CmdToolsClearCacheFlagCachePath = "Specify the location of the Zarf artifact cache (images and git repositories)" - CmdToolsCraneNotEnoughArgumentsErr = "You do not have enough arguments specified for this command" CmdToolsCraneConnectedButBadStateErr = "Detected a K8s cluster but was unable to get Zarf state - continuing without state information: %s" CmdToolsDownloadInitShort = "Downloads the init package for the current Zarf version into the specified directory" CmdToolsDownloadInitFlagOutputDirectory = "Specify a directory to place the init package in." - CmdToolsDownloadInitErr = "Unable to download the init package: %s" CmdToolsGenPkiShort = "Generates a Certificate Authority and PKI chain of trust for the given host" CmdToolsGenPkiSuccess = "Successfully created a chain of trust for %s" CmdToolsGenPkiFlagAltName = "Specify Subject Alternative Names for the certificate" - CmdToolsGenKeyShort = "Generates a cosign public/private keypair that can be used to sign packages" - CmdToolsGenKeyPrompt = "Private key password (empty for no password): " - CmdToolsGenKeyPromptAgain = "Private key password again (empty for no password): " - CmdToolsGenKeyPromptExists = "File %s already exists. Overwrite? " - CmdToolsGenKeyErrUnableGetPassword = "unable to get password for private key: %s" - CmdToolsGenKeyErrPasswordsNotMatch = "passwords do not match" - CmdToolsGenKeyErrUnableToGenKeypair = "unable to generate key pair: %s" - CmdToolsGenKeyErrNoConfirmOverwrite = "did not receive confirmation for overwriting key file(s)" - CmdToolsGenKeySuccess = "Generated key pair and written to %s and %s" + CmdToolsGenKeyShort = "Generates a cosign public/private keypair that can be used to sign packages" + CmdToolsGenKeyPrompt = "Private key password (empty for no password): " + CmdToolsGenKeyPromptAgain = "Private key password again (empty for no password): " + CmdToolsGenKeyPromptExists = "File %s already exists. Overwrite? " + CmdToolsGenKeyErrUnableGetPassword = "unable to get password for private key: %s" + CmdToolsGenKeyErrPasswordsNotMatch = "passwords do not match" + CmdToolsGenKeySuccess = "Generated key pair and written to %s and %s" CmdToolsSbomShort = "Generates a Software Bill of Materials (SBOM) for the given package" @@ -620,7 +582,6 @@ $ zarf tools update-creds artifact --artifact-push-username={USERNAME} --artifac CmdToolsUpdateCredsConfirmFlag = "Confirm updating credentials without prompting" CmdToolsUpdateCredsConfirmProvided = "Confirm flag specified, continuing without prompting." CmdToolsUpdateCredsConfirmContinue = "Continue with these changes?" - CmdToolsUpdateCredsInvalidServiceErr = "Invalid service key specified - valid keys are: %s, %s, and %s" CmdToolsUpdateCredsUnableCreateToken = "Unable to create the new Gitea artifact token: %s" CmdToolsUpdateCredsUnableUpdateRegistry = "Unable to update Zarf Registry values: %s" CmdToolsUpdateCredsUnableUpdateGit = "Unable to update Zarf Git Server values: %s" diff --git a/src/pkg/cluster/cluster.go b/src/pkg/cluster/cluster.go index 0aad1ad848..5d15550482 100644 --- a/src/pkg/cluster/cluster.go +++ b/src/pkg/cluster/cluster.go @@ -6,6 +6,7 @@ package cluster import ( "context" + "errors" "fmt" "time" @@ -55,13 +56,14 @@ func NewClusterWithWait(ctx context.Context) (*Cluster, error) { // NewCluster creates a new Cluster instance and validates connection to the cluster by fetching the Kubernetes version. func NewCluster() (*Cluster, error) { + clusterErr := errors.New("unable to connect to the cluster") clientset, config, err := pkgkubernetes.ClientAndConfig() if err != nil { - return nil, err + return nil, errors.Join(clusterErr, err) } watcher, err := pkgkubernetes.WatcherForConfig(config) if err != nil { - return nil, err + return nil, errors.Join(clusterErr, err) } c := &Cluster{ Clientset: clientset, @@ -71,7 +73,7 @@ func NewCluster() (*Cluster, error) { // Dogsled the version output. We just want to ensure no errors were returned to validate cluster connection. _, err = c.Clientset.Discovery().ServerVersion() if err != nil { - return nil, err + return nil, errors.Join(clusterErr, err) } return c, nil } diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index 5471628667..d194065125 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -206,19 +206,15 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO // LoadZarfState returns the current zarf/zarf-state secret data or an empty ZarfState. func (c *Cluster) LoadZarfState(ctx context.Context) (state *types.ZarfState, err error) { - // Set up the API connection secret, err := c.Clientset.CoreV1().Secrets(ZarfNamespaceName).Get(ctx, ZarfStateSecretName, metav1.GetOptions{}) if err != nil { return nil, fmt.Errorf("%w. %s", err, message.ColorWrap("Did you remember to zarf init?", color.Bold)) } - err = json.Unmarshal(secret.Data[ZarfStateDataKey], &state) if err != nil { return nil, err } - c.debugPrintZarfState(state) - return state, nil } diff --git a/src/test/e2e/02_component_actions_test.go b/src/test/e2e/02_component_actions_test.go index 6eda343103..b709c0c267 100644 --- a/src/test/e2e/02_component_actions_test.go +++ b/src/test/e2e/02_component_actions_test.go @@ -149,7 +149,7 @@ func TestComponentActions(t *testing.T) { t.Parallel() stdOut, stdErr, err = e2e.Zarf("package", "deploy", path, "--components=on-deploy-immediate-failure", "--confirm") require.Error(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Failed to deploy package") + require.Contains(t, stdErr, "failed to deploy package") // regression test to ensure that failed commands are not erroneously flagged as a timeout require.NotContains(t, stdErr, "timed out") }) diff --git a/src/test/e2e/09_component_compose_test.go b/src/test/e2e/09_component_compose_test.go index 0d5acf7c0e..c5edaeb682 100644 --- a/src/test/e2e/09_component_compose_test.go +++ b/src/test/e2e/09_component_compose_test.go @@ -185,7 +185,7 @@ func (suite *CompositionSuite) Test_2_ComposabilityBadLocalOS() { _, stdErr, err := e2e.Zarf("package", "create", composeTestBadLocalOS, "-o", "build", "--no-color", "--confirm") suite.Error(err) - suite.Contains(stdErr, "\"only.localOS\" \"linux\" cannot be\n redefined as \"windows\" during compose") + suite.Contains(stdErr, "\"only.localOS\" \"linux\" cannot be redefined as \"windows\" during compose") } func TestCompositionSuite(t *testing.T) { diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index 263ddccb55..8b53bacfaf 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -13,7 +13,6 @@ import ( "path/filepath" "testing" - "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/types" @@ -69,8 +68,13 @@ func testGitServerConnect(t *testing.T, gitURL string) { } func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + require.NoError(t, err) + // Init the state variable - zarfState, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) + zarfState, err := c.LoadZarfState(ctx) require.NoError(t, err) gitCfg := git.New(zarfState.GitServer) @@ -92,8 +96,13 @@ func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { } func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + require.NoError(t, err) + // Init the state variable - zarfState, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) + zarfState, err := c.LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") repoName := "zarf-public-test-2469062884" @@ -121,7 +130,8 @@ func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { func waitFluxPodInfoDeployment(t *testing.T) { ctx := context.Background() - cluster := common.NewClusterOrDie(ctx) + cluster, err := cluster.NewClusterWithWait(ctx) + require.NoError(t, err) zarfState, err := cluster.LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") registryAddress, err := cluster.GetServiceInfoFromRegistryAddress(ctx, zarfState.RegistryInfo.Address) diff --git a/src/test/e2e/31_checksum_and_signature_test.go b/src/test/e2e/31_checksum_and_signature_test.go index 08f7f2cdde..957deaeaa8 100644 --- a/src/test/e2e/31_checksum_and_signature_test.go +++ b/src/test/e2e/31_checksum_and_signature_test.go @@ -38,7 +38,7 @@ func TestChecksumAndSignature(t *testing.T) { // Test that we get an error when trying to deploy a package without providing the public key stdOut, stdErr, err = e2e.Zarf("package", "deploy", pkgName, "--confirm") require.Error(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Failed to deploy package: unable to load the package: package is signed but no key was provided") + require.Contains(t, stdErr, "failed to deploy package: unable to load the package: package is signed but no key was provided - add a key with the --key flag or use the --insecure flag and run the command again") // Test that we don't get an error when we remember to provide the public key stdOut, stdErr, err = e2e.Zarf("package", "deploy", pkgName, publicKeyFlag, "--confirm") From 9c3532c1d859914684c284162add8a4d1b8312d5 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Fri, 28 Jun 2024 11:18:42 +0200 Subject: [PATCH 103/132] refactor: remove all use of message.Fatal --- src/cmd/dev.go | 17 ++++++++++--- src/cmd/initialize.go | 5 +++- src/cmd/package.go | 35 +++++++++++++++++++++------ src/cmd/tools/zarf.go | 5 +++- src/internal/agent/http/server.go | 7 +++--- src/internal/agent/start.go | 6 ++++- src/internal/packager/helm/repo.go | 8 +++--- src/pkg/cluster/state.go | 12 +++++++-- src/pkg/cluster/state_test.go | 4 ++- src/pkg/message/message.go | 19 --------------- src/pkg/message/spinner.go | 15 ------------ src/pkg/packager/common.go | 18 -------------- src/pkg/packager/deploy.go | 2 +- src/pkg/packager/deploy_test.go | 3 ++- src/pkg/pki/pki.go | 39 +++++++++++++++--------------- 15 files changed, 97 insertions(+), 98 deletions(-) diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 7560b43651..f6fad80cbe 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -50,7 +50,10 @@ var devDeployCmd = &cobra.Command{ pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.DevDeploy(cmd.Context()); err != nil { @@ -72,10 +75,13 @@ var devGenerateCmd = &cobra.Command{ pkgConfig.CreateOpts.BaseDir = "." pkgConfig.FindImagesOpts.RepoHelmChartPath = pkgConfig.GenerateOpts.GitPath - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() - err := pkgClient.Generate() + err = pkgClient.Generate() if err != nil { return err } @@ -217,7 +223,10 @@ var devFindImagesCmd = &cobra.Command{ v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if _, err := pkgClient.FindImages(cmd.Context()); err != nil { diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index 5a680b7836..2262454866 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -63,7 +63,10 @@ var initCmd = &cobra.Command{ pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) + pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src)) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() err = pkgClient.Deploy(cmd.Context()) diff --git a/src/cmd/package.go b/src/cmd/package.go index b5b28cff41..fa939e10bb 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -54,7 +54,10 @@ var packageCreateCmd = &cobra.Command{ pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Create(cmd.Context()); err != nil { @@ -81,7 +84,10 @@ var packageDeployCmd = &cobra.Command{ pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() ctx := cmd.Context() @@ -106,7 +112,10 @@ var packageMirrorCmd = &cobra.Command{ return err } pkgConfig.PkgOpts.PackageSource = packageSource - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Mirror(cmd.Context()); err != nil { return fmt.Errorf("failed to mirror package: %w", err) @@ -131,7 +140,10 @@ var packageInspectCmd = &cobra.Command{ if err != nil { return err } - pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) + pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src)) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Inspect(cmd.Context()); err != nil { return fmt.Errorf("failed to inspect package: %w", err) @@ -200,7 +212,10 @@ var packageRemoveCmd = &cobra.Command{ if err != nil { return err } - pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) + pkgClient, err := packager.New(&pkgConfig, packager.WithSource(src)) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Remove(cmd.Context()); err != nil { return fmt.Errorf("unable to remove the package with an error of: %w", err) @@ -238,7 +253,10 @@ var packagePublishCmd = &cobra.Command{ pkgConfig.PublishOpts.PackageDestination = ref.String() - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Publish(cmd.Context()); err != nil { @@ -255,7 +273,10 @@ var packagePullCmd = &cobra.Command{ Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { pkgConfig.PkgOpts.PackageSource = args[0] - pkgClient := packager.NewOrDie(&pkgConfig) + pkgClient, err := packager.New(&pkgConfig) + if err != nil { + return err + } defer pkgClient.ClearTempPaths() if err := pkgClient.Pull(cmd.Context()); err != nil { return fmt.Errorf("failed to pull package: %w", err) diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index eafa9cbb47..f911b2ec40 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -233,7 +233,10 @@ var generatePKICmd = &cobra.Command{ Short: lang.CmdToolsGenPkiShort, Args: cobra.ExactArgs(1), RunE: func(_ *cobra.Command, args []string) error { - pki := pki.GeneratePKI(args[0], subAltNames...) + pki, err := pki.GeneratePKI(args[0], subAltNames...) + if err != nil { + return err + } if err := os.WriteFile("tls.ca", pki.CA, helpers.ReadAllWriteUser); err != nil { return err } diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go index 9bcff6a431..62fb72c59d 100644 --- a/src/internal/agent/http/server.go +++ b/src/internal/agent/http/server.go @@ -18,12 +18,12 @@ import ( ) // NewAdmissionServer creates a http.Server for the mutating webhook admission handler. -func NewAdmissionServer(ctx context.Context, port string) *http.Server { +func NewAdmissionServer(ctx context.Context, port string) (*http.Server, error) { message.Debugf("http.NewAdmissionServer(%s)", port) c, err := cluster.NewCluster() if err != nil { - message.Fatalf(err, err.Error()) + return nil, err } // Routers @@ -46,11 +46,12 @@ func NewAdmissionServer(ctx context.Context, port string) *http.Server { mux.Handle("/mutate/argocd-repository", admissionHandler.Serve(argocdRepositoryMutation)) mux.Handle("/metrics", promhttp.Handler()) - return &http.Server{ + srv := &http.Server{ Addr: fmt.Sprintf(":%s", port), Handler: mux, ReadHeaderTimeout: 5 * time.Second, // Set ReadHeaderTimeout to avoid Slowloris attacks } + return srv, nil } // NewProxyServer creates and returns an http proxy server. diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go index 83172d2e95..f110ce2b52 100644 --- a/src/internal/agent/start.go +++ b/src/internal/agent/start.go @@ -30,7 +30,11 @@ const ( // StartWebhook launches the Zarf agent mutating webhook in the cluster. func StartWebhook(ctx context.Context) error { message.Debug("agent.StartWebhook()") - return startServer(ctx, agentHttp.NewAdmissionServer(ctx, httpPort)) + srv, err := agentHttp.NewAdmissionServer(ctx, httpPort) + if err != nil { + return err + } + return startServer(ctx, srv) } // StartHTTPProxy launches the zarf agent proxy in the cluster. diff --git a/src/internal/packager/helm/repo.go b/src/internal/packager/helm/repo.go index 245625c787..6bc19c8e50 100644 --- a/src/internal/packager/helm/repo.go +++ b/src/internal/packager/helm/repo.go @@ -82,7 +82,7 @@ func (h *Helm) PackageChartFromLocalFiles(cosignKeyPath string) error { var saved string temp := filepath.Join(h.chartPath, "temp") if _, ok := cl.(loader.DirLoader); ok { - err = h.buildChartDependencies(spinner) + err = h.buildChartDependencies() if err != nil { return fmt.Errorf("unable to build dependencies for the chart: %w", err) } @@ -157,7 +157,7 @@ func (h *Helm) DownloadPublishedChart(cosignKeyPath string) error { if registry.IsOCI(h.chart.URL) { regClient, err = registry.NewClient(registry.ClientOptEnableCache(true)) if err != nil { - spinner.Fatalf(err, "Unable to create a new registry client") + return fmt.Errorf("unable to create the new registry client: %w", err) } chartURL = h.chart.URL // Explicitly set the pull version for OCI @@ -279,11 +279,11 @@ func (h *Helm) packageValues(cosignKeyPath string) error { } // buildChartDependencies builds the helm chart dependencies -func (h *Helm) buildChartDependencies(spinner *message.Spinner) error { +func (h *Helm) buildChartDependencies() error { // Download and build the specified dependencies regClient, err := registry.NewClient(registry.ClientOptEnableCache(true)) if err != nil { - spinner.Fatalf(err, "Unable to create a new registry client") + return fmt.Errorf("unable to create a new registry client: %w", err) } h.settings = cli.New() diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index d194065125..ad4ff27736 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -78,7 +78,11 @@ func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitO state.Distro = distro // Setup zarf agent PKI - state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) + agentTLS, err := pki.GeneratePKI(config.ZarfAgentHost) + if err != nil { + return err + } + state.AgentTLS = agentTLS namespaceList, err := c.Clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) if err != nil { @@ -359,7 +363,11 @@ func MergeZarfState(oldState *types.ZarfState, initOptions types.ZarfInitOptions } } if slices.Contains(services, message.AgentKey) { - newState.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) + agentTLS, err := pki.GeneratePKI(config.ZarfAgentHost) + if err != nil { + return nil, err + } + newState.AgentTLS = agentTLS } return &newState, nil diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go index 19301432f1..524b0e3295 100644 --- a/src/pkg/cluster/state_test.go +++ b/src/pkg/cluster/state_test.go @@ -468,8 +468,10 @@ func TestMergeZarfStateArtifact(t *testing.T) { func TestMergeZarfStateAgent(t *testing.T) { t.Parallel() + agentTLS, err := pki.GeneratePKI("example.com") + require.NoError(t, err) oldState := &types.ZarfState{ - AgentTLS: pki.GeneratePKI("example.com"), + AgentTLS: agentTLS, } newState, err := MergeZarfState(oldState, types.ZarfInitOptions{}, []string{message.AgentKey}) require.NoError(t, err) diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 68bf711219..591165559e 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -10,7 +10,6 @@ import ( "io" "net/http" "os" - "runtime/debug" "strings" "time" @@ -162,20 +161,6 @@ func WarnErrf(err any, format string, a ...any) { Warnf(format, a...) } -// Fatal prints a fatal error message and exits with a 1. -func Fatal(err any, message string) { - debugPrinter(2, err) - errorPrinter(2).Println(message) - debugPrinter(2, string(debug.Stack())) - os.Exit(1) -} - -// Fatalf prints a fatal error message and exits with a 1 with a given format. -func Fatalf(err any, format string, a ...any) { - message := Paragraph(format, a...) - Fatal(err, message) -} - // Info prints an info message. func Info(message string) { Infof("%s", message) @@ -351,7 +336,3 @@ func debugPrinter(offset int, a ...any) { Println(a...) } } - -func errorPrinter(offset int) *pterm.PrefixPrinter { - return pterm.Error.WithShowLineNumber(logLevel > 2).WithLineNumberOffset(offset) -} diff --git a/src/pkg/message/spinner.go b/src/pkg/message/spinner.go index cb2ea49a3a..93511a705c 100644 --- a/src/pkg/message/spinner.go +++ b/src/pkg/message/spinner.go @@ -134,18 +134,3 @@ func (p *Spinner) Errorf(err error, format string, a ...any) { Warnf(format, a...) debugPrinter(2, err) } - -// Fatal calls message.Fatalf with the given error. -func (p *Spinner) Fatal(err error) { - p.Fatalf(err, p.startText) -} - -// Fatalf calls message.Fatalf with the given error and format. -func (p *Spinner) Fatalf(err error, format string, a ...any) { - if p.spinner != nil { - p.spinner.RemoveWhenDone = true - _ = p.spinner.Stop() - activeSpinner = nil - } - Fatalf(err, format, a...) -} diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index eddce07a53..86c55b3240 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -117,24 +117,6 @@ func New(cfg *types.PackagerConfig, mods ...Modifier) (*Packager, error) { return pkgr, nil } -/* -NewOrDie creates a new package instance with the provided config or throws a fatal error. - -Note: This function creates a tmp directory that should be cleaned up with p.ClearTempPaths(). -*/ -func NewOrDie(config *types.PackagerConfig, mods ...Modifier) *Packager { - var ( - err error - pkgr *Packager - ) - - if pkgr, err = New(config, mods...); err != nil { - message.Fatalf(err, "Unable to setup the package config: %s", err.Error()) - } - - return pkgr -} - // setTempDirectory sets the temp directory for the packager. func (p *Packager) setTempDirectory(path string) error { dir, err := utils.MakeTempDir(path) diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 867706d957..716b672c75 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -472,7 +472,7 @@ func (p *Packager) setupState(ctx context.Context) (err error) { return nil }() if err != nil { - spinner.Fatalf(err, "Unable to create the zarf namespace") + return fmt.Errorf("unable to create the Zarf namespace: %w", err) } } diff --git a/src/pkg/packager/deploy_test.go b/src/pkg/packager/deploy_test.go index 8c69d1682f..5bb21bfcca 100644 --- a/src/pkg/packager/deploy_test.go +++ b/src/pkg/packager/deploy_test.go @@ -212,7 +212,8 @@ func TestGenerateValuesOverrides(t *testing.T) { for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { - p := NewOrDie(&types.PackagerConfig{DeployOpts: tt.deployOpts}, WithSource(&sources.TarballSource{})) + p, err := New(&types.PackagerConfig{DeployOpts: tt.deployOpts}, WithSource(&sources.TarballSource{})) + require.NoError(t, err) for k, v := range tt.setVariables { p.variableConfig.SetVariable(k, v, false, false, variables.RawVariableType) } diff --git a/src/pkg/pki/pki.go b/src/pkg/pki/pki.go index 00d1e212a6..6923042393 100644 --- a/src/pkg/pki/pki.go +++ b/src/pkg/pki/pki.go @@ -10,12 +10,12 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/pem" + "fmt" "math/big" "net" "time" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) @@ -29,49 +29,41 @@ const org = "Zarf Cluster" const validFor = time.Hour * 24 * 375 // GeneratePKI create a CA and signed server keypair. -func GeneratePKI(host string, dnsNames ...string) types.GeneratedPKI { +func GeneratePKI(host string, dnsNames ...string) (types.GeneratedPKI, error) { results := types.GeneratedPKI{} - ca, caKey, err := generateCA(validFor) if err != nil { - message.Fatal(err, "Unable to generate the ephemeral CA") + return types.GeneratedPKI{}, fmt.Errorf("unable to generate the ephemeral CA: %w", err) } - hostCert, hostKey, err := generateCert(host, ca, caKey, validFor, dnsNames...) if err != nil { - message.Fatalf(err, "Unable to generate the cert for %s", host) + return types.GeneratedPKI{}, fmt.Errorf("unable to generate the cert for %s: %w", host, err) } - results.CA = pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: ca.Raw, }) - results.Cert = pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: hostCert.Raw, }) - results.Key = pem.EncodeToMemory(&pem.Block{ Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(hostKey), }) - - return results + return results, nil } // newCertificate creates a new template. -func newCertificate(validFor time.Duration) *x509.Certificate { - notBefore := time.Now() - notAfter := notBefore.Add(validFor) - +func newCertificate(validFor time.Duration) (*x509.Certificate, error) { serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { - message.Fatalf(err, "failed to generate the certificate serial number") + return nil, fmt.Errorf("failed to generate the certificate serial number: %w", err) } - - return &x509.Certificate{ + notBefore := time.Now() + notAfter := notBefore.Add(validFor) + cert := &x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{org}, @@ -83,6 +75,7 @@ func newCertificate(validFor time.Duration) *x509.Certificate { ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } + return cert, nil } // newPrivateKey creates a new private key. @@ -95,7 +88,10 @@ func newPrivateKey() (*rsa.PrivateKey, error) { // private key should never be saved to disk, but rather used to // immediately generate further certificates. func generateCA(validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, error) { - template := newCertificate(validFor) + template, err := newCertificate(validFor) + if err != nil { + return nil, nil, err + } template.IsCA = true template.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} @@ -124,7 +120,10 @@ func generateCA(validFor time.Duration) (*x509.Certificate, *rsa.PrivateKey, err // provided certificate authority. The cert and key files are stored in // the provided files. func generateCert(host string, ca *x509.Certificate, caKey *rsa.PrivateKey, validFor time.Duration, dnsNames ...string) (*x509.Certificate, *rsa.PrivateKey, error) { - template := newCertificate(validFor) + template, err := newCertificate(validFor) + if err != nil { + return nil, nil, err + } template.IPAddresses = append(template.IPAddresses, net.ParseIP(helpers.IPV4Localhost)) From 8fc3ee7722c6d579d3342e8b3897dee5f48d0fc7 Mon Sep 17 00:00:00 2001 From: Austin Abro Date: Fri, 28 Jun 2024 15:37:22 +0000 Subject: [PATCH 104/132] merge groups merge groups --- .github/workflows/compare-cves.yml | 14 ++++++++++---- .github/workflows/dependency-review.yml | 4 +++- .github/workflows/scan-codeql.yml | 10 ++++++++++ .github/workflows/scan-docs-and-schema.yml | 1 + .github/workflows/scan-lint.yml | 4 +++- .github/workflows/test-bigbang.yml | 10 ++++++++++ .github/workflows/test-e2e-shim.yml | 10 ++++++++++ .github/workflows/test-e2e.yml | 10 ++++++++++ .github/workflows/test-external.yml | 10 ++++++++++ .github/workflows/test-site.yml | 1 + .github/workflows/test-unit.yml | 10 ++++++++++ .github/workflows/test-upgrade.yml | 10 ++++++++++ .github/workflows/test-windows.yml | 10 ++++++++++ 13 files changed, 98 insertions(+), 6 deletions(-) diff --git a/.github/workflows/compare-cves.yml b/.github/workflows/compare-cves.yml index f4c8500d88..dce4118ba5 100644 --- a/.github/workflows/compare-cves.yml +++ b/.github/workflows/compare-cves.yml @@ -6,10 +6,16 @@ permissions: on: pull_request: paths: - - "go.mod" - - "go.sum" - - "cargo.toml" - - "cargo.lock" + - "go.mod" + - "go.sum" + - "cargo.toml" + - "cargo.lock" + merge_group: + paths: + - "go.mod" + - "go.sum" + - "cargo.toml" + - "cargo.lock" jobs: validate: diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 89fd065a4d..2cd690860b 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,5 +1,7 @@ name: Dependency Review -on: pull_request +on: + pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/scan-codeql.yml b/.github/workflows/scan-codeql.yml index 7184bf1778..dd72737ab2 100644 --- a/.github/workflows/scan-codeql.yml +++ b/.github/workflows/scan-codeql.yml @@ -16,6 +16,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" schedule: - cron: "32 2 * * 5" diff --git a/.github/workflows/scan-docs-and-schema.yml b/.github/workflows/scan-docs-and-schema.yml index 0f24d27fc2..4d18ba393f 100644 --- a/.github/workflows/scan-docs-and-schema.yml +++ b/.github/workflows/scan-docs-and-schema.yml @@ -1,6 +1,7 @@ name: Validate Docs and Schema on: pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/scan-lint.yml b/.github/workflows/scan-lint.yml index e35d4f7330..d72450d66e 100644 --- a/.github/workflows/scan-lint.yml +++ b/.github/workflows/scan-lint.yml @@ -1,5 +1,7 @@ name: Validate Lint -on: pull_request +on: + pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/test-bigbang.yml b/.github/workflows/test-bigbang.yml index e7fbd80bda..1dd568bb26 100644 --- a/.github/workflows/test-bigbang.yml +++ b/.github/workflows/test-bigbang.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" permissions: contents: read diff --git a/.github/workflows/test-e2e-shim.yml b/.github/workflows/test-e2e-shim.yml index b79de0b0ca..95ba1c7184 100644 --- a/.github/workflows/test-e2e-shim.yml +++ b/.github/workflows/test-e2e-shim.yml @@ -1,6 +1,16 @@ name: Docs Skip Shim on: pull_request: + paths: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" + merge_group: paths: - "**.md" - "**.jpg" diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index a4716f24e2..7a689ba7c0 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" permissions: contents: read diff --git a/.github/workflows/test-external.yml b/.github/workflows/test-external.yml index f5b803bda4..f288989c8f 100644 --- a/.github/workflows/test-external.yml +++ b/.github/workflows/test-external.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" permissions: contents: read diff --git a/.github/workflows/test-site.yml b/.github/workflows/test-site.yml index 0bcb83d271..af0a3baef9 100644 --- a/.github/workflows/test-site.yml +++ b/.github/workflows/test-site.yml @@ -1,6 +1,7 @@ name: Test Site on: pull_request: + merge_group: permissions: contents: read diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 1f956b92a3..8ea2cc7bdb 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" push: # Running unit tests on main gives codecov a base to compare PRs against branches: diff --git a/.github/workflows/test-upgrade.yml b/.github/workflows/test-upgrade.yml index b7b8a96dde..4bbcf170b5 100644 --- a/.github/workflows/test-upgrade.yml +++ b/.github/workflows/test-upgrade.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" permissions: contents: read diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 38927d4230..08b1a6e845 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -10,6 +10,16 @@ on: - "adr/**" - "docs/**" - "CODEOWNERS" + merge_group: + paths-ignore: + - "**.md" + - "**.jpg" + - "**.png" + - "**.gif" + - "**.svg" + - "adr/**" + - "docs/**" + - "CODEOWNERS" permissions: contents: read From 0fd4992ac016fedbf1982a151bfc89a478ab2dc2 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:44:17 -0400 Subject: [PATCH 105/132] ci: remove dependency review merge queue and add label merge queue (#2688) --- .github/workflows/dependency-review.yml | 1 - .github/workflows/scan-labels.yml | 15 --------------- 2 files changed, 16 deletions(-) delete mode 100644 .github/workflows/scan-labels.yml diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 2cd690860b..8e12c590e0 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -1,7 +1,6 @@ name: Dependency Review on: pull_request: - merge_group: permissions: contents: read diff --git a/.github/workflows/scan-labels.yml b/.github/workflows/scan-labels.yml deleted file mode 100644 index b2ada16cda..0000000000 --- a/.github/workflows/scan-labels.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Validate Labels -on: - pull_request: - types: [labeled, unlabeled, opened, edited, synchronize] - -permissions: - contents: read - -jobs: - enforce: - runs-on: ubuntu-latest - steps: - - uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2 - with: - BANNED_LABELS: "needs-docs,needs-tests,needs-adr,needs-git-sign-off,needs-tutorial" From 43e50bb0c34ed891eccf41ca379bd58c17a73f3e Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Tue, 2 Jul 2024 20:50:55 +0200 Subject: [PATCH 106/132] refactor: remove warnings property from packager (#2687) --- src/pkg/packager/common.go | 22 +++----- src/pkg/packager/common_test.go | 95 +++++++++++---------------------- src/pkg/packager/create.go | 7 +-- src/pkg/packager/deploy.go | 27 +++++----- src/pkg/packager/dev.go | 2 +- src/pkg/packager/inspect.go | 2 +- src/pkg/packager/interactive.go | 7 ++- src/pkg/packager/mirror.go | 14 ++--- src/pkg/packager/prepare.go | 7 +-- src/pkg/packager/publish.go | 4 +- src/pkg/packager/remove.go | 6 +-- 11 files changed, 76 insertions(+), 117 deletions(-) diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index 86c55b3240..fb470d9ca6 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -35,7 +35,6 @@ type Packager struct { state *types.ZarfState cluster *cluster.Cluster layout *layout.PackagePaths - warnings []string hpaModified bool connectStrings types.ConnectStrings sbomViewFiles []string @@ -235,26 +234,18 @@ func (p *Packager) validatePackageArchitecture(ctx context.Context) error { } // validateLastNonBreakingVersion validates the Zarf CLI version against a package's LastNonBreakingVersion. -func (p *Packager) validateLastNonBreakingVersion() (err error) { - cliVersion := config.CLIVersion - lastNonBreakingVersion := p.cfg.Pkg.Build.LastNonBreakingVersion - +func validateLastNonBreakingVersion(cliVersion, lastNonBreakingVersion string) ([]string, error) { if lastNonBreakingVersion == "" { - return nil + return nil, nil } - lastNonBreakingSemVer, err := semver.NewVersion(lastNonBreakingVersion) if err != nil { - return fmt.Errorf("unable to parse lastNonBreakingVersion '%s' from Zarf package build data : %w", lastNonBreakingVersion, err) + return nil, fmt.Errorf("unable to parse last non breaking version %s from Zarf package build data: %w", lastNonBreakingVersion, err) } - cliSemVer, err := semver.NewVersion(cliVersion) if err != nil { - warning := fmt.Sprintf(lang.CmdPackageDeployInvalidCLIVersionWarn, config.CLIVersion) - p.warnings = append(p.warnings, warning) - return nil + return []string{fmt.Sprintf(lang.CmdPackageDeployInvalidCLIVersionWarn, cliVersion)}, nil } - if cliSemVer.LessThan(lastNonBreakingSemVer) { warning := fmt.Sprintf( lang.CmdPackageDeployValidateLastNonBreakingVersionWarn, @@ -262,8 +253,7 @@ func (p *Packager) validateLastNonBreakingVersion() (err error) { lastNonBreakingVersion, lastNonBreakingVersion, ) - p.warnings = append(p.warnings, warning) + return []string{warning}, nil } - - return nil + return nil, nil } diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go index b298a1d0de..6927082f9b 100644 --- a/src/pkg/packager/common_test.go +++ b/src/pkg/packager/common_test.go @@ -13,7 +13,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes/fake" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/types" @@ -120,98 +119,66 @@ func TestValidatePackageArchitecture(t *testing.T) { func TestValidateLastNonBreakingVersion(t *testing.T) { t.Parallel() - type testCase struct { + tests := []struct { name string cliVersion string lastNonBreakingVersion string - expectedErrorMessage string - expectedWarningMessage string - returnError bool - throwWarning bool - } - - testCases := []testCase{ + expectedErr string + expectedWarnings []string + }{ { - name: "CLI version less than lastNonBreakingVersion", + name: "CLI version less than last non breaking version", cliVersion: "v0.26.4", lastNonBreakingVersion: "v0.27.0", - returnError: false, - throwWarning: true, - expectedWarningMessage: fmt.Sprintf( - lang.CmdPackageDeployValidateLastNonBreakingVersionWarn, - "v0.26.4", - "v0.27.0", - "v0.27.0", - ), + expectedWarnings: []string{ + fmt.Sprintf( + lang.CmdPackageDeployValidateLastNonBreakingVersionWarn, + "v0.26.4", + "v0.27.0", + "v0.27.0", + ), + }, }, { - name: "invalid semantic version (CLI version)", + name: "invalid cli version", cliVersion: "invalidSemanticVersion", lastNonBreakingVersion: "v0.0.1", - returnError: false, - throwWarning: true, - expectedWarningMessage: fmt.Sprintf(lang.CmdPackageDeployInvalidCLIVersionWarn, "invalidSemanticVersion"), + expectedWarnings: []string{fmt.Sprintf(lang.CmdPackageDeployInvalidCLIVersionWarn, "invalidSemanticVersion")}, }, { - name: "invalid semantic version (lastNonBreakingVersion)", + name: "invalid last non breaking version", cliVersion: "v0.0.1", lastNonBreakingVersion: "invalidSemanticVersion", - throwWarning: false, - returnError: true, - expectedErrorMessage: "unable to parse lastNonBreakingVersion", + expectedErr: "unable to parse last non breaking version", }, { - name: "CLI version greater than lastNonBreakingVersion", + name: "CLI version greater than last non breaking version", cliVersion: "v0.28.2", lastNonBreakingVersion: "v0.27.0", - returnError: false, - throwWarning: false, }, { - name: "CLI version equal to lastNonBreakingVersion", + name: "CLI version equal to last non breaking version", cliVersion: "v0.27.0", lastNonBreakingVersion: "v0.27.0", - returnError: false, - throwWarning: false, }, { - name: "empty lastNonBreakingVersion", - cliVersion: "this shouldn't get evaluated when the lastNonBreakingVersion is empty", + name: "empty last non breaking version", + cliVersion: "", lastNonBreakingVersion: "", - returnError: false, - throwWarning: false, }, } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() - for _, testCase := range testCases { - testCase := testCase - - t.Run(testCase.name, func(t *testing.T) { - config.CLIVersion = testCase.cliVersion - - p := &Packager{ - cfg: &types.PackagerConfig{ - Pkg: types.ZarfPackage{ - Build: types.ZarfBuildData{ - LastNonBreakingVersion: testCase.lastNonBreakingVersion, - }, - }, - }, - } - - err := p.validateLastNonBreakingVersion() - - switch { - case testCase.returnError: - require.ErrorContains(t, err, testCase.expectedErrorMessage) - require.Empty(t, p.warnings, "Expected no warnings for test case: %s", testCase.name) - case testCase.throwWarning: - require.Contains(t, p.warnings, testCase.expectedWarningMessage) - require.NoError(t, err, "Expected no error for test case: %s", testCase.name) - default: - require.NoError(t, err, "Expected no error for test case: %s", testCase.name) - require.Empty(t, p.warnings, "Expected no warnings for test case: %s", testCase.name) + warnings, err := validateLastNonBreakingVersion(tt.cliVersion, tt.lastNonBreakingVersion) + if tt.expectedErr != "" { + require.ErrorContains(t, err, tt.expectedErr) + require.Empty(t, warnings) + return } + require.NoError(t, err) + require.ElementsMatch(t, tt.expectedWarnings, warnings) }) } } diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index ab46bdaa30..0423208639 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -17,7 +17,7 @@ import ( ) // Create generates a Zarf package tarball for a given PackageConfig and optional base directory. -func (p *Packager) Create(ctx context.Context) (err error) { +func (p *Packager) Create(ctx context.Context) error { cwd, err := os.Getwd() if err != nil { return err @@ -35,12 +35,13 @@ func (p *Packager) Create(ctx context.Context) (err error) { return err } - p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(ctx, p.layout) + pkg, warnings, err := pc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } + p.cfg.Pkg = pkg - if !p.confirmAction(config.ZarfCreateStage) { + if !p.confirmAction(config.ZarfCreateStage, warnings) { return fmt.Errorf("package creation canceled") } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 716b672c75..094e7cfef7 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -47,8 +47,7 @@ func (p *Packager) resetRegistryHPA(ctx context.Context) { } // Deploy attempts to deploy the given PackageConfig. -func (p *Packager) Deploy(ctx context.Context) (err error) { - +func (p *Packager) Deploy(ctx context.Context) error { isInteractive := !config.CommonOptions.Confirm deployFilter := filters.Combine( @@ -56,38 +55,42 @@ func (p *Packager) Deploy(ctx context.Context) (err error) { filters.ForDeploy(p.cfg.PkgOpts.OptionalComponents, isInteractive), ) + warnings := []string{} if isInteractive { filter := filters.Empty() - - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, true) + pkg, loadWarnings, err := p.source.LoadPackage(ctx, p.layout, filter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } + p.cfg.Pkg = pkg + warnings = append(warnings, loadWarnings...) } else { - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, deployFilter, true) + pkg, loadWarnings, err := p.source.LoadPackage(ctx, p.layout, deployFilter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } - + p.cfg.Pkg = pkg + warnings = append(warnings, loadWarnings...) if err := p.populatePackageVariableConfig(); err != nil { return fmt.Errorf("unable to set the active variables: %w", err) } } - if err := p.validateLastNonBreakingVersion(); err != nil { + validateWarnings, err := validateLastNonBreakingVersion(config.CLIVersion, p.cfg.Pkg.Build.LastNonBreakingVersion) + if err != nil { return err } + warnings = append(warnings, validateWarnings...) - var sbomWarnings []string - p.sbomViewFiles, sbomWarnings, err = p.layout.SBOMs.StageSBOMViewFiles() + sbomViewFiles, sbomWarnings, err := p.layout.SBOMs.StageSBOMViewFiles() if err != nil { return err } - - p.warnings = append(p.warnings, sbomWarnings...) + p.sbomViewFiles = sbomViewFiles + warnings = append(warnings, sbomWarnings...) // Confirm the overall package deployment - if !p.confirmAction(config.ZarfDeployStage) { + if !p.confirmAction(config.ZarfDeployStage, warnings) { return fmt.Errorf("deployment cancelled") } diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index 5332d10c10..138d175109 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -44,7 +44,7 @@ func (p *Packager) DevDeploy(ctx context.Context) error { return err } - p.cfg.Pkg, p.warnings, err = pc.LoadPackageDefinition(ctx, p.layout) + p.cfg.Pkg, _, err = pc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go index d7e4be3e18..d68ae5f5de 100644 --- a/src/pkg/packager/inspect.go +++ b/src/pkg/packager/inspect.go @@ -18,7 +18,7 @@ import ( func (p *Packager) Inspect(ctx context.Context) (err error) { wantSBOM := p.cfg.InspectOpts.ViewSBOM || p.cfg.InspectOpts.SBOMOutputDir != "" - p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(ctx, p.layout, wantSBOM, true) + p.cfg.Pkg, _, err = p.source.LoadPackageMetadata(ctx, p.layout, wantSBOM, true) if err != nil { return err } diff --git a/src/pkg/packager/interactive.go b/src/pkg/packager/interactive.go index 2326a4910a..d481b06d63 100644 --- a/src/pkg/packager/interactive.go +++ b/src/pkg/packager/interactive.go @@ -18,8 +18,7 @@ import ( "github.com/pterm/pterm" ) -func (p *Packager) confirmAction(stage string) (confirm bool) { - +func (p *Packager) confirmAction(stage string, warnings []string) (confirm bool) { pterm.Println() message.HeaderInfof("📦 PACKAGE DEFINITION") utils.ColorPrintYAML(p.cfg.Pkg, p.getPackageYAMLHints(stage), true) @@ -54,10 +53,10 @@ func (p *Packager) confirmAction(stage string) (confirm bool) { } } - if len(p.warnings) > 0 { + if len(warnings) > 0 { message.HorizontalRule() message.Title("Package Warnings", "the following warnings were flagged while reading the package") - for _, warning := range p.warnings { + for _, warning := range warnings { message.Warn(warning) } } diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go index f19536443c..3e1782755f 100644 --- a/src/pkg/packager/mirror.go +++ b/src/pkg/packager/mirror.go @@ -17,27 +17,27 @@ import ( ) // Mirror pulls resources from a package (images, git repositories, etc) and pushes them to remotes in the air gap without deploying them -func (p *Packager) Mirror(ctx context.Context) (err error) { +func (p *Packager) Mirror(ctx context.Context) error { filter := filters.Combine( filters.ByLocalOS(runtime.GOOS), filters.BySelectState(p.cfg.PkgOpts.OptionalComponents), ) - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, true) + pkg, warnings, err := p.source.LoadPackage(ctx, p.layout, filter, true) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } + p.cfg.Pkg = pkg - var sbomWarnings []string - p.sbomViewFiles, sbomWarnings, err = p.layout.SBOMs.StageSBOMViewFiles() + sbomViewFiles, sbomWarnings, err := p.layout.SBOMs.StageSBOMViewFiles() if err != nil { return err } - - p.warnings = append(p.warnings, sbomWarnings...) + p.sbomViewFiles = sbomViewFiles + warnings = append(warnings, sbomWarnings...) // Confirm the overall package mirror - if !p.confirmAction(config.ZarfMirrorStage) { + if !p.confirmAction(config.ZarfMirrorStage, warnings) { return fmt.Errorf("mirror cancelled") } diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go index 4bd9c88e54..9a4f7a1280 100644 --- a/src/pkg/packager/prepare.go +++ b/src/pkg/packager/prepare.go @@ -38,7 +38,7 @@ import ( type imageMap map[string]bool // FindImages iterates over a Zarf.yaml and attempts to parse any images. -func (p *Packager) FindImages(ctx context.Context) (imgMap map[string][]string, err error) { +func (p *Packager) FindImages(ctx context.Context) (map[string][]string, error) { cwd, err := os.Getwd() if err != nil { return nil, err @@ -60,12 +60,13 @@ func (p *Packager) FindImages(ctx context.Context) (imgMap map[string][]string, return nil, err } - p.cfg.Pkg, p.warnings, err = c.LoadPackageDefinition(ctx, p.layout) + pkg, warnings, err := c.LoadPackageDefinition(ctx, p.layout) if err != nil { return nil, err } + p.cfg.Pkg = pkg - for _, warning := range p.warnings { + for _, warning := range warnings { message.Warn(warning) } diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go index e889ea89b6..6f5e90843c 100644 --- a/src/pkg/packager/publish.go +++ b/src/pkg/packager/publish.go @@ -58,7 +58,7 @@ func (p *Packager) Publish(ctx context.Context) (err error) { return err } - p.cfg.Pkg, p.warnings, err = sc.LoadPackageDefinition(ctx, p.layout) + p.cfg.Pkg, _, err = sc.LoadPackageDefinition(ctx, p.layout) if err != nil { return err } @@ -72,7 +72,7 @@ func (p *Packager) Publish(ctx context.Context) (err error) { } } else { filter := filters.Empty() - p.cfg.Pkg, p.warnings, err = p.source.LoadPackage(ctx, p.layout, filter, false) + p.cfg.Pkg, _, err = p.source.LoadPackage(ctx, p.layout, filter, false) if err != nil { return fmt.Errorf("unable to load the package: %w", err) } diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index 091f446ae2..be34b6dda7 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -37,15 +37,13 @@ func (p *Packager) Remove(ctx context.Context) (err error) { spinner := message.NewProgressSpinner("Removing Zarf package %s", p.cfg.PkgOpts.PackageSource) defer spinner.Stop() - var packageName string - // we do not want to allow removal of signed packages without a signature if there are remove actions // as this is arbitrary code execution from an untrusted source - p.cfg.Pkg, p.warnings, err = p.source.LoadPackageMetadata(ctx, p.layout, false, false) + p.cfg.Pkg, _, err = p.source.LoadPackageMetadata(ctx, p.layout, false, false) if err != nil { return err } - packageName = p.cfg.Pkg.Metadata.Name + packageName := p.cfg.Pkg.Metadata.Name // Build a list of components to remove and determine if we need a cluster connection componentsToRemove := []string{} From 014d1d22544c1b85f2444b9cce7584eac6a70ab2 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 3 Jul 2024 17:45:49 +0200 Subject: [PATCH 107/132] refactor: remove sbom view files property from packager (#2695) --- src/pkg/packager/common.go | 1 - src/pkg/packager/create.go | 2 +- src/pkg/packager/deploy.go | 3 +-- src/pkg/packager/interactive.go | 12 ++++++------ src/pkg/packager/mirror.go | 3 +-- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index fb470d9ca6..0f59319ae6 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -37,7 +37,6 @@ type Packager struct { layout *layout.PackagePaths hpaModified bool connectStrings types.ConnectStrings - sbomViewFiles []string source sources.PackageSource generation int } diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index 0423208639..ee9a7fd2bc 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -41,7 +41,7 @@ func (p *Packager) Create(ctx context.Context) error { } p.cfg.Pkg = pkg - if !p.confirmAction(config.ZarfCreateStage, warnings) { + if !p.confirmAction(config.ZarfCreateStage, warnings, nil) { return fmt.Errorf("package creation canceled") } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 094e7cfef7..8c0eb6b7a4 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -86,11 +86,10 @@ func (p *Packager) Deploy(ctx context.Context) error { if err != nil { return err } - p.sbomViewFiles = sbomViewFiles warnings = append(warnings, sbomWarnings...) // Confirm the overall package deployment - if !p.confirmAction(config.ZarfDeployStage, warnings) { + if !p.confirmAction(config.ZarfDeployStage, warnings, sbomViewFiles) { return fmt.Errorf("deployment cancelled") } diff --git a/src/pkg/packager/interactive.go b/src/pkg/packager/interactive.go index d481b06d63..8c67fd55fe 100644 --- a/src/pkg/packager/interactive.go +++ b/src/pkg/packager/interactive.go @@ -18,7 +18,7 @@ import ( "github.com/pterm/pterm" ) -func (p *Packager) confirmAction(stage string, warnings []string) (confirm bool) { +func (p *Packager) confirmAction(stage string, warnings []string, sbomViewFiles []string) (confirm bool) { pterm.Println() message.HeaderInfof("📦 PACKAGE DEFINITION") utils.ColorPrintYAML(p.cfg.Pkg, p.getPackageYAMLHints(stage), true) @@ -30,14 +30,14 @@ func (p *Packager) confirmAction(stage string, warnings []string) (confirm bool) message.HorizontalRule() message.Title("Software Bill of Materials", "an inventory of all software contained in this package") - if len(p.sbomViewFiles) > 0 { + if len(sbomViewFiles) > 0 { cwd, _ := os.Getwd() - link := pterm.FgLightCyan.Sprint(pterm.Bold.Sprint(filepath.Join(cwd, layout.SBOMDir, filepath.Base(p.sbomViewFiles[0])))) + link := pterm.FgLightCyan.Sprint(pterm.Bold.Sprint(filepath.Join(cwd, layout.SBOMDir, filepath.Base(sbomViewFiles[0])))) inspect := pterm.BgBlack.Sprint(pterm.FgWhite.Sprint(pterm.Bold.Sprintf("$ zarf package inspect %s", p.cfg.PkgOpts.PackageSource))) - artifactMsg := pterm.Bold.Sprintf("%d artifacts", len(p.sbomViewFiles)) + " to be reviewed. These are" - if len(p.sbomViewFiles) == 1 { - artifactMsg = pterm.Bold.Sprintf("%d artifact", len(p.sbomViewFiles)) + " to be reviewed. This is" + artifactMsg := pterm.Bold.Sprintf("%d artifacts", len(sbomViewFiles)) + " to be reviewed. These are" + if len(sbomViewFiles) == 1 { + artifactMsg = pterm.Bold.Sprintf("%d artifact", len(sbomViewFiles)) + " to be reviewed. This is" } msg := fmt.Sprintf("This package has %s available in a temporary '%s' folder in this directory and will be removed upon deployment.\n", artifactMsg, pterm.Bold.Sprint("zarf-sbom")) diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go index 3e1782755f..27ffaf9c18 100644 --- a/src/pkg/packager/mirror.go +++ b/src/pkg/packager/mirror.go @@ -33,11 +33,10 @@ func (p *Packager) Mirror(ctx context.Context) error { if err != nil { return err } - p.sbomViewFiles = sbomViewFiles warnings = append(warnings, sbomWarnings...) // Confirm the overall package mirror - if !p.confirmAction(config.ZarfMirrorStage, warnings) { + if !p.confirmAction(config.ZarfMirrorStage, warnings, sbomViewFiles) { return fmt.Errorf("mirror cancelled") } From 7288cf2b6c40570d84128fd4ec6673a3277aedee Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 8 Jul 2024 18:05:47 +0200 Subject: [PATCH 108/132] fix: remove ignore label when adopting resource (#2699) --- src/pkg/cluster/namespace.go | 1 + src/pkg/cluster/namespace_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/pkg/cluster/namespace_test.go diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index c65c968d0e..997247bfa9 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -67,5 +67,6 @@ func AdoptZarfManagedLabels(labels map[string]string) map[string]string { labels = make(map[string]string) } labels[ZarfManagedByLabel] = "zarf" + delete(labels, AgentLabel) return labels } diff --git a/src/pkg/cluster/namespace_test.go b/src/pkg/cluster/namespace_test.go new file mode 100644 index 0000000000..1fcc9fe8e9 --- /dev/null +++ b/src/pkg/cluster/namespace_test.go @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package cluster + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAdoptZarfManagedLabels(t *testing.T) { + t.Parallel() + + labels := map[string]string{ + "foo": "bar", + AgentLabel: "ignore", + } + adoptedLabels := AdoptZarfManagedLabels(labels) + expectedLabels := map[string]string{ + "foo": "bar", + ZarfManagedByLabel: "zarf", + } + require.Equal(t, expectedLabels, adoptedLabels) +} From 05479771f25b1501acc06cc96d4024b5a0ea320c Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:45:32 -0400 Subject: [PATCH 109/132] fix: revert fix: remove ignore label when adopting resource (#2711) --- src/pkg/cluster/namespace.go | 1 - src/pkg/cluster/namespace_test.go | 25 ------------------------- 2 files changed, 26 deletions(-) delete mode 100644 src/pkg/cluster/namespace_test.go diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 997247bfa9..c65c968d0e 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -67,6 +67,5 @@ func AdoptZarfManagedLabels(labels map[string]string) map[string]string { labels = make(map[string]string) } labels[ZarfManagedByLabel] = "zarf" - delete(labels, AgentLabel) return labels } diff --git a/src/pkg/cluster/namespace_test.go b/src/pkg/cluster/namespace_test.go deleted file mode 100644 index 1fcc9fe8e9..0000000000 --- a/src/pkg/cluster/namespace_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -package cluster - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestAdoptZarfManagedLabels(t *testing.T) { - t.Parallel() - - labels := map[string]string{ - "foo": "bar", - AgentLabel: "ignore", - } - adoptedLabels := AdoptZarfManagedLabels(labels) - expectedLabels := map[string]string{ - "foo": "bar", - ZarfManagedByLabel: "zarf", - } - require.Equal(t, expectedLabels, adoptedLabels) -} From ecdac0bdcd4cb474cd7fa24a0cc6c5ed597a13f6 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:21:14 -0400 Subject: [PATCH 110/132] ci: run e2e tests (#2710) --- src/test/e2e/main_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/e2e/main_test.go b/src/test/e2e/main_test.go index 6c9c283a55..9670335641 100644 --- a/src/test/e2e/main_test.go +++ b/src/test/e2e/main_test.go @@ -38,6 +38,7 @@ func TestMain(m *testing.M) { e2e.ZarfBinPath = filepath.Join("build", test.GetCLIName()) e2e.ApplianceMode = os.Getenv(applianceModeEnvVar) == "true" e2e.ApplianceModeKeep = os.Getenv(applianceModeKeepEnvVar) == "true" + e2e.RunClusterTests = true message.SetLogLevel(message.TraceLevel) From d05c409c1cbfe8bf83bf698acfa835555e3b9d05 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 10 Jul 2024 19:51:19 +0200 Subject: [PATCH 111/132] refactor: test and refactor split file (#2708) Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/pkg/layout/package.go | 2 +- src/pkg/layout/split.go | 109 +++++++++++++++++++++++++++ src/pkg/layout/split_test.go | 96 +++++++++++++++++++++++ src/pkg/utils/io.go | 142 ----------------------------------- 4 files changed, 206 insertions(+), 143 deletions(-) create mode 100644 src/pkg/layout/split.go create mode 100644 src/pkg/layout/split_test.go diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go index e68f77b551..1b99379e81 100644 --- a/src/pkg/layout/package.go +++ b/src/pkg/layout/package.go @@ -243,7 +243,7 @@ func (pp *PackagePaths) ArchivePackage(destinationTarball string, maxPackageSize return fmt.Errorf("unable to split the package archive into multiple files: must be less than 1,000 files") } message.Notef("Package is larger than %dMB, splitting into multiple files", maxPackageSizeMB) - err := utils.SplitFile(destinationTarball, chunkSize) + err := splitFile(destinationTarball, chunkSize) if err != nil { return fmt.Errorf("unable to split the package archive into multiple files: %w", err) } diff --git a/src/pkg/layout/split.go b/src/pkg/layout/split.go new file mode 100644 index 0000000000..aecf295dad --- /dev/null +++ b/src/pkg/layout/split.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package layout contains functions for interacting with Zarf's package layout on disk. +package layout + +import ( + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io" + "os" + + "github.com/defenseunicorns/pkg/helpers/v2" + "github.com/defenseunicorns/zarf/src/pkg/message" + "github.com/defenseunicorns/zarf/src/types" +) + +// splitFile will split the file into chunks and remove the original file. +func splitFile(srcPath string, chunkSize int) error { + srcFile, err := os.Open(srcPath) + if err != nil { + return err + } + defer srcFile.Close() + fi, err := srcFile.Stat() + if err != nil { + return err + } + + title := fmt.Sprintf("[0/%d] MB bytes written", fi.Size()/1000/1000) + progressBar := message.NewProgressBar(fi.Size(), title) + defer progressBar.Close() + + hash := sha256.New() + fileCount := 0 + for { + path := fmt.Sprintf("%s.part%03d", srcPath, fileCount+1) + dstFile, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, helpers.ReadAllWriteUser) + if err != nil { + return err + } + defer dstFile.Close() + + written, copyErr := io.CopyN(dstFile, srcFile, int64(chunkSize)) + if copyErr != nil && !errors.Is(copyErr, io.EOF) { + return err + } + progressBar.Add(int(written)) + title := fmt.Sprintf("[%d/%d] MB bytes written", progressBar.GetCurrent()/1000/1000, fi.Size()/1000/1000) + progressBar.Updatef(title) + + _, err = dstFile.Seek(0, io.SeekStart) + if err != nil { + return err + } + _, err = io.Copy(hash, dstFile) + if err != nil { + return err + } + err = dstFile.Close() + if err != nil { + return err + } + + // EOF error could be returned on 0 bytes written. + if written == 0 { + err = os.Remove(path) + if err != nil { + return err + } + break + } + + fileCount++ + if errors.Is(copyErr, io.EOF) { + break + } + } + + // Remove original file + err = srcFile.Close() + if err != nil { + return err + } + err = os.Remove(srcPath) + if err != nil { + return err + } + + // Write header file + data := types.ZarfSplitPackageData{ + Count: fileCount, + Bytes: fi.Size(), + Sha256Sum: fmt.Sprintf("%x", hash.Sum(nil)), + } + b, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("unable to marshal the split package data: %w", err) + } + path := fmt.Sprintf("%s.part000", srcPath) + if err := os.WriteFile(path, b, helpers.ReadAllWriteUser); err != nil { + return fmt.Errorf("unable to write the file %s: %w", path, err) + } + progressBar.Successf("Package split across %d files", fileCount+1) + + return nil +} diff --git a/src/pkg/layout/split_test.go b/src/pkg/layout/split_test.go new file mode 100644 index 0000000000..b7f48fc9f6 --- /dev/null +++ b/src/pkg/layout/split_test.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package layout + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestSplitFile(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + fileSize int + chunkSize int + expectedFileSize int64 + expectedLastFileSize int64 + expectedFileCount int + expectedSha256Sum string + }{ + { + name: "split evenly", + fileSize: 2048, + chunkSize: 16, + expectedFileSize: 16, + expectedLastFileSize: 16, + expectedFileCount: 128, + expectedSha256Sum: "93ecad679eff0df493aaf5d7d615211b0f1d7a919016efb15c98f0b8efb1ba43", + }, + { + name: "split with remainder", + fileSize: 2048, + chunkSize: 10, + expectedFileSize: 10, + expectedLastFileSize: 8, + expectedFileCount: 205, + expectedSha256Sum: "fe8460f4d53d3578aa37191acf55b3db7bbcb706056f4b6b02a0c70f24b0d95a", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + name := "random" + p := filepath.Join(dir, name) + f, err := os.Create(p) + require.NoError(t, err) + b := make([]byte, tt.fileSize) + for i := range tt.fileSize { + b[i] = byte(tt.chunkSize) + } + require.NoError(t, err) + _, err = f.Write(b) + require.NoError(t, err) + f.Close() + + err = splitFile(p, tt.chunkSize) + require.NoError(t, err) + + _, err = os.Stat(p) + require.ErrorIs(t, err, os.ErrNotExist) + entries, err := os.ReadDir(dir) + require.NoError(t, err) + require.Len(t, entries, tt.expectedFileCount+1) + for i, entry := range entries[1:] { + require.Equal(t, fmt.Sprintf("%s.part%03d", name, i+1), entry.Name()) + + fi, err := entry.Info() + require.NoError(t, err) + if i == len(entries)-2 { + require.Equal(t, tt.expectedLastFileSize, fi.Size()) + } else { + require.Equal(t, tt.expectedFileSize, fi.Size()) + } + } + + b, err = os.ReadFile(filepath.Join(dir, fmt.Sprintf("%s.part000", name))) + require.NoError(t, err) + var data types.ZarfSplitPackageData + err = json.Unmarshal(b, &data) + require.NoError(t, err) + require.Equal(t, tt.expectedFileCount, data.Count) + require.Equal(t, int64(tt.fileSize), data.Bytes) + require.Equal(t, tt.expectedSha256Sum, data.Sha256Sum) + }) + } +} diff --git a/src/pkg/utils/io.go b/src/pkg/utils/io.go index 8b9592def3..2fd4cfc72b 100755 --- a/src/pkg/utils/io.go +++ b/src/pkg/utils/io.go @@ -5,17 +5,13 @@ package utils import ( - "crypto/sha256" - "encoding/json" "fmt" - "io" "os" "path/filepath" "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" - "github.com/defenseunicorns/zarf/src/types" ) const ( @@ -73,141 +69,3 @@ func GetFinalExecutableCommand() (string, error) { return zarfCommand, err } - -// SplitFile will take a srcFile path and split it into files based on chunkSizeBytes -// the first file will be a metadata file containing: -// - sha256sum of the original file -// - number of bytes in the original file -// - number of files the srcFile was split into -// SplitFile will delete the original file -// -// Returns: -// - fileNames: list of file paths srcFile was split across -// - sha256sum: sha256sum of the srcFile before splitting -// - err: any errors encountered -func SplitFile(srcPath string, chunkSizeBytes int) (err error) { - var fileNames []string - var sha256sum string - hash := sha256.New() - - // Set buffer size to some multiple of 4096 KiB for modern file system cluster sizes - bufferSize := 16 * 1024 * 1024 // 16 MiB - // if chunkSizeBytes is less than bufferSize, use chunkSizeBytes as bufferSize for simplicity - if chunkSizeBytes < bufferSize { - bufferSize = chunkSizeBytes - } - buf := make([]byte, bufferSize) - - // get file size - fi, err := os.Stat(srcPath) - if err != nil { - return err - } - fileSize := fi.Size() - - // start progress bar - title := fmt.Sprintf("[0/%d] MB bytes written", fileSize/1000/1000) - progressBar := message.NewProgressBar(fileSize, title) - defer progressBar.Close() - - // open srcFile - srcFile, err := os.Open(srcPath) - if err != nil { - return err - } - defer srcFile.Close() - - // create file path starting from part 001 - path := fmt.Sprintf("%s.part001", srcPath) - chunkFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, helpers.ReadAllWriteUser) - if err != nil { - return err - } - fileNames = append(fileNames, path) - defer chunkFile.Close() - - // setup counter for tracking how many bytes are left to write to file - chunkBytesRemaining := chunkSizeBytes - // Loop over the tarball hashing as we go and breaking it into chunks based on the chunkSizeBytes - for { - bytesRead, err := srcFile.Read(buf) - - if err != nil { - if err == io.EOF { - // At end of file, break out of loop - break - } - return err - } - - // Pass data to hash - hash.Write(buf[0:bytesRead]) - - // handle if we should split the data between two chunks - if chunkBytesRemaining < bytesRead { - // write the remaining chunk size to file - _, err := chunkFile.Write(buf[0:chunkBytesRemaining]) - if err != nil { - return err - } - err = chunkFile.Close() - if err != nil { - return err - } - - // create new file - path = fmt.Sprintf("%s.part%03d", srcPath, len(fileNames)+1) - chunkFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, helpers.ReadAllWriteUser) - if err != nil { - return err - } - fileNames = append(fileNames, path) - defer chunkFile.Close() - - // write to new file where we left off - _, err = chunkFile.Write(buf[chunkBytesRemaining:bytesRead]) - if err != nil { - return err - } - - // set chunkBytesRemaining considering how many bytes are already written to new file - chunkBytesRemaining = chunkSizeBytes - (bufferSize - chunkBytesRemaining) - } else { - _, err := chunkFile.Write(buf[0:bytesRead]) - if err != nil { - return err - } - chunkBytesRemaining = chunkBytesRemaining - bytesRead - } - - // update progress bar - progressBar.Add(bufferSize) - title := fmt.Sprintf("[%d/%d] MB bytes written", progressBar.GetCurrent()/1000/1000, fileSize/1000/1000) - progressBar.Updatef(title) - } - srcFile.Close() - _ = os.RemoveAll(srcPath) - - // calculate sha256 sum - sha256sum = fmt.Sprintf("%x", hash.Sum(nil)) - - // Marshal the data into a json file. - jsonData, err := json.Marshal(types.ZarfSplitPackageData{ - Count: len(fileNames), - Bytes: fileSize, - Sha256Sum: sha256sum, - }) - if err != nil { - return fmt.Errorf("unable to marshal the split package data: %w", err) - } - - // write header file - path = fmt.Sprintf("%s.part000", srcPath) - if err := os.WriteFile(path, jsonData, helpers.ReadAllWriteUser); err != nil { - return fmt.Errorf("unable to write the file %s: %w", path, err) - } - fileNames = append(fileNames, path) - progressBar.Successf("Package split across %d files", len(fileNames)) - - return nil -} From 2ef121628231b4cdd60e90db5de89685e25cdfaf Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 10 Jul 2024 20:28:10 +0200 Subject: [PATCH 112/132] refactor: remove unused message functions and verbose logging (#2712) Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/dev.go | 7 +++- .../agent/hooks/argocd-application.go | 1 - src/internal/agent/hooks/argocd-repository.go | 1 - src/internal/agent/hooks/flux-gitrepo.go | 1 - src/internal/agent/hooks/flux-helmrepo.go | 1 - src/internal/agent/hooks/flux-ocirepo.go | 1 - src/internal/agent/hooks/pods.go | 4 --- src/internal/agent/http/admission/handler.go | 3 -- src/internal/agent/start.go | 2 -- src/pkg/cluster/data.go | 7 +++- src/pkg/cluster/secrets.go | 2 -- src/pkg/cluster/state.go | 6 +++- src/pkg/message/connect.go | 2 -- src/pkg/message/message.go | 36 ------------------- src/pkg/utils/cosign.go | 2 -- src/pkg/utils/io.go | 2 -- src/pkg/utils/yaml.go | 2 -- src/test/e2e/05_tarball_test.go | 11 ------ 18 files changed, 17 insertions(+), 74 deletions(-) diff --git a/src/cmd/dev.go b/src/cmd/dev.go index f6fad80cbe..e304ca2edf 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -23,6 +23,8 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" "github.com/mholt/archiver/v3" + "github.com/pterm/pterm" + "github.com/sergi/go-diff/diffmatchpatch" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -110,7 +112,10 @@ var devTransformGitLinksCmd = &cobra.Command{ processedText := transform.MutateGitURLsInText(message.Warnf, pkgConfig.InitOpts.GitServer.Address, text, pkgConfig.InitOpts.GitServer.PushUsername) // Print the differences - message.PrintDiff(text, processedText) + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(text, processedText, true) + diffs = dmp.DiffCleanupSemantic(diffs) + pterm.Println(dmp.DiffPrettyText(diffs)) // Ask the user before this destructive action confirm := false diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go index a09b6d003f..b9197bae51 100644 --- a/src/internal/agent/hooks/argocd-application.go +++ b/src/internal/agent/hooks/argocd-application.go @@ -48,7 +48,6 @@ type ApplicationSource struct { // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook. func NewApplicationMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewApplicationMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutateApplication(ctx, r, cluster) diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go index 40506a395f..23e05df999 100644 --- a/src/internal/agent/hooks/argocd-repository.go +++ b/src/internal/agent/hooks/argocd-repository.go @@ -36,7 +36,6 @@ type RepoCreds struct { // NewRepositorySecretMutationHook creates a new instance of the ArgoCD repository secret mutation hook. func NewRepositorySecretMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewRepositoryMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutateRepositorySecret(ctx, r, cluster) diff --git a/src/internal/agent/hooks/flux-gitrepo.go b/src/internal/agent/hooks/flux-gitrepo.go index 2638da0b5b..8256b40aae 100644 --- a/src/internal/agent/hooks/flux-gitrepo.go +++ b/src/internal/agent/hooks/flux-gitrepo.go @@ -26,7 +26,6 @@ const AgentErrTransformGitURL = "unable to transform the git url" // NewGitRepositoryMutationHook creates a new instance of the git repo mutation hook. func NewGitRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewGitRepositoryMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutateGitRepo(ctx, r, cluster) diff --git a/src/internal/agent/hooks/flux-helmrepo.go b/src/internal/agent/hooks/flux-helmrepo.go index 750eeb0406..6fb0e7e712 100644 --- a/src/internal/agent/hooks/flux-helmrepo.go +++ b/src/internal/agent/hooks/flux-helmrepo.go @@ -24,7 +24,6 @@ import ( // NewHelmRepositoryMutationHook creates a new instance of the helm repo mutation hook. func NewHelmRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewHelmRepositoryMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutateHelmRepo(ctx, r, cluster) diff --git a/src/internal/agent/hooks/flux-ocirepo.go b/src/internal/agent/hooks/flux-ocirepo.go index c026b2e7cc..00d7ded917 100644 --- a/src/internal/agent/hooks/flux-ocirepo.go +++ b/src/internal/agent/hooks/flux-ocirepo.go @@ -23,7 +23,6 @@ import ( // NewOCIRepositoryMutationHook creates a new instance of the oci repo mutation hook. func NewOCIRepositoryMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewOCIRepositoryMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutateOCIRepo(ctx, r, cluster) diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go index 299b841f31..a152aa4a8a 100644 --- a/src/internal/agent/hooks/pods.go +++ b/src/internal/agent/hooks/pods.go @@ -22,7 +22,6 @@ import ( // NewPodMutationHook creates a new instance of pods mutation hook. func NewPodMutationHook(ctx context.Context, cluster *cluster.Cluster) operations.Hook { - message.Debug("hooks.NewMutationHook()") return operations.Hook{ Create: func(r *v1.AdmissionRequest) (*operations.Result, error) { return mutatePod(ctx, r, cluster) @@ -34,7 +33,6 @@ func NewPodMutationHook(ctx context.Context, cluster *cluster.Cluster) operation } func parsePod(object []byte) (*corev1.Pod, error) { - message.Debugf("pods.parsePod(%s)", string(object)) var pod corev1.Pod if err := json.Unmarshal(object, &pod); err != nil { return nil, err @@ -43,8 +41,6 @@ func parsePod(object []byte) (*corev1.Pod, error) { } func mutatePod(ctx context.Context, r *v1.AdmissionRequest, cluster *cluster.Cluster) (*operations.Result, error) { - message.Debugf("hooks.mutatePod()(*v1.AdmissionRequest) - %#v , %s/%s: %#v", r.Kind, r.Namespace, r.Name, r.Operation) - pod, err := parsePod(r.Object.Raw) if err != nil { return nil, fmt.Errorf(lang.AgentErrParsePod, err) diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go index 68a17cbee5..f1d3cd8ead 100644 --- a/src/internal/agent/http/admission/handler.go +++ b/src/internal/agent/http/admission/handler.go @@ -35,10 +35,7 @@ func NewHandler() *Handler { // Serve returns an http.HandlerFunc for an admission webhook. func (h *Handler) Serve(hook operations.Hook) http.HandlerFunc { - message.Debugf("http.Serve(%#v)", hook) return func(w http.ResponseWriter, r *http.Request) { - message.Debugf("http.Serve()(writer, %#v)", r.URL) - w.Header().Set("Content-Type", "application/json") if r.Method != http.MethodPost { http.Error(w, lang.AgentErrInvalidMethod, http.StatusMethodNotAllowed) diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go index f110ce2b52..9acd2afcc4 100644 --- a/src/internal/agent/start.go +++ b/src/internal/agent/start.go @@ -29,7 +29,6 @@ const ( // StartWebhook launches the Zarf agent mutating webhook in the cluster. func StartWebhook(ctx context.Context) error { - message.Debug("agent.StartWebhook()") srv, err := agentHttp.NewAdmissionServer(ctx, httpPort) if err != nil { return err @@ -39,7 +38,6 @@ func StartWebhook(ctx context.Context) error { // StartHTTPProxy launches the zarf agent proxy in the cluster. func StartHTTPProxy(ctx context.Context) error { - message.Debug("agent.StartHttpProxy()") return startServer(ctx, agentHttp.NewProxyServer(httpPort)) } diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index e47e80cbfe..21a060742e 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -6,6 +6,7 @@ package cluster import ( "context" + "encoding/json" "fmt" "os" "path/filepath" @@ -46,8 +47,12 @@ func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, d // Pod filter to ensure we only use the current deployment's pods podFilterByInitContainer := func(pod corev1.Pod) bool { + b, err := json.Marshal(pod) + if err != nil { + return false + } // Look everywhere in the pod for a matching data injection marker - return strings.Contains(message.JSONValue(pod), config.GetDataInjectionMarker()) + return strings.Contains(string(b), config.GetDataInjectionMarker()) } // Get the OS shell to execute commands in diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 09be8beba5..d083f6bbf4 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -89,8 +89,6 @@ func (c *Cluster) GenerateRegistryPullCreds(ctx context.Context, namespace, name // GenerateGitPullCreds generates a secret containing the git credentials. func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo types.GitServerInfo) *corev1.Secret { - message.Debugf("k8s.GenerateGitPullCreds(%s, %s, gitServerInfo)", namespace, name) - gitServerSecret := &corev1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: corev1.SchemeGroupVersion.String(), diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index ad4ff27736..65fba1431a 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -250,7 +250,11 @@ func (c *Cluster) debugPrintZarfState(state *types.ZarfState) { // this is a shallow copy, nested pointers WILL NOT be copied oldState := *state sanitized := c.sanitizeZarfState(&oldState) - message.Debugf("ZarfState - %s", message.JSONValue(sanitized)) + b, err := json.MarshalIndent(sanitized, "", " ") + if err != nil { + return + } + message.Debugf("ZarfState - %s", string(b)) } // SaveZarfState takes a given state and persists it to the Zarf/zarf-state secret. diff --git a/src/pkg/message/connect.go b/src/pkg/message/connect.go index 1c7e43cfe1..75f87fdd64 100644 --- a/src/pkg/message/connect.go +++ b/src/pkg/message/connect.go @@ -12,8 +12,6 @@ import ( // PrintConnectStringTable prints a table of connect strings. func PrintConnectStringTable(connectStrings types.ConnectStrings) { - Debugf("message.PrintConnectStringTable(%#v)", connectStrings) - if len(connectStrings) > 0 { connectData := [][]string{} // Loop over each connectStrings and convert to a string matrix diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go index 591165559e..0ead8b0e01 100644 --- a/src/pkg/message/message.go +++ b/src/pkg/message/message.go @@ -5,10 +5,8 @@ package message import ( - "encoding/json" "fmt" "io" - "net/http" "os" "strings" "time" @@ -17,7 +15,6 @@ import ( "github.com/defenseunicorns/zarf/src/config" "github.com/fatih/color" "github.com/pterm/pterm" - "github.com/sergi/go-diff/diffmatchpatch" ) // LogLevel is the level of logging to display. @@ -97,11 +94,6 @@ func SetLogLevel(lvl LogLevel) { } } -// GetLogLevel returns the current log level. -func GetLogLevel() LogLevel { - return logLevel -} - // DisableColor disables color in output func DisableColor() { pterm.DisableColor() @@ -129,14 +121,6 @@ func Debugf(format string, a ...any) { debugPrinter(2, message) } -// ErrorWebf prints an error message and returns a web response. -func ErrorWebf(err any, w http.ResponseWriter, format string, a ...any) { - debugPrinter(2, err) - message := fmt.Sprintf(format, a...) - Warn(message) - http.Error(w, message, http.StatusInternalServerError) -} - // Warn prints a warning message. func Warn(message string) { Warnf("%s", message) @@ -242,15 +226,6 @@ func HorizontalRule() { pterm.Println(RuleLine) } -// JSONValue prints any value as JSON. -func JSONValue(value any) string { - bytes, err := json.MarshalIndent(value, "", " ") - if err != nil { - debugPrinter(2, fmt.Sprintf("ERROR marshalling json: %s", err.Error())) - } - return string(bytes) -} - // Paragraph formats text into a paragraph matching the TermWidth func Paragraph(format string, a ...any) string { return Paragraphn(TermWidth, format, a...) @@ -269,17 +244,6 @@ func Paragraphn(n int, format string, a ...any) string { return strings.Join(formattedLines, "\n") } -// PrintDiff prints the differences between a and b with a as original and b as new -func PrintDiff(textA, textB string) { - dmp := diffmatchpatch.New() - - diffs := dmp.DiffMain(textA, textB, true) - - diffs = dmp.DiffCleanupSemantic(diffs) - - pterm.Println(dmp.DiffPrettyText(diffs)) -} - // Table prints a padded table containing the specified header and data func Table(header []string, data [][]string) { pterm.Println() diff --git a/src/pkg/utils/cosign.go b/src/pkg/utils/cosign.go index d4b8fe695d..fc66d92ba9 100644 --- a/src/pkg/utils/cosign.go +++ b/src/pkg/utils/cosign.go @@ -50,8 +50,6 @@ func Sget(ctx context.Context, image, key string, out io.Writer) error { // Remove the custom protocol header from the url image = strings.TrimPrefix(image, helpers.SGETURLPrefix) - message.Debugf("utils.Sget: image=%s, key=%s", image, key) - spinner := message.NewProgressSpinner("Loading signed file %s", image) defer spinner.Stop() diff --git a/src/pkg/utils/io.go b/src/pkg/utils/io.go index 2fd4cfc72b..16c1abf377 100755 --- a/src/pkg/utils/io.go +++ b/src/pkg/utils/io.go @@ -38,8 +38,6 @@ func MakeTempDir(basePath string) (string, error) { // GetFinalExecutablePath returns the absolute path to the current executable, following any symlinks along the way. func GetFinalExecutablePath() (string, error) { - message.Debug("utils.GetExecutablePath()") - binaryPath, err := os.Executable() if err != nil { return "", err diff --git a/src/pkg/utils/yaml.go b/src/pkg/utils/yaml.go index f2c73d5f77..bc49d46502 100644 --- a/src/pkg/utils/yaml.go +++ b/src/pkg/utils/yaml.go @@ -124,8 +124,6 @@ func AddRootHint(hints map[string]string, rootKey string, hintText string) map[s // ReadYaml reads a yaml file and unmarshals it into a given config. func ReadYaml(path string, destConfig any) error { - message.Debugf("Reading YAML at %s", path) - file, err := os.ReadFile(path) if err != nil { return err diff --git a/src/test/e2e/05_tarball_test.go b/src/test/e2e/05_tarball_test.go index 7293b378bc..e4ebfde06d 100644 --- a/src/test/e2e/05_tarball_test.go +++ b/src/test/e2e/05_tarball_test.go @@ -13,7 +13,6 @@ import ( "github.com/defenseunicorns/pkg/helpers/v2" "github.com/defenseunicorns/zarf/src/pkg/layout" - "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" @@ -98,10 +97,6 @@ func TestReproducibleTarballs(t *testing.T) { err = utils.ReadYaml(filepath.Join(unpack1, layout.ZarfYAML), &pkg1) require.NoError(t, err) - b, err := os.ReadFile(filepath.Join(unpack1, layout.Checksums)) - require.NoError(t, err) - checksums1 := string(b) - e2e.CleanFiles(unpack1, tb) stdOut, stdErr, err = e2e.Zarf("package", "create", createPath, "--confirm", "--output", tmp) @@ -114,11 +109,5 @@ func TestReproducibleTarballs(t *testing.T) { err = utils.ReadYaml(filepath.Join(unpack2, layout.ZarfYAML), &pkg2) require.NoError(t, err) - b, err = os.ReadFile(filepath.Join(unpack2, layout.Checksums)) - require.NoError(t, err) - checksums2 := string(b) - - message.PrintDiff(checksums1, checksums2) - require.Equal(t, pkg1.Metadata.AggregateChecksum, pkg2.Metadata.AggregateChecksum) } From f2c77ef21623999202c43592927a8096b0814606 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 10 Jul 2024 20:39:58 +0200 Subject: [PATCH 113/132] refactor: connect command list printing (#2703) Co-authored-by: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> --- src/cmd/connect.go | 135 +++++++++++----------- src/config/config.go | 4 - src/internal/packager/helm/post-render.go | 6 +- src/pkg/cluster/tunnel.go | 32 ++--- src/pkg/cluster/tunnel_test.go | 38 ++++++ 5 files changed, 123 insertions(+), 92 deletions(-) diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 7111abf813..89eb341b45 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -5,14 +5,14 @@ package cmd import ( - "context" "fmt" + "github.com/spf13/cobra" + "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" - "github.com/spf13/cobra" ) var ( @@ -22,83 +22,80 @@ var ( connectLocalPort int connectRemotePort int cliOnly bool +) - connectCmd = &cobra.Command{ - Use: "connect { REGISTRY | GIT | connect-name }", - Aliases: []string{"c"}, - Short: lang.CmdConnectShort, - Long: lang.CmdConnectLong, - RunE: func(cmd *cobra.Command, args []string) error { - var target string - if len(args) > 0 { - target = args[0] - } - spinner := message.NewProgressSpinner(lang.CmdConnectPreparingTunnel, target) - defer spinner.Stop() - c, err := cluster.NewCluster() - if err != nil { - return err - } +var connectCmd = &cobra.Command{ + Use: "connect { REGISTRY | GIT | connect-name }", + Aliases: []string{"c"}, + Short: lang.CmdConnectShort, + Long: lang.CmdConnectLong, + RunE: func(cmd *cobra.Command, args []string) error { + target := "" + if len(args) > 0 { + target = args[0] + } - ctx := cmd.Context() + spinner := message.NewProgressSpinner(lang.CmdConnectPreparingTunnel, target) + defer spinner.Stop() - var tunnel *cluster.Tunnel - if connectResourceName != "" { - zt := cluster.NewTunnelInfo(connectNamespace, connectResourceType, connectResourceName, "", connectLocalPort, connectRemotePort) - tunnel, err = c.ConnectTunnelInfo(ctx, zt) - } else { - tunnel, err = c.Connect(ctx, target) - } - if err != nil { - return fmt.Errorf("unable to connect to the service: %w", err) - } + c, err := cluster.NewCluster() + if err != nil { + return err + } - defer tunnel.Close() - url := tunnel.FullURL() + ctx := cmd.Context() - // Dump the tunnel URL to the console for other tools to use. - fmt.Print(url) + var tunnel *cluster.Tunnel + if connectResourceName == "" { + tunnel, err = c.Connect(ctx, target) + } else { + zt := cluster.NewTunnelInfo(connectNamespace, connectResourceType, connectResourceName, "", connectLocalPort, connectRemotePort) + tunnel, err = c.ConnectTunnelInfo(ctx, zt) + } + if err != nil { + return fmt.Errorf("unable to connect to the service: %w", err) + } + defer tunnel.Close() - if cliOnly { - spinner.Updatef(lang.CmdConnectEstablishedCLI, url) - } else { - spinner.Updatef(lang.CmdConnectEstablishedWeb, url) + // Dump the tunnel URL to the console for other tools to use. + fmt.Print(tunnel.FullURL()) - if err := exec.LaunchURL(url); err != nil { - message.Debug(err) - } + if cliOnly { + spinner.Updatef(lang.CmdConnectEstablishedCLI, tunnel.FullURL()) + } else { + spinner.Updatef(lang.CmdConnectEstablishedWeb, tunnel.FullURL()) + if err := exec.LaunchURL(tunnel.FullURL()); err != nil { + message.Debug(err) } + } - // Wait for the interrupt signal or an error. - select { - case <-ctx.Done(): - spinner.Successf(lang.CmdConnectTunnelClosed, url) - case err = <-tunnel.ErrChan(): - return fmt.Errorf("lost connection to the service: %w", err) - } - return nil - }, - } - - connectListCmd = &cobra.Command{ - Use: "list", - Aliases: []string{"l"}, - Short: lang.CmdConnectListShort, - RunE: func(cmd *cobra.Command, _ []string) error { - timeoutCtx, cancel := context.WithTimeout(cmd.Context(), cluster.DefaultTimeout) - defer cancel() - c, err := cluster.NewClusterWithWait(timeoutCtx) - if err != nil { - return err - } - err = c.PrintConnectTable(cmd.Context()) - if err != nil { - return err - } + select { + case <-ctx.Done(): + spinner.Successf(lang.CmdConnectTunnelClosed, tunnel.FullURL()) return nil - }, - } -) + case err = <-tunnel.ErrChan(): + return fmt.Errorf("lost connection to the service: %w", err) + } + }, +} + +var connectListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"l"}, + Short: lang.CmdConnectListShort, + RunE: func(cmd *cobra.Command, _ []string) error { + c, err := cluster.NewCluster() + if err != nil { + return err + } + connections, err := c.ListConnections(cmd.Context()) + if err != nil { + return err + } + message.PrintConnectStringTable(connections) + return nil + }, +} func init() { rootCmd.AddCommand(connectCmd) diff --git a/src/config/config.go b/src/config/config.go index d3b0c07a21..f9dfc580ab 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -21,10 +21,6 @@ const ( ZarfAgentHost = "agent-hook.zarf.svc" - ZarfConnectLabelName = "zarf.dev/connect-name" - ZarfConnectAnnotationDescription = "zarf.dev/connect-description" - ZarfConnectAnnotationURL = "zarf.dev/connect-url" - ZarfCleanupScriptsPath = "/opt/zarf" ZarfPackagePrefix = "zarf-package-" diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 43bc102a6b..b03aa6a93a 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -254,14 +254,14 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti if annotations == nil { annotations = map[string]string{} } - if key, keyExists := labels[config.ZarfConnectLabelName]; keyExists { + if key, keyExists := labels[cluster.ZarfConnectLabelName]; keyExists { // If there is a zarf-connect label message.Debugf("Match helm service %s for zarf connection %s", rawData.GetName(), key) // Add the connectString for processing later in the deployment r.connectStrings[key] = types.ConnectString{ - Description: annotations[config.ZarfConnectAnnotationDescription], - URL: annotations[config.ZarfConnectAnnotationURL], + Description: annotations[cluster.ZarfConnectAnnotationDescription], + URL: annotations[cluster.ZarfConnectAnnotationURL], } } } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index 10a287986d..3f8433ed9f 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -23,13 +23,16 @@ import ( "k8s.io/client-go/transport/spdy" "github.com/defenseunicorns/pkg/helpers/v2" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" ) // Zarf specific connect strings const ( + ZarfConnectLabelName = "zarf.dev/connect-name" + ZarfConnectAnnotationDescription = "zarf.dev/connect-description" + ZarfConnectAnnotationURL = "zarf.dev/connect-url" + ZarfRegistry = "REGISTRY" ZarfGit = "GIT" ZarfInjector = "INJECTOR" @@ -64,33 +67,30 @@ func NewTunnelInfo(namespace, resourceType, resourceName, urlSuffix string, loca } } -// PrintConnectTable will print a table of all Zarf connect matches found in the cluster. -func (c *Cluster) PrintConnectTable(ctx context.Context) error { +// ListConnections will return a list of all Zarf connect matches found in the cluster. +func (c *Cluster) ListConnections(ctx context.Context) (types.ConnectStrings, error) { selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ Operator: metav1.LabelSelectorOpExists, - Key: config.ZarfConnectLabelName, + Key: ZarfConnectLabelName, }}, }) if err != nil { - return err + return nil, err } serviceList, err := c.Clientset.CoreV1().Services("").List(ctx, metav1.ListOptions{LabelSelector: selector.String()}) if err != nil { - return err + return nil, err } - - connections := make(types.ConnectStrings) + connections := types.ConnectStrings{} for _, svc := range serviceList.Items { - name := svc.Labels[config.ZarfConnectLabelName] - // Add the connectString for processing later in the deployment. + name := svc.Labels[ZarfConnectLabelName] connections[name] = types.ConnectString{ - Description: svc.Annotations[config.ZarfConnectAnnotationDescription], - URL: svc.Annotations[config.ZarfConnectAnnotationURL], + Description: svc.Annotations[ZarfConnectAnnotationDescription], + URL: svc.Annotations[ZarfConnectAnnotationURL], } } - message.PrintConnectStringTable(connections) - return nil + return connections, nil } // Connect will establish a tunnel to the specified target. @@ -189,7 +189,7 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu selector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: map[string]string{ - config.ZarfConnectLabelName: name, + ZarfConnectLabelName: name, }, }) if err != nil { @@ -222,7 +222,7 @@ func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (Tu } // Add the url suffix too. - zt.urlSuffix = svc.Annotations[config.ZarfConnectAnnotationURL] + zt.urlSuffix = svc.Annotations[ZarfConnectAnnotationURL] message.Debugf("tunnel connection match: %s/%s on port %d", svc.Namespace, svc.Name, zt.remotePort) } else { diff --git a/src/pkg/cluster/tunnel_test.go b/src/pkg/cluster/tunnel_test.go index f9dfc7b5c8..fd1866da19 100644 --- a/src/pkg/cluster/tunnel_test.go +++ b/src/pkg/cluster/tunnel_test.go @@ -4,13 +4,51 @@ package cluster import ( + "context" "testing" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes/fake" + + "github.com/defenseunicorns/zarf/src/types" ) +func TestListConnections(t *testing.T) { + t.Parallel() + + c := &Cluster{ + Clientset: fake.NewSimpleClientset(), + } + svc := corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "connect", + Labels: map[string]string{ + ZarfConnectLabelName: "connect name", + }, + Annotations: map[string]string{ + ZarfConnectAnnotationDescription: "description", + ZarfConnectAnnotationURL: "url", + }, + }, + Spec: corev1.ServiceSpec{}, + } + _, err := c.Clientset.CoreV1().Services(svc.ObjectMeta.Namespace).Create(context.Background(), &svc, metav1.CreateOptions{}) + require.NoError(t, err) + + connections, err := c.ListConnections(context.Background()) + require.NoError(t, err) + expectedConnections := types.ConnectStrings{ + "connect name": types.ConnectString{ + Description: "description", + URL: "url", + }, + } + require.Equal(t, expectedConnections, connections) +} + func TestServiceInfoFromNodePortURL(t *testing.T) { t.Parallel() From c89e2dd65bd00353cd28e591f2fd45607fc558d0 Mon Sep 17 00:00:00 2001 From: Xander Grzywinski Date: Thu, 11 Jul 2024 05:11:26 -0700 Subject: [PATCH 114/132] docs: add contributing doc to root and add tsc (#2706) Signed-off-by: Xander Grzywinski --- .github/CONTRIBUTING.md => CONTRIBUTING.md | 28 ++++++++++++++++++- .../docs/contribute/contributor-guide.mdx | 2 +- 2 files changed, 28 insertions(+), 2 deletions(-) rename .github/CONTRIBUTING.md => CONTRIBUTING.md (88%) diff --git a/.github/CONTRIBUTING.md b/CONTRIBUTING.md similarity index 88% rename from .github/CONTRIBUTING.md rename to CONTRIBUTING.md index 4dda9e1668..700f161b7a 100644 --- a/.github/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ First off, thanks so much for wanting to help out! :tada: -This document describes the steps and requirements for contributing a bug fix or feature in a Pull Request to Zarf! If you have any questions about the process or the pull request you are working on feel free to reach out in the [Zarf Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03BP9Z3CMA). +This document describes the steps and requirements for contributing a bug fix or feature in a Pull Request to Zarf! If you have any questions about the process or the pull request you are working on feel free to reach out in the [Zarf Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03BP9Z3CMA). The doc also details a bit about the governance structure of the project. ## Developer Experience @@ -83,3 +83,29 @@ adr new -l "15:Amends:Amended by" Use store-bought butter for all waffle making # Get full help docs. There are all sorts of other helpful commands that help manage the decision log. adr help ``` + +## Governance + +### Technical Steering Committee +The Technical Steering Committee (the “TSC”) will be responsible for all technical oversight of the project. The TSC may elect a TSC Chair, who will preside over meetings of the TSC and will serve until their resignation or replacement by the TSC. Current members of the TSC include: + +#### Austin Abro +Affiliation: Defense Unicorns +GitHub: @AustinAbro321 + +#### Danny Gershman +Affiliation: Radius Method +GitHub: @dgershman + +#### Jeff McCoy (TSC Chair) +Affiliation: Defense Unicorns +GitHub: @jeff-mccoy + +#### Sarah Christoff +Affiliation: Defense Unicorns +GitHub: @schristoff-du + +#### Wayne Starr +Affiliation: Defense Unicorns +GitHub: @Racer159 + diff --git a/site/src/content/docs/contribute/contributor-guide.mdx b/site/src/content/docs/contribute/contributor-guide.mdx index 02277d13d4..70b1174c64 100644 --- a/site/src/content/docs/contribute/contributor-guide.mdx +++ b/site/src/content/docs/contribute/contributor-guide.mdx @@ -5,7 +5,7 @@ tableOfContents: false --- import StripH1 from "@components/StripH1.astro"; -import Contributing from "../../../../../.github/CONTRIBUTING.md"; +import Contributing from "../../../../../CONTRIBUTING.md"; From eca0c530a04c3ed155c03761eb45bd16ba717b79 Mon Sep 17 00:00:00 2001 From: Jason Washburn <35488541+jasonwashburn@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:36:52 -0500 Subject: [PATCH 115/132] fix: remove unpinned image warning in lint for cosign signatures (#2681) --- src/pkg/packager/lint/lint.go | 12 +++++++++++- src/pkg/packager/lint/lint_test.go | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go index ddcdea76db..27a823deff 100644 --- a/src/pkg/packager/lint/lint.go +++ b/src/pkg/packager/lint/lint.go @@ -64,7 +64,6 @@ func lintComponents(ctx context.Context, pkg types.ZarfPackage, createOpts types } chain, err := composer.NewImportChain(ctx, component, i, pkg.Metadata.Name, arch, createOpts.Flavor) - if err != nil { return nil, err } @@ -144,9 +143,20 @@ func isPinnedImage(image string) (bool, error) { } return false, err } + if isCosignSignature(transformedImage.Tag) || isCosignAttestation(transformedImage.Tag) { + return true, nil + } return (transformedImage.Digest != ""), err } +func isCosignSignature(image string) bool { + return strings.HasSuffix(image, ".sig") +} + +func isCosignAttestation(image string) bool { + return strings.HasSuffix(image, ".att") +} + func isPinnedRepo(repo string) bool { return (strings.Contains(repo, "@")) } diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go index 78d4c4102d..d20257b23a 100644 --- a/src/pkg/packager/lint/lint_test.go +++ b/src/pkg/packager/lint/lint_test.go @@ -217,10 +217,14 @@ func TestValidateComponent(t *testing.T) { t.Parallel() unpinnedImage := "registry.com:9001/whatever/image:1.0.0" badImage := "badimage:badimage@@sha256:3fbc632167424a6d997e74f5" + cosignSignature := "ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.sig" + cosignAttestation := "ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.att" component := types.ZarfComponent{Images: []string{ unpinnedImage, "busybox:latest@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79", badImage, + cosignSignature, + cosignAttestation, }} findings := checkForUnpinnedImages(component, 0) expected := []types.PackageFinding{ @@ -333,6 +337,16 @@ func TestValidateComponent(t *testing.T) { expected: true, err: nil, }, + { + input: "ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.sig", + expected: true, + err: nil, + }, + { + input: "ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.att", + expected: true, + err: nil, + }, } for _, tc := range tests { t.Run(tc.input, func(t *testing.T) { From a567687b84fc69ced50e4e674f79295114024aed Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:35:50 -0400 Subject: [PATCH 116/132] test: simplifying e2e test checks (#2721) --- .pre-commit-config.yaml | 2 +- src/test/common.go | 9 --------- src/test/e2e/20_zarf_init_test.go | 1 - src/test/e2e/21_connect_creds_test.go | 2 -- src/test/e2e/22_git_and_gitops_test.go | 3 --- src/test/e2e/23_data_injection_test.go | 1 - src/test/e2e/24_variables_test.go | 1 - src/test/e2e/25_helm_test.go | 1 - src/test/e2e/26_simple_packages_test.go | 3 --- src/test/e2e/27_deploy_regression_test.go | 2 -- src/test/e2e/28_wait_test.go | 1 - src/test/e2e/29_config_file_test.go | 1 - src/test/e2e/30_component_action_cluster_test.go | 2 -- src/test/e2e/31_checksum_and_signature_test.go | 1 - src/test/e2e/32_component_webhooks_test.go | 1 - src/test/e2e/34_custom_init_package_test.go | 2 +- src/test/e2e/35_custom_retries_test.go | 1 - src/test/e2e/50_oci_publish_deploy_test.go | 1 - src/test/e2e/51_oci_compose_test.go | 1 - src/test/e2e/99_appliance_remove_test.go | 2 -- src/test/e2e/99_yolo_test.go | 3 --- src/test/e2e/main_test.go | 1 - src/test/nightly/ecr_publish_test.go | 2 -- 23 files changed, 2 insertions(+), 42 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b77cc86c1c..ad9ea6d447 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ repos: args: - "--allow-missing-credentials" - id: detect-private-key - exclude: "src/test/e2e/30_config_file_test.go" + exclude: "src/test/e2e/29_config_file_test.go" - id: end-of-file-fixer exclude: site/src/content/docs/commands/.* - id: fix-byte-order-marker diff --git a/src/test/common.go b/src/test/common.go index 4eb9844045..70492f014c 100644 --- a/src/test/common.go +++ b/src/test/common.go @@ -27,7 +27,6 @@ type ZarfE2ETest struct { Arch string ApplianceMode bool ApplianceModeKeep bool - RunClusterTests bool } var logRegex = regexp.MustCompile(`Saving log file to (?P.*?\.log)`) @@ -53,14 +52,6 @@ func GetCLIName() string { return binaryName } -// SetupWithCluster performs actions for each test that requires a K8s cluster. -func (e2e *ZarfE2ETest) SetupWithCluster(t *testing.T) { - if !e2e.RunClusterTests { - t.Skip("") - } - _ = exec.CmdWithPrint("sh", "-c", fmt.Sprintf("%s tools kubectl describe nodes | grep -A 99 Non-terminated", e2e.ZarfBinPath)) -} - // Zarf executes a Zarf command. func (e2e *ZarfE2ETest) Zarf(args ...string) (string, string, error) { if !slices.Contains(args, "--tmpdir") && !slices.Contains(args, "tools") { diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go index 92fe26bc98..f54fc54f85 100644 --- a/src/test/e2e/20_zarf_init_test.go +++ b/src/test/e2e/20_zarf_init_test.go @@ -18,7 +18,6 @@ import ( func TestZarfInit(t *testing.T) { t.Log("E2E: Zarf init") - e2e.SetupWithCluster(t) initComponents := "git-server" if e2e.ApplianceMode { diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index 5c68277ff7..3a929ac596 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -23,7 +23,6 @@ type RegistryResponse struct { func TestConnectAndCreds(t *testing.T) { t.Log("E2E: Connect") - e2e.SetupWithCluster(t) prevAgentSecretData, _, err := e2e.Kubectl("get", "secret", "agent-hook-tls", "-n", "zarf", "-o", "jsonpath={.data}") require.NoError(t, err) @@ -44,7 +43,6 @@ func TestConnectAndCreds(t *testing.T) { func TestMetrics(t *testing.T) { t.Log("E2E: Emits metrics") - e2e.SetupWithCluster(t) c, err := cluster.NewCluster() require.NoError(t, err) diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index 8b53bacfaf..956ded5ed9 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -21,7 +21,6 @@ import ( func TestGit(t *testing.T) { t.Log("E2E: Git") - e2e.SetupWithCluster(t) buildPath := filepath.Join("src", "test", "packages", "22-git-data") stdOut, stdErr, err := e2e.Zarf("package", "create", buildPath, "-o=build", "--confirm") @@ -48,14 +47,12 @@ func TestGit(t *testing.T) { func TestGitOpsFlux(t *testing.T) { t.Log("E2E: GitOps / Flux") - e2e.SetupWithCluster(t) waitFluxPodInfoDeployment(t) } func TestGitOpsArgoCD(t *testing.T) { t.Log("E2E: ArgoCD / Flux") - e2e.SetupWithCluster(t) waitArgoDeployment(t) } diff --git a/src/test/e2e/23_data_injection_test.go b/src/test/e2e/23_data_injection_test.go index efbce9bc13..9e4bbef7c3 100644 --- a/src/test/e2e/23_data_injection_test.go +++ b/src/test/e2e/23_data_injection_test.go @@ -19,7 +19,6 @@ import ( func TestDataInjection(t *testing.T) { t.Log("E2E: Data injection") - e2e.SetupWithCluster(t) ctx := context.Background() diff --git a/src/test/e2e/24_variables_test.go b/src/test/e2e/24_variables_test.go index c0f546ad83..765c8902bf 100644 --- a/src/test/e2e/24_variables_test.go +++ b/src/test/e2e/24_variables_test.go @@ -15,7 +15,6 @@ import ( func TestVariables(t *testing.T) { t.Log("E2E: Package variables") - e2e.SetupWithCluster(t) evilSrc := filepath.Join("src", "test", "packages", "24-evil-variables") evilPath := fmt.Sprintf("zarf-package-evil-variables-%s.tar.zst", e2e.Arch) diff --git a/src/test/e2e/25_helm_test.go b/src/test/e2e/25_helm_test.go index 7c643288c0..280ba3d7ec 100644 --- a/src/test/e2e/25_helm_test.go +++ b/src/test/e2e/25_helm_test.go @@ -17,7 +17,6 @@ var helmChartsPkg string func TestHelm(t *testing.T) { t.Log("E2E: Helm chart") - e2e.SetupWithCluster(t) helmChartsPkg = filepath.Join("build", fmt.Sprintf("zarf-package-helm-charts-%s-0.0.1.tar.zst", e2e.Arch)) diff --git a/src/test/e2e/26_simple_packages_test.go b/src/test/e2e/26_simple_packages_test.go index 347aad89fd..97a67423e3 100644 --- a/src/test/e2e/26_simple_packages_test.go +++ b/src/test/e2e/26_simple_packages_test.go @@ -17,7 +17,6 @@ import ( func TestDosGames(t *testing.T) { t.Log("E2E: Dos games") - e2e.SetupWithCluster(t) path := filepath.Join("build", fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch)) @@ -53,7 +52,6 @@ func TestDosGames(t *testing.T) { func TestManifests(t *testing.T) { t.Log("E2E: Local, Remote, and Kustomize Manifests") - e2e.SetupWithCluster(t) path := filepath.Join("build", fmt.Sprintf("zarf-package-manifests-%s-0.0.1.tar.zst", e2e.Arch)) @@ -68,7 +66,6 @@ func TestManifests(t *testing.T) { func TestAgentIgnore(t *testing.T) { t.Log("E2E: Test Manifests that are Agent Ignored") - e2e.SetupWithCluster(t) testCreate := filepath.Join("src", "test", "packages", "26-agent-ignore") testDeploy := filepath.Join("build", fmt.Sprintf("zarf-package-agent-ignore-namespace-%s.tar.zst", e2e.Arch)) diff --git a/src/test/e2e/27_deploy_regression_test.go b/src/test/e2e/27_deploy_regression_test.go index 0f70365a63..fec8d55433 100644 --- a/src/test/e2e/27_deploy_regression_test.go +++ b/src/test/e2e/27_deploy_regression_test.go @@ -15,7 +15,6 @@ import ( func TestGHCRDeploy(t *testing.T) { t.Log("E2E: GHCR OCI deploy") - e2e.SetupWithCluster(t) var sha string // shas for package published 2023-08-08T22:13:51Z @@ -36,7 +35,6 @@ func TestGHCRDeploy(t *testing.T) { func TestCosignDeploy(t *testing.T) { t.Log("E2E: Cosign deploy") - e2e.SetupWithCluster(t) // Test with command from https://docs.zarf.dev/getting-started/install/ command := fmt.Sprintf("%s package deploy sget://defenseunicorns/zarf-hello-world:$(uname -m) --confirm", e2e.ZarfBinPath) diff --git a/src/test/e2e/28_wait_test.go b/src/test/e2e/28_wait_test.go index 16d2ddff4f..0de68f243e 100644 --- a/src/test/e2e/28_wait_test.go +++ b/src/test/e2e/28_wait_test.go @@ -27,7 +27,6 @@ func zarfCommandWStruct(e2e test.ZarfE2ETest, path string) (result zarfCommandRe func TestNoWait(t *testing.T) { t.Log("E2E: Helm Wait") - e2e.SetupWithCluster(t) stdOut, stdErr, err := e2e.Zarf("package", "create", "src/test/packages/28-helm-no-wait", "-o=build", "--confirm") require.NoError(t, err, stdOut, stdErr) diff --git a/src/test/e2e/29_config_file_test.go b/src/test/e2e/29_config_file_test.go index 5095efa91f..40c66b4204 100644 --- a/src/test/e2e/29_config_file_test.go +++ b/src/test/e2e/29_config_file_test.go @@ -15,7 +15,6 @@ import ( func TestConfigFile(t *testing.T) { t.Log("E2E: Config file") - e2e.SetupWithCluster(t) var ( path = fmt.Sprintf("zarf-package-config-file-%s.tar.zst", e2e.Arch) diff --git a/src/test/e2e/30_component_action_cluster_test.go b/src/test/e2e/30_component_action_cluster_test.go index 47efd3a6a4..5d6658152f 100644 --- a/src/test/e2e/30_component_action_cluster_test.go +++ b/src/test/e2e/30_component_action_cluster_test.go @@ -14,7 +14,6 @@ import ( func TestComponentActionRemove(t *testing.T) { t.Log("E2E: Component action remove") - e2e.SetupWithCluster(t) packagePath := filepath.Join("build", fmt.Sprintf("zarf-package-component-actions-%s.tar.zst", e2e.Arch)) @@ -31,7 +30,6 @@ func TestComponentActionRemove(t *testing.T) { func TestComponentActionEdgeCases(t *testing.T) { t.Log("E2E: Component action edge cases") - e2e.SetupWithCluster(t) sourcePath := filepath.Join("src", "test", "packages", "31-component-actions-edgecases") packagePath := fmt.Sprintf("zarf-package-component-actions-edgecases-%s.tar.zst", e2e.Arch) diff --git a/src/test/e2e/31_checksum_and_signature_test.go b/src/test/e2e/31_checksum_and_signature_test.go index 957deaeaa8..2b3856779f 100644 --- a/src/test/e2e/31_checksum_and_signature_test.go +++ b/src/test/e2e/31_checksum_and_signature_test.go @@ -13,7 +13,6 @@ import ( func TestChecksumAndSignature(t *testing.T) { t.Log("E2E: Checksum and Signature") - e2e.SetupWithCluster(t) testPackageDirPath := "examples/dos-games" pkgName := fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) diff --git a/src/test/e2e/32_component_webhooks_test.go b/src/test/e2e/32_component_webhooks_test.go index cbb8321b08..d409e62521 100644 --- a/src/test/e2e/32_component_webhooks_test.go +++ b/src/test/e2e/32_component_webhooks_test.go @@ -13,7 +13,6 @@ import ( func TestComponentWebhooks(t *testing.T) { t.Log("E2E: Component Webhooks") - e2e.SetupWithCluster(t) // Deploy example Pepr webhook. webhookPath := fmt.Sprintf("build/zarf-package-component-webhooks-%s-0.0.1.tar.zst", e2e.Arch) diff --git a/src/test/e2e/34_custom_init_package_test.go b/src/test/e2e/34_custom_init_package_test.go index aae69e30e9..6229255aca 100644 --- a/src/test/e2e/34_custom_init_package_test.go +++ b/src/test/e2e/34_custom_init_package_test.go @@ -14,7 +14,7 @@ import ( func TestCustomInit(t *testing.T) { t.Log("E2E: Custom Init Package") - e2e.SetupWithCluster(t) + buildPath := filepath.Join("src", "test", "packages", "35-custom-init-package") pkgName := fmt.Sprintf("zarf-init-%s-%s.tar.zst", e2e.Arch, e2e.GetZarfVersion(t)) privateKeyFlag := "--signing-key=src/test/packages/zarf-test.prv-key" diff --git a/src/test/e2e/35_custom_retries_test.go b/src/test/e2e/35_custom_retries_test.go index cbe5428a25..92d7e2fffe 100644 --- a/src/test/e2e/35_custom_retries_test.go +++ b/src/test/e2e/35_custom_retries_test.go @@ -15,7 +15,6 @@ import ( func TestRetries(t *testing.T) { t.Log("E2E: Custom Retries") - e2e.SetupWithCluster(t) tmpDir := t.TempDir() diff --git a/src/test/e2e/50_oci_publish_deploy_test.go b/src/test/e2e/50_oci_publish_deploy_test.go index 96f0d6c7f1..f3d5d89428 100644 --- a/src/test/e2e/50_oci_publish_deploy_test.go +++ b/src/test/e2e/50_oci_publish_deploy_test.go @@ -164,7 +164,6 @@ func (suite *PublishDeploySuiteTestSuite) Test_3_Copy() { } func TestPublishDeploySuite(t *testing.T) { - e2e.SetupWithCluster(t) suite.Run(t, new(PublishDeploySuiteTestSuite)) } diff --git a/src/test/e2e/51_oci_compose_test.go b/src/test/e2e/51_oci_compose_test.go index c59d7477c1..124dbb953a 100644 --- a/src/test/e2e/51_oci_compose_test.go +++ b/src/test/e2e/51_oci_compose_test.go @@ -284,7 +284,6 @@ func (suite *SkeletonSuite) verifyComponentPaths(unpackedPath string, components } func TestSkeletonSuite(t *testing.T) { - e2e.SetupWithCluster(t) suite.Run(t, new(SkeletonSuite)) } diff --git a/src/test/e2e/99_appliance_remove_test.go b/src/test/e2e/99_appliance_remove_test.go index 1e34af99e0..321f353228 100644 --- a/src/test/e2e/99_appliance_remove_test.go +++ b/src/test/e2e/99_appliance_remove_test.go @@ -19,8 +19,6 @@ func TestApplianceRemove(t *testing.T) { return } - e2e.SetupWithCluster(t) - initPackageVersion := e2e.GetZarfVersion(t) path := fmt.Sprintf("build/zarf-init-%s-%s.tar.zst", e2e.Arch, initPackageVersion) diff --git a/src/test/e2e/99_yolo_test.go b/src/test/e2e/99_yolo_test.go index bce9ab1450..01671b8600 100644 --- a/src/test/e2e/99_yolo_test.go +++ b/src/test/e2e/99_yolo_test.go @@ -22,8 +22,6 @@ func TestYOLOMode(t *testing.T) { return } - e2e.SetupWithCluster(t) - // Destroy the cluster to test Zarf cleaning up after itself stdOut, stdErr, err := e2e.Zarf("destroy", "--confirm", "--remove-components") require.NoError(t, err, stdOut, stdErr) @@ -54,7 +52,6 @@ func TestDevDeploy(t *testing.T) { if e2e.ApplianceMode { return } - e2e.SetupWithCluster(t) stdOut, stdErr, err := e2e.Zarf("dev", "deploy", "examples/dos-games") require.NoError(t, err, stdOut, stdErr) diff --git a/src/test/e2e/main_test.go b/src/test/e2e/main_test.go index 9670335641..6c9c283a55 100644 --- a/src/test/e2e/main_test.go +++ b/src/test/e2e/main_test.go @@ -38,7 +38,6 @@ func TestMain(m *testing.M) { e2e.ZarfBinPath = filepath.Join("build", test.GetCLIName()) e2e.ApplianceMode = os.Getenv(applianceModeEnvVar) == "true" e2e.ApplianceModeKeep = os.Getenv(applianceModeKeepEnvVar) == "true" - e2e.RunClusterTests = true message.SetLogLevel(message.TraceLevel) diff --git a/src/test/nightly/ecr_publish_test.go b/src/test/nightly/ecr_publish_test.go index a1d07e73e1..8d696660c8 100644 --- a/src/test/nightly/ecr_publish_test.go +++ b/src/test/nightly/ecr_publish_test.go @@ -39,8 +39,6 @@ func TestECRPublishing(t *testing.T) { // Set up the e2e configs e2e.Arch = config.GetArch() e2e.ZarfBinPath = path.Join("build", test.GetCLIName()) - e2e.ApplianceMode = true - e2e.RunClusterTests = false // Set up variables for common names/locations testPackageName := "helm-charts" From 891e96ee754dd16dea7c598fc8f3c688c9172336 Mon Sep 17 00:00:00 2001 From: David E Worth Date: Sat, 13 Jul 2024 16:11:50 -0600 Subject: [PATCH 117/132] fix: fix link to CONTRIBUTING.md in PR template (#2726) --- .github/pull_request_template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cee28d19f1..370d4faa9a 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,4 +11,4 @@ Relates to # ## Checklist before merging - [ ] Test, docs, adr added or updated as needed -- [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/.github/CONTRIBUTING.md#developer-workflow) followed +- [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow) followed From ff50f77bd652a3034fb82e4ebb1da3b739793562 Mon Sep 17 00:00:00 2001 From: Matias Insaurralde Date: Wed, 17 Jul 2024 12:00:02 -0300 Subject: [PATCH 118/132] refactor: compile local cluster service format regexp just once (#2727) --- src/pkg/packager/deploy.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 8c0eb6b7a4..9044772169 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -38,6 +38,11 @@ import ( "github.com/defenseunicorns/zarf/src/types" ) +var ( + // localClusterServiceRegex is used to match the local cluster service format: + localClusterServiceRegex = regexp.MustCompile(`^(?P[^\.]+)\.(?P[^\.]+)\.svc\.cluster\.local$`) +) + func (p *Packager) resetRegistryHPA(ctx context.Context) { if p.isConnectedToCluster() && p.hpaModified { if err := p.cluster.EnableRegHPAScaleDown(ctx); err != nil { @@ -742,11 +747,7 @@ func serviceInfoFromServiceURL(serviceURL string) (string, string, int, error) { } // Match hostname against local cluster service format. - pattern, err := regexp.Compile(`^(?P[^\.]+)\.(?P[^\.]+)\.svc\.cluster\.local$`) - if err != nil { - return "", "", 0, err - } - get, err := helpers.MatchRegex(pattern, parsedURL.Hostname()) + get, err := helpers.MatchRegex(localClusterServiceRegex, parsedURL.Hostname()) // If incomplete match, return an error. if err != nil { From 37aa79da4c93023ff593da39a603ebe5352de6f2 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Wed, 17 Jul 2024 15:54:39 -0400 Subject: [PATCH 119/132] chore: update s3 injector (#2730) --- .github/workflows/build-rust-injector.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-rust-injector.yml b/.github/workflows/build-rust-injector.yml index 270a091fcf..77d939f6f7 100644 --- a/.github/workflows/build-rust-injector.yml +++ b/.github/workflows/build-rust-injector.yml @@ -9,9 +9,6 @@ on: versionTag: description: "Version tag" required: true - branchName: - description: "Branch to build the injector from" - required: true jobs: build-injector: @@ -19,8 +16,6 @@ jobs: steps: - name: "Checkout Repo" uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - ref: ${{ github.event.inputs.branchName }} - name: Install tools uses: ./.github/actions/install-tools @@ -37,13 +32,14 @@ jobs: shasum zarf-injector-amd64 >> checksums.txt shasum zarf-injector-arm64 >> checksums.txt - - name: Set AWS Credentials + - name: Auth with AWS uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: - aws-access-key-id: ${{ secrets.AWS_GOV_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_GOV_SECRET_ACCESS_KEY }} - aws-region: us-gov-west-1 + role-to-assume: ${{ secrets.AWS_WRITE_ROLE }} + role-session-name: ${{ github.job || github.event.client_payload.pull_request.head.sha || github.sha }} + aws-region: us-east-2 + role-duration-seconds: 3600 - name: Sync Artifacts to S3 run: | - aws s3 sync src/injector/dist/ s3://zarf-public/injector/${{ github.event.inputs.versionTag }}/ + aws s3 sync src/injector/dist/ s3://zarf-init/injector/${{ github.event.inputs.versionTag }}/ From 6d7e90ad42c3858f69557b20761a8c9bf7fdc65d Mon Sep 17 00:00:00 2001 From: Xander Grzywinski Date: Thu, 18 Jul 2024 11:40:56 -0400 Subject: [PATCH 120/132] docs: fix codeowners file Signed-off-by: Xander Grzywinski --- CODEOWNERS | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index c713375a0d..976d425740 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,5 +1,5 @@ -* @defenseunicorns/zarf @dgershman +* @zarf-dev/maintainers @zarf-dev/reviewers -/CODEOWNERS @jeff-mccoy @austenbryan -/cosign.pub @jeff-mccoy @austenbryan -/LICENSE @jeff-mccoy @austenbryan +/CODEOWNERS @zarf-dev/tsc +/cosign.pub @zarf-dev/tsc +/LICENSE @zarf-dev/tsc From 62253cecc6b94cd2d42d79caacf57061dad8548a Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Wed, 17 Jul 2024 19:20:44 +0200 Subject: [PATCH 121/132] Rename org name in image references Signed-off-by: Philip Laine --- .github/workflows/publish-application-packages.yml | 8 ++++---- .github/workflows/release.yml | 10 +++++----- .github/workflows/test-upgrade.yml | 2 +- Makefile | 2 +- src/config/config.go | 2 +- src/pkg/zoci/utils.go | 2 +- src/test/e2e/08_create_differential_test.go | 2 +- src/test/e2e/11_oci_pull_inspect_test.go | 4 ++-- src/test/e2e/13_find_images_test.go | 2 +- src/test/e2e/14_create_sha_index_test.go | 2 +- src/test/e2e/21_connect_creds_test.go | 2 +- src/test/packages/00-remote-pull-fail/zarf.yaml | 2 +- src/test/packages/08-differential-package/zarf.yaml | 2 +- src/test/packages/14-index-sha/image-index/zarf.yaml | 2 +- zarf-config.toml | 2 +- 15 files changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/publish-application-packages.yml b/.github/workflows/publish-application-packages.yml index c30c7105fa..59be8e3ff3 100644 --- a/.github/workflows/publish-application-packages.yml +++ b/.github/workflows/publish-application-packages.yml @@ -23,7 +23,7 @@ jobs: ref: ${{ github.event.inputs.branchName }} - name: Install The Latest Release Version of Zarf - uses: defenseunicorns/setup-zarf@f95763914e20e493bb5d45d63e30e17138f981d6 # v1.0.0 + uses: defenseunicorns/setup-zarf@10e539efed02f75ec39eb8823e22a5c795f492ae #v1.0.1 - name: "Login to GHCR" uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0 @@ -39,11 +39,11 @@ jobs: zarf package create -o build -a arm64 examples/dos-games --signing-key=awskms:///${{ secrets.COSIGN_AWS_KMS_KEY }} --confirm # Publish a the signed dos-games package - zarf package publish ./build/zarf-package-dos-games-amd64-1.0.0.tar.zst oci://ghcr.io/defenseunicorns/packages --key=https://zarf.dev/cosign.pub - zarf package publish ./build/zarf-package-dos-games-arm64-1.0.0.tar.zst oci://ghcr.io/defenseunicorns/packages --key=https://zarf.dev/cosign.pub + zarf package publish ./build/zarf-package-dos-games-amd64-1.0.0.tar.zst oci://ghcr.io/zarf-dev/packages --key=https://zarf.dev/cosign.pub + zarf package publish ./build/zarf-package-dos-games-arm64-1.0.0.tar.zst oci://ghcr.io/zarf-dev/packages --key=https://zarf.dev/cosign.pub # Publish a skeleton of the dos-games package - zarf package publish examples/dos-games oci://ghcr.io/defenseunicorns/packages + zarf package publish examples/dos-games oci://ghcr.io/zarf-dev/packages env: AWS_REGION: ${{ secrets.COSIGN_AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.COSIGN_AWS_KEY_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f23f8c9c3..f8e6e36604 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,13 +42,13 @@ jobs: run: | cp build/zarf build/zarf-linux-amd64 cp build/zarf-arm build/zarf-linux-arm64 - docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/defenseunicorns/zarf/agent:$GITHUB_REF_NAME . + docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/zarf-dev/zarf/agent:$GITHUB_REF_NAME . rm build/zarf-linux-amd64 rm build/zarf-linux-arm64 - echo ZARF_AGENT_IMAGE_DIGEST=$(docker buildx imagetools inspect ghcr.io/defenseunicorns/zarf/agent:$GITHUB_REF_NAME --format '{{ json . }}' | jq -r .manifest.digest) >> $GITHUB_ENV + echo ZARF_AGENT_IMAGE_DIGEST=$(docker buildx imagetools inspect ghcr.io/zarf-dev/zarf/agent:$GITHUB_REF_NAME --format '{{ json . }}' | jq -r .manifest.digest) >> $GITHUB_ENV - name: "Zarf Agent: Sign the Image" - run: cosign sign --key awskms:///${{ secrets.COSIGN_AWS_KMS_KEY }} -a release-engineer=https://github.com/${{ github.actor }} -a version=$GITHUB_REF_NAME ghcr.io/defenseunicorns/zarf/agent@$ZARF_AGENT_IMAGE_DIGEST -y + run: cosign sign --key awskms:///${{ secrets.COSIGN_AWS_KMS_KEY }} -a release-engineer=https://github.com/${{ github.actor }} -a version=$GITHUB_REF_NAME ghcr.io/zarf-dev/zarf/agent@$ZARF_AGENT_IMAGE_DIGEST -y env: COSIGN_EXPERIMENTAL: 1 AWS_REGION: ${{ secrets.COSIGN_AWS_REGION }} @@ -63,8 +63,8 @@ jobs: - name: Publish Init Package as OCI and Skeleton run: | - make publish-init-package ARCH=amd64 REPOSITORY_URL=ghcr.io/defenseunicorns/packages - make publish-init-package ARCH=arm64 REPOSITORY_URL=ghcr.io/defenseunicorns/packages + make publish-init-package ARCH=amd64 REPOSITORY_URL=ghcr.io/zerf-dev/packages + make publish-init-package ARCH=arm64 REPOSITORY_URL=ghcr.io/zarf-dev/packages # Create a CVE report based on this build - name: Create release time CVE report diff --git a/.github/workflows/test-upgrade.yml b/.github/workflows/test-upgrade.yml index 4bbcf170b5..552d79e103 100644 --- a/.github/workflows/test-upgrade.yml +++ b/.github/workflows/test-upgrade.yml @@ -73,7 +73,7 @@ jobs: chmod +x build/zarf - name: Install release version of Zarf - uses: defenseunicorns/setup-zarf@f95763914e20e493bb5d45d63e30e17138f981d6 # v1.0.0 + uses: defenseunicorns/setup-zarf@10e539efed02f75ec39eb8823e22a5c795f492ae #v1.0.1 with: download-init-package: true diff --git a/Makefile b/Makefile index 9a4fa4e58e..0f2deebcaf 100644 --- a/Makefile +++ b/Makefile @@ -121,7 +121,7 @@ build-local-agent-image: ## Build the Zarf agent image to be used in a locally b @ if [ "$(ARCH)" = "amd64" ]; then cp build/zarf build/zarf-linux-amd64; fi @ if [ "$(ARCH)" = "arm64" ] && [ ! -s ./build/zarf-arm ]; then $(MAKE) build-cli-linux-arm; fi @ if [ "$(ARCH)" = "arm64" ]; then cp build/zarf-arm build/zarf-linux-arm64; fi - docker buildx build --load --platform linux/$(ARCH) --tag ghcr.io/defenseunicorns/zarf/agent:local . + docker buildx build --load --platform linux/$(ARCH) --tag ghcr.io/zarf-dev/zarf/agent:local . @ if [ "$(ARCH)" = "amd64" ]; then rm build/zarf-linux-amd64; fi @ if [ "$(ARCH)" = "arm64" ]; then rm build/zarf-linux-arm64; fi diff --git a/src/config/config.go b/src/config/config.go index f9dfc580ab..203767f6d8 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -17,7 +17,7 @@ import ( // Zarf Global Configuration Constants. const ( - GithubProject = "defenseunicorns/zarf" + GithubProject = "zarf-dev/zarf" ZarfAgentHost = "agent-hook.zarf.svc" diff --git a/src/pkg/zoci/utils.go b/src/pkg/zoci/utils.go index e63910a738..bcd5da45d4 100644 --- a/src/pkg/zoci/utils.go +++ b/src/pkg/zoci/utils.go @@ -43,5 +43,5 @@ func ReferenceFromMetadata(registryLocation string, metadata *types.ZarfMetadata // GetInitPackageURL returns the URL for the init package for the given version. func GetInitPackageURL(version string) string { - return fmt.Sprintf("ghcr.io/defenseunicorns/packages/init:%s", version) + return fmt.Sprintf("ghcr.io/zarf-dev/packages/init:%s", version) } diff --git a/src/test/e2e/08_create_differential_test.go b/src/test/e2e/08_create_differential_test.go index 042ed2e185..ff0a1a698c 100644 --- a/src/test/e2e/08_create_differential_test.go +++ b/src/test/e2e/08_create_differential_test.go @@ -73,7 +73,7 @@ func TestCreateDifferential(t *testing.T) { /* Validate we have ONLY the images we expect to have */ expectedImages := []string{ "ghcr.io/stefanprodan/podinfo:latest", - "ghcr.io/defenseunicorns/zarf/agent:v0.26.0", + "ghcr.io/zarf-dev/zarf/agent:v0.26.0", } require.Len(t, actualImages, 2, "zarf.yaml from the differential package does not contain the correct number of images") for _, expectedImage := range expectedImages { diff --git a/src/test/e2e/11_oci_pull_inspect_test.go b/src/test/e2e/11_oci_pull_inspect_test.go index 47d31232fb..98e5e4ff52 100644 --- a/src/test/e2e/11_oci_pull_inspect_test.go +++ b/src/test/e2e/11_oci_pull_inspect_test.go @@ -42,7 +42,7 @@ func (suite *PullInspectTestSuite) Test_0_Pull() { out := fmt.Sprintf("zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) // Build the fully qualified reference. - ref := fmt.Sprintf("oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0-%s", e2e.Arch) + ref := fmt.Sprintf("oci://ghcr.io/zarf-dev/packages/dos-games:1.0.0-%s", e2e.Arch) // Pull the package via OCI. stdOut, stdErr, err := e2e.Zarf("package", "pull", ref) @@ -74,7 +74,7 @@ func (suite *PullInspectTestSuite) Test_1_Remote_Inspect() { // Test inspect on a public package. // NOTE: This also makes sure that Zarf does not attempt auth when inspecting a public package. - ref := fmt.Sprintf("oci://ghcr.io/defenseunicorns/packages/dos-games:1.0.0-%s", e2e.Arch) + ref := fmt.Sprintf("oci://ghcr.io/zarf-dev/packages/dos-games:1.0.0-%s", e2e.Arch) _, stdErr, err = e2e.Zarf("package", "inspect", ref) suite.NoError(err, stdErr) } diff --git a/src/test/e2e/13_find_images_test.go b/src/test/e2e/13_find_images_test.go index fbb0185320..27e7bbe715 100644 --- a/src/test/e2e/13_find_images_test.go +++ b/src/test/e2e/13_find_images_test.go @@ -53,7 +53,7 @@ func TestFindImages(t *testing.T) { stdOut, _, err := e2e.Zarf("prepare", "find-images", ".", "--registry-url", registry, "--create-set", fmt.Sprintf("agent_image_tag=%s", agentTag)) require.NoError(t, err) - internalRegistryImage := fmt.Sprintf("%s/%s:%s", registry, "defenseunicorns/zarf/agent", agentTag) + internalRegistryImage := fmt.Sprintf("%s/%s:%s", registry, "zarf-dev/zarf/agent", agentTag) require.Contains(t, stdOut, internalRegistryImage, "registry image should be found with registry url") require.Contains(t, stdOut, "busybox:latest", "Busybox image should be found as long as helm chart doesn't error") diff --git a/src/test/e2e/14_create_sha_index_test.go b/src/test/e2e/14_create_sha_index_test.go index 9dee622c85..59993c5874 100644 --- a/src/test/e2e/14_create_sha_index_test.go +++ b/src/test/e2e/14_create_sha_index_test.go @@ -21,7 +21,7 @@ func TestCreateIndexShaErrors(t *testing.T) { { name: "Image Index", packagePath: "src/test/packages/14-index-sha/image-index", - expectedImageInStderr: "ghcr.io/defenseunicorns/zarf/agent:v0.32.6@sha256:b3fabdc7d4ecd0f396016ef78da19002c39e3ace352ea0ae4baa2ce9d5958376", + expectedImageInStderr: "ghcr.io/zarf-dev/zarf/agent:v0.32.6@sha256:b3fabdc7d4ecd0f396016ef78da19002c39e3ace352ea0ae4baa2ce9d5958376", }, { name: "Manifest List", diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index 3a929ac596..8a2a1f65ba 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -88,7 +88,7 @@ func connectToZarfServices(ctx context.Context, t *testing.T) { // We assert greater than or equal to since the base init has 8 images // HOWEVER during an upgrade we could have mismatched versions/names resulting in more images require.GreaterOrEqual(t, len(registryList), 3) - require.Contains(t, stdOut, "defenseunicorns/zarf/agent") + require.Contains(t, stdOut, "zarf-dev/zarf/agent") require.Contains(t, stdOut, "gitea/gitea") require.Contains(t, stdOut, "library/registry") diff --git a/src/test/packages/00-remote-pull-fail/zarf.yaml b/src/test/packages/00-remote-pull-fail/zarf.yaml index f44d99d4b1..b2212b15d6 100644 --- a/src/test/packages/00-remote-pull-fail/zarf.yaml +++ b/src/test/packages/00-remote-pull-fail/zarf.yaml @@ -5,4 +5,4 @@ components: - name: doesnotexist-docker required: true images: - - ghcr.io/defenseunicorns/doesnotexist:1.3.3.7 + - ghcr.io/zarf-dev/doesnotexist:1.3.3.7 diff --git a/src/test/packages/08-differential-package/zarf.yaml b/src/test/packages/08-differential-package/zarf.yaml index 9d911a66d0..c1e5d4ab71 100644 --- a/src/test/packages/08-differential-package/zarf.yaml +++ b/src/test/packages/08-differential-package/zarf.yaml @@ -9,7 +9,7 @@ components: required: true images: - ghcr.io/stefanprodan/podinfo:6.0.0 - - ghcr.io/defenseunicorns/zarf/agent:###ZARF_PKG_TMPL_PACKAGE_VERSION### + - ghcr.io/zarf-dev/zarf/agent:###ZARF_PKG_TMPL_PACKAGE_VERSION### repos: - https://github.com/defenseunicorns/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e - https://github.com/defenseunicorns/zarf.git@refs/tags/###ZARF_PKG_TMPL_PACKAGE_VERSION### diff --git a/src/test/packages/14-index-sha/image-index/zarf.yaml b/src/test/packages/14-index-sha/image-index/zarf.yaml index a29c4380b9..76d3e6ab4f 100644 --- a/src/test/packages/14-index-sha/image-index/zarf.yaml +++ b/src/test/packages/14-index-sha/image-index/zarf.yaml @@ -6,4 +6,4 @@ components: - name: baseline required: true images: - - ghcr.io/defenseunicorns/zarf/agent:v0.32.6@sha256:05a82656df5466ce17c3e364c16792ae21ce68438bfe06eeab309d0520c16b48 + - ghcr.io/zarf-dev/zarf/agent:v0.32.6@sha256:05a82656df5466ce17c3e364c16792ae21ce68438bfe06eeab309d0520c16b48 diff --git a/zarf-config.toml b/zarf-config.toml index eb549dac54..09afb3eed3 100644 --- a/zarf-config.toml +++ b/zarf-config.toml @@ -1,7 +1,7 @@ [package.create.set] # The image reference to use for the Zarf agent, defaults to a locally built image agent_image_domain = 'ghcr.io/' -agent_image = 'defenseunicorns/zarf/agent' +agent_image = 'zarf-dev/zarf/agent' agent_image_tag = 'local' # Tag for the zarf injector binary to use From a550ac14d2fe9ac1fea86c30c13c01688743b359 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:24:15 -0400 Subject: [PATCH 122/132] chore: move public test repo (#2739) Signed-off-by: Austin Abro --- .github/workflows/build-rust-injector.yml | 1 + .github/workflows/dummy-dco.yaml | 12 ++++++++++++ examples/git-data/zarf.yaml | 10 +++++----- src/pkg/packager/lint/lint_test.go | 2 +- src/test/e2e/07_create_git_test.go | 10 +++++----- src/test/e2e/09_component_compose_test.go | 4 ++-- src/test/e2e/22_git_and_gitops_test.go | 4 ++-- .../09-composable-packages/sub-package/zarf.yaml | 2 +- src/test/packages/09-composable-packages/zarf.yaml | 2 +- src/test/packages/12-lint/zarf.yaml | 2 +- src/test/packages/22-git-data/zarf.yaml | 12 +++++------- 11 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/dummy-dco.yaml diff --git a/.github/workflows/build-rust-injector.yml b/.github/workflows/build-rust-injector.yml index 77d939f6f7..eb41af52c7 100644 --- a/.github/workflows/build-rust-injector.yml +++ b/.github/workflows/build-rust-injector.yml @@ -2,6 +2,7 @@ name: Zarf Injector Rust Binaries permissions: contents: read + id-token: write on: workflow_dispatch: diff --git a/.github/workflows/dummy-dco.yaml b/.github/workflows/dummy-dco.yaml new file mode 100644 index 0000000000..25f022c804 --- /dev/null +++ b/.github/workflows/dummy-dco.yaml @@ -0,0 +1,12 @@ +name: DCO +on: + merge_group: + +permissions: + contents: read + +jobs: + DCO: + runs-on: ubuntu-latest + steps: + - run: echo "dummy DCO workflow (it won't run any check actually) to trigger by merge_group in order to enable merge queue" diff --git a/examples/git-data/zarf.yaml b/examples/git-data/zarf.yaml index 01e2c5d4eb..f2294f7c09 100644 --- a/examples/git-data/zarf.yaml +++ b/examples/git-data/zarf.yaml @@ -8,30 +8,30 @@ components: - name: full-repo repos: # The following performs a full Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git + - https://github.com/zarf-dev/zarf-public-test.git # The following performs a full Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test - name: specific-tag repos: # The following performs a tag Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@v0.0.1 + - https://github.com/zarf-dev/zarf-public-test.git@v0.0.1 # The following performs a refspec tag Git Repo Mirror with `go-git` - - https://github.com/defenseunicorns/zarf-public-test.git@refs/tags/v0.0.1 + - https://github.com/zarf-dev/zarf-public-test.git@refs/tags/v0.0.1 # The following performs a tag Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1 - name: specific-branch repos: # The following performs a branch Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@refs/heads/dragons + - https://github.com/zarf-dev/zarf-public-test.git@refs/heads/dragons # The following performs a branch Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@refs/heads/dragons - name: specific-hash repos: # The following performs a SHA Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@01a23218923f24194133b5eb11268cf8d73ff1bb + - https://github.com/zarf-dev/zarf-public-test.git@01a23218923f24194133b5eb11268cf8d73ff1bb # The following performs a SHA Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@01a23218923f24194133b5eb11268cf8d73ff1bb diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go index d20257b23a..67af529a8d 100644 --- a/src/pkg/packager/lint/lint_test.go +++ b/src/pkg/packager/lint/lint_test.go @@ -196,7 +196,7 @@ func TestValidateComponent(t *testing.T) { t.Run("Unpinnned repo warning", func(t *testing.T) { t.Parallel() - unpinnedRepo := "https://github.com/defenseunicorns/zarf-public-test.git" + unpinnedRepo := "https://github.com/zarf-dev/zarf-public-test.git" component := types.ZarfComponent{Repos: []string{ unpinnedRepo, "https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1", diff --git a/src/test/e2e/07_create_git_test.go b/src/test/e2e/07_create_git_test.go index 115558ace9..6c1e3a38c6 100644 --- a/src/test/e2e/07_create_git_test.go +++ b/src/test/e2e/07_create_git_test.go @@ -26,7 +26,7 @@ func TestCreateGit(t *testing.T) { defer e2e.CleanFiles(extractDir) // Verify the full-repo component - gitDir := fmt.Sprintf("%s/components/full-repo/repos/zarf-public-test-1143224168/.git", extractDir) + gitDir := fmt.Sprintf("%s/components/full-repo/repos/zarf-public-test-2395699829/.git", extractDir) verifyGitRepo(t, gitDir, "0a6b587", "(HEAD -> main, online-upstream/main)", "Adjust dragon spacing", "v0.0.1\n", " dragons\n* main\n") @@ -38,13 +38,13 @@ func TestCreateGit(t *testing.T) { "v0.0.1\n", " dragons\n* main\n") // Verify specific tag component shorthand tag - gitDir = fmt.Sprintf("%s/components/specific-tag/repos/zarf-public-test-443792367/.git", extractDir) + gitDir = fmt.Sprintf("%s/components/specific-tag/repos/zarf-public-test-470731282/.git", extractDir) verifyGitRepo(t, gitDir, "5249809", "(HEAD -> zarf-ref-v0.0.1, tag: v0.0.1)", "Added README.md", "v0.0.1\n", "* zarf-ref-v0.0.1\n") // Verify specific tag component refspec tag - gitDir = fmt.Sprintf("%s/components/specific-tag/repos/zarf-public-test-1981411475/.git", extractDir) + gitDir = fmt.Sprintf("%s/components/specific-tag/repos/zarf-public-test-482869567/.git", extractDir) verifyGitRepo(t, gitDir, "5249809", "(HEAD -> zarf-ref-v0.0.1, tag: v0.0.1)", "Added README.md", "v0.0.1\n", "* zarf-ref-v0.0.1\n") @@ -56,7 +56,7 @@ func TestCreateGit(t *testing.T) { "v0.0.1\n", "* zarf-ref-v0.0.1\n") // Verify specific branch component - gitDir = fmt.Sprintf("%s/components/specific-branch/repos/zarf-public-test-1670574289/.git", extractDir) + gitDir = fmt.Sprintf("%s/components/specific-branch/repos/zarf-public-test-2265377406/.git", extractDir) verifyGitRepo(t, gitDir, "01a2321", "(HEAD -> dragons, online-upstream/dragons)", "Explain what this repo does", "", "* dragons\n") @@ -68,7 +68,7 @@ func TestCreateGit(t *testing.T) { "", "* dragons\n") // Verify specific hash component - gitDir = fmt.Sprintf("%s/components/specific-hash/repos/zarf-public-test-2357350897/.git", extractDir) + gitDir = fmt.Sprintf("%s/components/specific-hash/repos/zarf-public-test-3231174532/.git", extractDir) verifyGitRepo(t, gitDir, "01a2321", "(HEAD -> zarf-ref-01a23218923f24194133b5eb11268cf8d73ff1bb, online-upstream/dragons)", "Explain what this repo does", "v0.0.1\n", " main\n* zarf-ref-01a23218923f24194133b5eb11268cf8d73ff1bb\n") diff --git a/src/test/e2e/09_component_compose_test.go b/src/test/e2e/09_component_compose_test.go index c5edaeb682..176b96207e 100644 --- a/src/test/e2e/09_component_compose_test.go +++ b/src/test/e2e/09_component_compose_test.go @@ -139,8 +139,8 @@ func (suite *CompositionSuite) Test_1_FullComposability() { - ghcr.io/stefanprodan/podinfo:6.4.0 - ghcr.io/stefanprodan/podinfo:6.4.1 repos: - - https://github.com/defenseunicorns/zarf-public-test.git - - https://github.com/defenseunicorns/zarf-public-test.git@refs/heads/dragons + - https://github.com/zarf-dev/zarf-public-test.git + - https://github.com/zarf-dev/zarf-public-test.git@refs/heads/dragons `) // Check dataInjections diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index 956ded5ed9..572702dbe4 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -77,7 +77,7 @@ func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { gitCfg := git.New(zarfState.GitServer) // Get the repo as the readonly user - repoName := "zarf-public-test-2469062884" + repoName := "zarf-public-test-2363058019" getRepoRequest, _ := http.NewRequest("GET", fmt.Sprintf("%s/api/v1/repos/%s/%s", gitURL, zarfState.GitServer.PushUsername, repoName), nil) getRepoResponseBody, _, err := gitCfg.DoHTTPThings(getRepoRequest, types.ZarfGitReadUser, zarfState.GitServer.PullPassword) require.NoError(t, err) @@ -101,7 +101,7 @@ func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable zarfState, err := c.LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") - repoName := "zarf-public-test-2469062884" + repoName := "zarf-public-test-2363058019" gitCfg := git.New(zarfState.GitServer) diff --git a/src/test/packages/09-composable-packages/sub-package/zarf.yaml b/src/test/packages/09-composable-packages/sub-package/zarf.yaml index 20b0a5b323..a749d669fb 100644 --- a/src/test/packages/09-composable-packages/sub-package/zarf.yaml +++ b/src/test/packages/09-composable-packages/sub-package/zarf.yaml @@ -26,7 +26,7 @@ components: images: - ghcr.io/stefanprodan/podinfo:6.4.0 repos: - - https://github.com/defenseunicorns/zarf-public-test.git + - https://github.com/zarf-dev/zarf-public-test.git files: - source: ../files/coffee-ipsum.txt target: coffee-ipsum.txt diff --git a/src/test/packages/09-composable-packages/zarf.yaml b/src/test/packages/09-composable-packages/zarf.yaml index cf121120e6..6ec2dd49b8 100644 --- a/src/test/packages/09-composable-packages/zarf.yaml +++ b/src/test/packages/09-composable-packages/zarf.yaml @@ -40,7 +40,7 @@ components: images: - ghcr.io/stefanprodan/podinfo:6.4.1 repos: - - https://github.com/defenseunicorns/zarf-public-test.git@refs/heads/dragons + - https://github.com/zarf-dev/zarf-public-test.git@refs/heads/dragons files: - source: files/coffee-ipsum.txt target: coffee-ipsum.txt diff --git a/src/test/packages/12-lint/zarf.yaml b/src/test/packages/12-lint/zarf.yaml index 84f5b53c72..a77e3f89c9 100644 --- a/src/test/packages/12-lint/zarf.yaml +++ b/src/test/packages/12-lint/zarf.yaml @@ -14,7 +14,7 @@ components: - name: full-repo repos: - - https://github.com/defenseunicorns/zarf-public-test.git + - https://github.com/zarf-dev/zarf-public-test.git - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1 - https://gitlab.com/gitlab-org/build/omnibus-mirror/pcre2/-/tree/vreverse?ref_type=heads images: diff --git a/src/test/packages/22-git-data/zarf.yaml b/src/test/packages/22-git-data/zarf.yaml index 2e3b56d75c..8051918672 100644 --- a/src/test/packages/22-git-data/zarf.yaml +++ b/src/test/packages/22-git-data/zarf.yaml @@ -9,19 +9,17 @@ components: required: true repos: # Do a full Git Repo Mirror - - https://github.com/defenseunicorns/zarf-public-test.git + - https://github.com/zarf-dev/zarf-public-test.git # The following performs a full Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test - # Perform a full repo mirror of a simple repository with a single branch - (this causes an "already up to date" error in go-git) - - https://github.com/defenseunicorns/golang-tekton-hello-world.git - name: specific-tag required: true repos: # The following performs a tag Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@v0.0.1 + - https://github.com/zarf-dev/zarf-public-test.git@v0.0.1 # The following performs a refspec tag Git Repo Mirror with `go-git` - - https://github.com/defenseunicorns/zarf-public-test.git@refs/tags/v0.0.1 + - https://github.com/zarf-dev/zarf-public-test.git@refs/tags/v0.0.1 # The following performs a tag Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@v0.0.1 actions: @@ -36,7 +34,7 @@ components: required: true repos: # The following performs a branch Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@refs/heads/dragons + - https://github.com/zarf-dev/zarf-public-test.git@refs/heads/dragons # The following performs a branch Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@refs/heads/dragons actions: @@ -51,7 +49,7 @@ components: required: true repos: # The following performs a SHA Git Repo Mirror with `go-git` (internal to Zarf) - - https://github.com/defenseunicorns/zarf-public-test.git@01a23218923f24194133b5eb11268cf8d73ff1bb + - https://github.com/zarf-dev/zarf-public-test.git@01a23218923f24194133b5eb11268cf8d73ff1bb # The following performs a SHA Git Repo Mirror forcing a fallback to host `git` - https://dev.azure.com/defenseunicorns/zarf-public-test/_git/zarf-public-test@01a23218923f24194133b5eb11268cf8d73ff1bb actions: From 0a5d54c883ca48e1dea441b0071a2368d13d63e7 Mon Sep 17 00:00:00 2001 From: schristoff-du <167717759+schristoff-du@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:51:48 -0400 Subject: [PATCH 123/132] fix: update README.md (#2729) --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 221ac84ae0..88385564ce 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ [![Latest Release](https://img.shields.io/github/v/release/defenseunicorns/zarf)](https://github.com/defenseunicorns/zarf/releases) [![Go version](https://img.shields.io/github/go-mod/go-version/defenseunicorns/zarf?filename=go.mod)](https://go.dev/) [![Build Status](https://img.shields.io/github/actions/workflow/status/defenseunicorns/zarf/release.yml)](https://github.com/defenseunicorns/zarf/actions/workflows/release.yml) -[![Zarf Documentation Status](https://api.netlify.com/api/v1/badges/fe846ae4-25fb-4274-9968-90782640ee9f/deploy-status)](https://app.netlify.com/sites/zarf-docs/deploys) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/zarf/badge)](https://securityscorecards.dev/viewer/?uri=github.com/defenseunicorns/zarf) zarf logo From 4bd52447194a771d38f6694027d90b6ee8c4aa2b Mon Sep 17 00:00:00 2001 From: Xander Grzywinski Date: Fri, 19 Jul 2024 13:23:57 -0400 Subject: [PATCH 124/132] docs: update to openssf code of conduct (#2734) Signed-off-by: Xander Grzywinski Co-authored-by: schristoff <28318173+schristoff@users.noreply.github.com> --- CODE_OF_CONDUCT.md | 133 +-------------------------------------------- README.md | 1 - 2 files changed, 1 insertion(+), 133 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a7d8c48f7e..6dd1456839 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,132 +1 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -zarf-dev-private@googlegroups.com. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file +Community members are required to abide by the [OpenSSF Code of Conduct](https://openssf.org/community/code-of-conduct/) in all project spaces including (but not limited to) GitHub, Slack, social media, and conferences. \ No newline at end of file diff --git a/README.md b/README.md index 88385564ce..e51160774f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,6 @@ [![Zarf Documentation](https://img.shields.io/badge/docs-docs.zarf.dev-775ba1)](https://docs.zarf.dev/) [![Zarf Slack Channel](https://img.shields.io/badge/k8s%20slack-zarf-40a3dd)](https://kubernetes.slack.com/archives/C03B6BJAUJ3) [![Community Meetups](https://img.shields.io/badge/community-meetups-22aebb)](https://github.com/defenseunicorns/zarf/issues/2202) -[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) Zarf eliminates the [complexity of air gap software delivery](https://www.itopstimes.com/contain/air-gap-kubernetes-considerations-for-running-cloud-native-applications-without-the-cloud/) for Kubernetes clusters and cloud-native workloads using a declarative packaging strategy to support DevSecOps in offline and semi-connected environments. From 1e4399ebeca84789666b938d76ed10351da939d0 Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Fri, 19 Jul 2024 14:03:57 -0500 Subject: [PATCH 125/132] chore: update project name references (#2741) Signed-off-by: Lucas Rodriguez --- .github/ISSUE_TEMPLATE/tech_debt.md | 2 +- .github/SECURITY.md | 2 +- .github/pull_request_template.md | 2 +- .goreleaser.yaml | 8 +- CONTRIBUTING.md | 8 +- Makefile | 10 +- README.md | 14 +-- SUPPORT.md | 4 +- examples/big-bang/zarf.yaml | 2 +- go.mod | 2 +- main.go | 6 +- packages/README.md | 2 +- packages/zarf-registry/configmap.yaml | 2 +- site/astro.config.ts | 4 +- site/hack/copy-examples.js | 2 +- site/public/architecture.drawio.svg | 2 +- .../public/tutorials/package_create_init.html | 2 +- .../publish_and_deploy_manifest.html | 2 +- .../tutorials/resource_adoption_deploy.html | 2 +- .../tutorials/resource_adoption_package.html | 2 +- .../content/docs/contribute/nerd-notes.mdx | 6 +- site/src/content/docs/contribute/testing.mdx | 2 +- site/src/content/docs/faq.mdx | 10 +- .../content/docs/getting-started/install.mdx | 14 +-- site/src/content/docs/index.mdx | 2 +- site/src/content/docs/ref/components.mdx | 8 +- site/src/content/docs/ref/dev.mdx | 8 +- site/src/content/docs/ref/init-package.mdx | 10 +- site/src/content/docs/ref/packages.mdx | 2 +- site/src/content/docs/roadmap.mdx | 8 +- site/src/content/docs/support.mdx | 2 +- .../tutorials/0-creating-a-zarf-package.mdx | 4 +- .../1-initializing-a-k8s-cluster.mdx | 4 +- .../tutorials/3-deploy-a-retro-arcade.mdx | 2 +- .../4-creating-a-k8s-cluster-with-zarf.mdx | 4 +- .../src/content/docs/tutorials/5-big-bang.mdx | 4 +- .../docs/tutorials/7-custom-init-packages.mdx | 2 +- .../9-package-create-differential.mdx | 2 +- site/src/content/docs/tutorials/index.mdx | 4 +- src/cmd/common/setup.go | 6 +- src/cmd/common/vendor.go | 2 +- src/cmd/common/viper.go | 6 +- src/cmd/connect.go | 8 +- src/cmd/destroy.go | 12 +-- src/cmd/dev.go | 16 +-- src/cmd/initialize.go | 18 ++-- src/cmd/internal.go | 14 +-- src/cmd/package.go | 16 +-- src/cmd/root.go | 14 +-- src/cmd/tools/archiver.go | 6 +- src/cmd/tools/common.go | 6 +- src/cmd/tools/crane.go | 14 +-- src/cmd/tools/helm.go | 6 +- src/cmd/tools/helm/repo_add.go | 2 +- src/cmd/tools/k9s.go | 2 +- src/cmd/tools/kubectl.go | 6 +- src/cmd/tools/syft.go | 4 +- src/cmd/tools/wait.go | 6 +- src/cmd/tools/yq.go | 2 +- src/cmd/tools/zarf.go | 24 ++--- src/cmd/version.go | 4 +- src/config/config.go | 2 +- src/extensions/bigbang/banner.go | 2 +- src/extensions/bigbang/bigbang.go | 14 +-- src/extensions/bigbang/flux.go | 8 +- src/extensions/bigbang/manifests.go | 2 +- src/extensions/bigbang/test/bigbang_test.go | 6 +- .../agent/hooks/argocd-application.go | 12 +-- .../agent/hooks/argocd-application_test.go | 6 +- src/internal/agent/hooks/argocd-repository.go | 12 +-- .../agent/hooks/argocd-repository_test.go | 6 +- src/internal/agent/hooks/common.go | 2 +- src/internal/agent/hooks/flux-gitrepo.go | 12 +-- src/internal/agent/hooks/flux-gitrepo_test.go | 8 +- src/internal/agent/hooks/flux-helmrepo.go | 12 +-- .../agent/hooks/flux-helmrepo_test.go | 8 +- src/internal/agent/hooks/flux-ocirepo.go | 12 +-- src/internal/agent/hooks/flux-ocirepo_test.go | 8 +- src/internal/agent/hooks/pods.go | 12 +-- src/internal/agent/hooks/pods_test.go | 8 +- src/internal/agent/hooks/utils_test.go | 6 +- src/internal/agent/http/admission/handler.go | 6 +- src/internal/agent/http/proxy.go | 8 +- src/internal/agent/http/server.go | 8 +- src/internal/agent/operations/hook.go | 4 +- src/internal/agent/start.go | 6 +- src/internal/packager/git/checkout.go | 2 +- src/internal/packager/git/clone.go | 6 +- src/internal/packager/git/common.go | 4 +- src/internal/packager/git/gitea.go | 8 +- src/internal/packager/git/pull.go | 6 +- src/internal/packager/git/push.go | 6 +- src/internal/packager/helm/chart.go | 6 +- src/internal/packager/helm/common.go | 10 +- src/internal/packager/helm/destroy.go | 4 +- src/internal/packager/helm/images.go | 2 +- src/internal/packager/helm/post-render.go | 10 +- src/internal/packager/helm/repo.go | 14 +-- src/internal/packager/helm/utils.go | 2 +- src/internal/packager/helm/zarf.go | 14 +-- src/internal/packager/images/common.go | 10 +- src/internal/packager/images/pull.go | 14 +-- src/internal/packager/images/pull_test.go | 2 +- src/internal/packager/images/push.go | 8 +- src/internal/packager/sbom/catalog.go | 10 +- src/internal/packager/sbom/tools.go | 4 +- src/internal/packager/sbom/viewer.go | 4 +- src/internal/packager/template/template.go | 12 +-- src/pkg/cluster/cluster.go | 2 +- src/pkg/cluster/data.go | 12 +-- src/pkg/cluster/injector.go | 8 +- src/pkg/cluster/namespace.go | 2 +- src/pkg/cluster/secrets.go | 6 +- src/pkg/cluster/secrets_test.go | 2 +- src/pkg/cluster/state.go | 10 +- src/pkg/cluster/state_test.go | 6 +- src/pkg/cluster/tunnel.go | 4 +- src/pkg/cluster/tunnel_test.go | 2 +- src/pkg/cluster/zarf.go | 6 +- src/pkg/cluster/zarf_test.go | 4 +- src/pkg/interactive/components.go | 6 +- src/pkg/interactive/prompt.go | 4 +- src/pkg/layout/component.go | 4 +- src/pkg/layout/image.go | 2 +- src/pkg/layout/package.go | 10 +- src/pkg/layout/split.go | 4 +- src/pkg/layout/split_test.go | 2 +- src/pkg/message/connect.go | 2 +- src/pkg/message/credentials.go | 2 +- src/pkg/message/message.go | 2 +- src/pkg/packager/actions/actions.go | 12 +-- src/pkg/packager/common.go | 22 ++-- src/pkg/packager/common_test.go | 6 +- src/pkg/packager/composer/list.go | 14 +-- src/pkg/packager/composer/list_test.go | 6 +- src/pkg/packager/composer/oci.go | 10 +- src/pkg/packager/composer/override.go | 2 +- src/pkg/packager/composer/pathfixer.go | 2 +- src/pkg/packager/create.go | 8 +- src/pkg/packager/creator/compose.go | 6 +- src/pkg/packager/creator/compose_test.go | 2 +- src/pkg/packager/creator/creator.go | 4 +- src/pkg/packager/creator/creator_test.go | 4 +- src/pkg/packager/creator/differential.go | 10 +- src/pkg/packager/creator/normal.go | 34 +++--- src/pkg/packager/creator/skeleton.go | 20 ++-- src/pkg/packager/creator/template.go | 12 +-- src/pkg/packager/creator/utils.go | 6 +- src/pkg/packager/deploy.go | 26 ++--- src/pkg/packager/deploy_test.go | 6 +- src/pkg/packager/deprecated/common.go | 6 +- src/pkg/packager/deprecated/common_test.go | 2 +- .../deprecated/pluralize-set-variable.go | 4 +- .../packager/deprecated/scripts-to-actions.go | 2 +- src/pkg/packager/dev.go | 16 +-- src/pkg/packager/filters/deploy.go | 4 +- src/pkg/packager/filters/deploy_test.go | 2 +- src/pkg/packager/filters/diff.go | 6 +- src/pkg/packager/filters/diff_test.go | 2 +- src/pkg/packager/filters/empty.go | 2 +- src/pkg/packager/filters/empty_test.go | 2 +- src/pkg/packager/filters/os.go | 2 +- src/pkg/packager/filters/os_test.go | 2 +- src/pkg/packager/filters/select.go | 2 +- src/pkg/packager/filters/select_test.go | 2 +- src/pkg/packager/filters/strat.go | 2 +- src/pkg/packager/filters/strat_test.go | 2 +- src/pkg/packager/generate.go | 8 +- src/pkg/packager/inspect.go | 4 +- src/pkg/packager/interactive.go | 8 +- src/pkg/packager/lint/findings.go | 2 +- src/pkg/packager/lint/findings_test.go | 2 +- src/pkg/packager/lint/lint.go | 16 +-- src/pkg/packager/lint/lint_test.go | 4 +- src/pkg/packager/mirror.go | 8 +- src/pkg/packager/prepare.go | 18 ++-- src/pkg/packager/publish.go | 18 ++-- src/pkg/packager/remove.go | 16 +-- src/pkg/packager/sources/cluster.go | 10 +- src/pkg/packager/sources/new.go | 12 +-- src/pkg/packager/sources/new_test.go | 10 +- src/pkg/packager/sources/oci.go | 14 +-- src/pkg/packager/sources/split.go | 8 +- src/pkg/packager/sources/tarball.go | 10 +- src/pkg/packager/sources/url.go | 10 +- src/pkg/packager/sources/utils.go | 8 +- src/pkg/packager/sources/validate.go | 8 +- src/pkg/pki/pki.go | 2 +- src/pkg/transform/git_test.go | 102 +++++++++--------- src/pkg/transform/image_test.go | 24 ++--- src/pkg/utils/auth.go | 2 +- src/pkg/utils/auth_test.go | 2 +- src/pkg/utils/bytes.go | 2 +- src/pkg/utils/cosign.go | 6 +- src/pkg/utils/image.go | 2 +- src/pkg/utils/io.go | 4 +- src/pkg/utils/network.go | 4 +- src/pkg/utils/network_test.go | 2 +- src/pkg/utils/wait.go | 4 +- src/pkg/utils/wait_test.go | 2 +- src/pkg/utils/yaml.go | 4 +- src/pkg/variables/types.go | 2 +- src/pkg/zoci/common.go | 4 +- src/pkg/zoci/copier.go | 2 +- src/pkg/zoci/fetch.go | 4 +- src/pkg/zoci/pull.go | 8 +- src/pkg/zoci/push.go | 6 +- src/pkg/zoci/utils.go | 2 +- src/test/common.go | 2 +- src/test/e2e/05_tarball_test.go | 6 +- src/test/e2e/07_create_git_test.go | 2 +- src/test/e2e/08_create_differential_test.go | 10 +- src/test/e2e/12_lint_test.go | 2 +- src/test/e2e/13_zarf_package_generate_test.go | 6 +- src/test/e2e/20_zarf_init_test.go | 2 +- src/test/e2e/21_connect_creds_test.go | 2 +- src/test/e2e/22_git_and_gitops_test.go | 6 +- src/test/e2e/23_data_injection_test.go | 4 +- src/test/e2e/25_helm_test.go | 2 +- src/test/e2e/26_simple_packages_test.go | 2 +- src/test/e2e/27_deploy_regression_test.go | 2 +- src/test/e2e/28_wait_test.go | 2 +- src/test/e2e/50_oci_publish_deploy_test.go | 2 +- src/test/e2e/51_oci_compose_test.go | 8 +- src/test/e2e/99_yolo_test.go | 2 +- src/test/e2e/main_test.go | 6 +- src/test/external/common.go | 4 +- src/test/external/ext_in_cluster_test.go | 4 +- src/test/external/ext_out_cluster_test.go | 4 +- src/test/nightly/ecr_publish_test.go | 4 +- .../08-differential-package/zarf.yaml | 6 +- .../packages/51-import-everything/zarf.yaml | 2 +- src/test/upgrade/previously_built_test.go | 4 +- src/types/component.go | 6 +- src/types/k8s.go | 2 +- src/types/package.go | 2 +- src/types/validate.go | 2 +- src/types/validate_test.go | 4 +- zarf.schema.json | 2 +- 239 files changed, 796 insertions(+), 796 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/tech_debt.md b/.github/ISSUE_TEMPLATE/tech_debt.md index 8d862c65b9..aa19a1fdfc 100644 --- a/.github/ISSUE_TEMPLATE/tech_debt.md +++ b/.github/ISSUE_TEMPLATE/tech_debt.md @@ -10,7 +10,7 @@ assignees: '' A clear and concise description of what should be changed/researched. Ex. This piece of the code is not DRY enough [...] ### Links to any relevant code -(optional) i.e. - https://github.com/defenseunicorns/zarf/blob/main/README.md?plain=1#L1 +(optional) i.e. - https://github.com/zarf-dev/zarf/blob/main/README.md?plain=1#L1 ### Additional context Add any other context or screenshots about the technical debt here. diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 6fd559327f..53776bbac6 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,6 +1,6 @@ # Reporting Security Issues -To report a security issue or vulnerability in Zarf, please use the confidential GitHub Security Advisory ["Report a Vulnerability"](https://github.com/defenseunicorns/zarf/security/advisories) tab. The Zarf team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. +To report a security issue or vulnerability in Zarf, please use the confidential GitHub Security Advisory ["Report a Vulnerability"](https://github.com/zarf-dev/zarf/security/advisories) tab. The Zarf team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. ### When Should I Report a Vulnerability? diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 370d4faa9a..00544265c2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,4 +11,4 @@ Relates to # ## Checklist before merging - [ ] Test, docs, adr added or updated as needed -- [ ] [Contributor Guide Steps](https://github.com/defenseunicorns/zarf/blob/main/CONTRIBUTING.md#developer-workflow) followed +- [ ] [Contributor Guide Steps](https://github.com/zarf-dev/zarf/blob/main/CONTRIBUTING.md#developer-workflow) followed diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8743d5d263..8af7ee7d5b 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -17,7 +17,7 @@ builds: - darwin - windows ldflags: - - -s -w -X github.com/defenseunicorns/zarf/src/config.CLIVersion={{.Tag}} + - -s -w -X github.com/zarf-dev/zarf/src/config.CLIVersion={{.Tag}} - -X k8s.io/component-base/version.gitVersion=v{{.Env.K8S_MODULES_MAJOR_VER}}.{{.Env.K8S_MODULES_MINOR_VER}}.{{.Env.K8S_MODULES_PATCH_VER}} - -X k8s.io/component-base/version.gitCommit={{.FullCommit}} - -X k8s.io/component-base/version.buildDate={{.Date}} @@ -27,9 +27,9 @@ builds: - -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMinor={{.Env.K8S_MODULES_MINOR_VER}} - -X github.com/derailed/k9s/cmd.version={{.Env.K9S_VERSION}} - -X github.com/google/go-containerregistry/cmd/crane/cmd.Version={{.Env.CRANE_VERSION}} - - -X github.com/defenseunicorns/zarf/src/cmd/tools.syftVersion={{.Env.SYFT_VERSION}} - - -X github.com/defenseunicorns/zarf/src/cmd/tools.archiverVersion={{.Env.ARCHIVER_VERSION}} - - -X github.com/defenseunicorns/zarf/src/cmd/tools.helmVersion={{.Env.HELM_VERSION}} + - -X github.com/zarf-dev/zarf/src/cmd/tools.syftVersion={{.Env.SYFT_VERSION}} + - -X github.com/zarf-dev/zarf/src/cmd/tools.archiverVersion={{.Env.ARCHIVER_VERSION}} + - -X github.com/zarf-dev/zarf/src/cmd/tools.helmVersion={{.Env.HELM_VERSION}} goarch: - amd64 - arm64 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 700f161b7a..2434cba024 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ Now every time you commit, the hooks will run and format your code, linting can :key: == Required by automation -1. Look at the next due [release milestone](https://github.com/defenseunicorns/zarf/milestones) and pick an issue that you want to work on. If you don't see anything that interests you, create an issue and assign it to yourself. +1. Look at the next due [release milestone](https://github.com/zarf-dev/zarf/milestones) and pick an issue that you want to work on. If you don't see anything that interests you, create an issue and assign it to yourself. 1. Drop a comment in the issue to let everyone know you're working on it and submit a Draft PR (step 4) as soon as you are able. If you have any questions as you work through the code, reach out in the [Zarf Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03BP9Z3CMA). 1. :key: Set up your Git config to GPG sign all commits. [Here's some documentation on how to set it up](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits). You won't be able to merge your PR if you have any unverified commits. 1. Create a Draft Pull Request as soon as you can, even if it is just 5 minutes after you started working on it. We lean towards working in the open as much as we can. If you're not sure what to put in the PR description, just put a link to the issue you're working on. @@ -41,8 +41,8 @@ Now every time you commit, the hooks will run and format your code, linting can - :key: We follow the [conventional commits spec](https://www.conventionalcommits.org/en/v1.0.0/) with the [commitlint conventional config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) as extended types for PR titles. 1. :key: Automated tests will begin based on the paths you have edited in your Pull Request. - > ⚠️ **NOTE:** _If you are an external third-party contributor, the pipelines won't run until a [CODEOWNER](https://github.com/defenseunicorns/zarf/blob/main/CODEOWNERS) approves the pipeline run._ -1. :key: Be sure to use the [needs-adr,needs-docs,needs-tests](https://github.com/defenseunicorns/zarf/labels?q=needs) labels as appropriate for the PR. Once you have addressed all of the needs, remove the label. + > ⚠️ **NOTE:** _If you are an external third-party contributor, the pipelines won't run until a [CODEOWNER](https://github.com/zarf-dev/zarf/blob/main/CODEOWNERS) approves the pipeline run._ +1. :key: Be sure to use the [needs-adr,needs-docs,needs-tests](https://github.com/zarf-dev/zarf/labels?q=needs) labels as appropriate for the PR. Once you have addressed all of the needs, remove the label. 1. Once the review is complete and approved, a core member of the zarf project will merge your PR. If you are an external third-party contributor, two core members of the zarf project will be required to approve the PR. 1. Close the issue if it is fully resolved by your PR. _Hint: You can add "Fixes #XX" to the PR description to automatically close an issue when the PR is merged._ @@ -56,7 +56,7 @@ Our unit tests can be found as `*_test.go` files inside the package that they ar ## Documentation -The CLI docs (located at `site/src/content/docs/commands`), and [`zarf.schema.json`](https://github.com/defenseunicorns/zarf/blob/main/zarf.schema.json) are autogenerated from `make docs-and-schema`. Run this make target locally to regenerate the schema and documentation each time you make a change to the CLI commands or the schema, otherwise CI will fail. +The CLI docs (located at `site/src/content/docs/commands`), and [`zarf.schema.json`](https://github.com/zarf-dev/zarf/blob/main/zarf.schema.json) are autogenerated from `make docs-and-schema`. Run this make target locally to regenerate the schema and documentation each time you make a change to the CLI commands or the schema, otherwise CI will fail. We do this so that there is a git commit signature from a person on the commit for better traceability, rather than a non-person entity (e.g. GitHub CI token). diff --git a/Makefile b/Makefile index 0f2deebcaf..bdce6970d3 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ else endif CLI_VERSION ?= $(if $(shell git describe --tags),$(shell git describe --tags),"UnknownVersion") -BUILD_ARGS := -s -w -X github.com/defenseunicorns/zarf/src/config.CLIVersion=$(CLI_VERSION) +BUILD_ARGS := -s -w -X github.com/zarf-dev/zarf/src/config.CLIVersion=$(CLI_VERSION) K8S_MODULES_VER=$(subst ., ,$(subst v,,$(shell go list -f '{{.Version}}' -m k8s.io/client-go))) K8S_MODULES_MAJOR_VER=$(shell echo $$(($(firstword $(K8S_MODULES_VER)) + 1))) K8S_MODULES_MINOR_VER=$(word 2,$(K8S_MODULES_VER)) @@ -48,9 +48,9 @@ BUILD_ARGS += -X helm.sh/helm/v3/pkg/chartutil.k8sVersionMinor=$(K8S_MODULES_MIN BUILD_ARGS += -X k8s.io/component-base/version.gitVersion=v$(K8S_MODULES_MAJOR_VER).$(K8S_MODULES_MINOR_VER).$(K8S_MODULES_PATCH_VER) BUILD_ARGS += -X github.com/derailed/k9s/cmd.version=$(K9S_VERSION) BUILD_ARGS += -X github.com/google/go-containerregistry/cmd/crane/cmd.Version=$(CRANE_VERSION) -BUILD_ARGS += -X github.com/defenseunicorns/zarf/src/cmd/tools.syftVersion=$(SYFT_VERSION) -BUILD_ARGS += -X github.com/defenseunicorns/zarf/src/cmd/tools.archiverVersion=$(ARCHIVER_VERSION) -BUILD_ARGS += -X github.com/defenseunicorns/zarf/src/cmd/tools.helmVersion=$(HELM_VERSION) +BUILD_ARGS += -X github.com/zarf-dev/zarf/src/cmd/tools.syftVersion=$(SYFT_VERSION) +BUILD_ARGS += -X github.com/zarf-dev/zarf/src/cmd/tools.archiverVersion=$(ARCHIVER_VERSION) +BUILD_ARGS += -X github.com/zarf-dev/zarf/src/cmd/tools.helmVersion=$(HELM_VERSION) GIT_SHA := $(if $(shell git rev-parse HEAD),$(shell git rev-parse HEAD),"") BUILD_DATE := $(shell date -u +'%Y-%m-%dT%H:%M:%SZ') @@ -210,7 +210,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 -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v '^github.com/defenseunicorns/zarf/src/test' | grep -v 'github.com/defenseunicorns/zarf/src/extensions/bigbang/test') + go test -failfast -v -coverprofile=coverage.out -covermode=atomic $$(go list ./... | grep -v '^github.com/zarf-dev/zarf/src/test' | grep -v 'github.com/zarf-dev/zarf/src/extensions/bigbang/test') # INTERNAL: used to test that a dev has ran `make docs-and-schema` in their PR test-docs-and-schema: diff --git a/README.md b/README.md index e51160774f..c49b2ade6c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # Zarf - DevSecOps for Air Gap -[![Latest Release](https://img.shields.io/github/v/release/defenseunicorns/zarf)](https://github.com/defenseunicorns/zarf/releases) -[![Go version](https://img.shields.io/github/go-mod/go-version/defenseunicorns/zarf?filename=go.mod)](https://go.dev/) -[![Build Status](https://img.shields.io/github/actions/workflow/status/defenseunicorns/zarf/release.yml)](https://github.com/defenseunicorns/zarf/actions/workflows/release.yml) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/zarf/badge)](https://securityscorecards.dev/viewer/?uri=github.com/defenseunicorns/zarf) +[![Latest Release](https://img.shields.io/github/v/release/zarf-dev/zarf)](https://github.com/zarf-dev/zarf/releases) +[![Go version](https://img.shields.io/github/go-mod/go-version/zarf-dev/zarf?filename=go.mod)](https://go.dev/) +[![Build Status](https://img.shields.io/github/actions/workflow/status/zarf-dev/zarf/release.yml)](https://github.com/zarf-dev/zarf/actions/workflows/release.yml) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/zarf-dev/zarf/badge)](https://securityscorecards.dev/viewer/?uri=github.com/zarf-dev/zarf) zarf logo [![Zarf Website](https://img.shields.io/badge/web-zarf.dev-6d87c3)](https://zarf.dev/) [![Zarf Documentation](https://img.shields.io/badge/docs-docs.zarf.dev-775ba1)](https://docs.zarf.dev/) [![Zarf Slack Channel](https://img.shields.io/badge/k8s%20slack-zarf-40a3dd)](https://kubernetes.slack.com/archives/C03B6BJAUJ3) -[![Community Meetups](https://img.shields.io/badge/community-meetups-22aebb)](https://github.com/defenseunicorns/zarf/issues/2202) +[![Community Meetups](https://img.shields.io/badge/community-meetups-22aebb)](https://github.com/zarf-dev/zarf/issues/2202) Zarf eliminates the [complexity of air gap software delivery](https://www.itopstimes.com/contain/air-gap-kubernetes-considerations-for-running-cloud-native-applications-without-the-cloud/) for Kubernetes clusters and cloud-native workloads using a declarative packaging strategy to support DevSecOps in offline and semi-connected environments. @@ -77,8 +77,8 @@ Join us on the [Kubernetes Slack](https://kubernetes.slack.com/) in the [_#zarf_ We are so grateful to our Zarf community for contributing bug fixes and collaborating on new features: - - Zarf contributors + + Zarf contributors Made with [contrib.rocks](https://contrib.rocks). diff --git a/SUPPORT.md b/SUPPORT.md index ad6ce504bd..27d362cb58 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -4,10 +4,10 @@ We strive to create clear guidelines on communication to the Zarf team to provid ## Questions For guidance on using Zarf, [the documentation](https://docs.zarf.dev/) should cover most use cases. -For all questions documentation may not cover, we suggest utilizing [Github Discussions](https://github.com/defenseunicorns/zarf/discussions). +For all questions documentation may not cover, we suggest utilizing [Github Discussions](https://github.com/zarf-dev/zarf/discussions). ## Standard Process -All code issues should be a [Github Issue](https://github.com/defenseunicorns/zarf/issues/new/choose) that follows the issue template. +All code issues should be a [Github Issue](https://github.com/zarf-dev/zarf/issues/new/choose) that follows the issue template. Following the templates provides the Zarf community a foundation of understanding to be able to assist quickly. After an issue is made, this issue can be brought into other channels such as the [Kubernetes Slack #Zarf](https://zarf.dev/slack) channel or the [bi-weekly Zarf Community Meeting](https://docs.zarf.dev/contribute/contributor-guide/). diff --git a/examples/big-bang/zarf.yaml b/examples/big-bang/zarf.yaml index 534ebd0ea4..672ec4f8d1 100644 --- a/examples/big-bang/zarf.yaml +++ b/examples/big-bang/zarf.yaml @@ -96,4 +96,4 @@ x-mdx: | You can learn about YOLO mode in the [FAQ](/faq#what-is-yolo-mode-and-why-would-i-use-it) or the [YOLO mode example](/ref/examples/yolo/). - [Big Bang YOLO Mode Example](https://github.com/defenseunicorns/zarf/tree/main/examples/big-bang/yolo). + [Big Bang YOLO Mode Example](https://github.com/zarf-dev/zarf/tree/main/examples/big-bang/yolo). diff --git a/go.mod b/go.mod index b8cee0d6d6..9bf35fbbba 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/defenseunicorns/zarf +module github.com/zarf-dev/zarf go 1.22.4 diff --git a/main.go b/main.go index 8068f4b4fc..70feb480f4 100644 --- a/main.go +++ b/main.go @@ -11,9 +11,9 @@ import ( "os/signal" "syscall" - "github.com/defenseunicorns/zarf/src/cmd" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/pkg/packager/lint" + "github.com/zarf-dev/zarf/src/cmd" + "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/pkg/packager/lint" ) //go:embed cosign.pub diff --git a/packages/README.md b/packages/README.md index 1776860ba4..be4516cf68 100644 --- a/packages/README.md +++ b/packages/README.md @@ -1,6 +1,6 @@ # Zarf Packages -This folder contains packages maintained by the [Zarf team](https://github.com/defenseunicorns/zarf/graphs/contributors). Some of these packages are used by `zarf init` for new cluster initialization. +This folder contains packages maintained by the [Zarf team](https://github.com/zarf-dev/zarf/graphs/contributors). Some of these packages are used by `zarf init` for new cluster initialization. ## Distros diff --git a/packages/zarf-registry/configmap.yaml b/packages/zarf-registry/configmap.yaml index 02c9b9bb0b..fe3e7bf235 100644 --- a/packages/zarf-registry/configmap.yaml +++ b/packages/zarf-registry/configmap.yaml @@ -7,4 +7,4 @@ metadata: data: localRegistryHosting.v1: | host: "###ZARF_REGISTRY###" - help: "https://github.com/defenseunicorns/zarf" + help: "https://github.com/zarf-dev/zarf" diff --git a/site/astro.config.ts b/site/astro.config.ts index d787bd08c6..d00b26b4af 100644 --- a/site/astro.config.ts +++ b/site/astro.config.ts @@ -39,12 +39,12 @@ export default defineConfig({ SkipLink: "./src/components/SkipLink.astro", }, social: { - github: "https://github.com/defenseunicorns/zarf", + github: "https://github.com/zarf-dev/zarf", slack: "https://kubernetes.slack.com/archives/C03B6BJAUJ3", }, favicon: "/favicon.svg", editLink: { - baseUrl: "https://github.com/defenseunicorns/zarf/edit/main/site", + baseUrl: "https://github.com/zarf-dev/zarf/edit/main/site", }, logo: { src: "./src/assets/zarf-logo-header.svg", diff --git a/site/hack/copy-examples.js b/site/hack/copy-examples.js index a3cda64641..77abf23104 100644 --- a/site/hack/copy-examples.js +++ b/site/hack/copy-examples.js @@ -25,7 +25,7 @@ async function copyExamples() { } const mdx = parsed.get("x-mdx").trim(); examples.push(dir); - const repo = "https://github.com/defenseunicorns/zarf"; + const repo = "https://github.com/zarf-dev/zarf"; const link = new URL(`${repo}/edit/main/examples/${dir}/zarf.yaml`).toString(); const fm = `--- title: "${dir}" diff --git a/site/public/architecture.drawio.svg b/site/public/architecture.drawio.svg index 4fa51a8850..aced8942e9 100644 --- a/site/public/architecture.drawio.svg +++ b/site/public/architecture.drawio.svg @@ -1,4 +1,4 @@ -
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
ns
ns
ns
ns
pod
pod
rs
rs
deploy
deploy
Zarf-Managed Deployments
Zarf-Managed Deployments
ns
ns
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222...
pod
pod
pvc
pvc
nodeport
svc
nodeport...
rs
rs
deploy
deploy
Zarf Registry 
Zarf Registry 
clusterip
svc
clusteri...
rs
rs
deploy
deploy
Zarf Agent
(Mutating Webhook)
Zarf Agent...
pod
pod
pvc
pvc
clusterip
svc
clusteri...
rs
rs
deploy
deploy
Zarf Gitops Service
Zarf Gitops Service
pod
pod
ns
ns
zarf-state
zarf-sta...
NodePort
31999
NodePort...
zarf
zarf
pv
pv
sc
sc
nodeport
svc
nodeport...
Zarf Injector
Zarf Injector
pod
from existing
image
pod...
Dynamic configmaps:
n = tarball size / 512 KB
Dynamic configmaps:...
Dynamic
NodePort

Dynamic...
pv
pv
sc
sc
Zarf Resource
Zarf Resource
Zarf Temporary Resource
Zarf Temporary Resource
Zarf-Managed Resource
Zarf-Managed Resource
Zarf CLI to Cluster Comms
Zarf CLI to Cluster Comms
Image Pull From Zarf Registry
Image Pull From Zarf Registry
Standard K8s Comms
Standard K8s Comms
Standard K8s Controller Comms
Standard K8s Controller Comms
Initial image pulled from zarf-injector nodeport
Initial image pulled from zarf-injector nodep...
Post registry boot all images pull from the registry
Post registry boot all images pull from the regi...
POD Create / Flux GitRepository Create Webhook
POD Create / Flux GitRepository Create Webhook
https://github.com/defenseunicorns/zarf
https://github.com/defenseunicorns/zarf
   1. Create the Zarf State in the cluster
1. Create the Zarf State in the clu...
   2. Launch the injector system
   2. Launch the injector system
   3. Deploy the Zarf Registry
   3. Deploy the Zarf Registry
   4. Deploy the Zarf Agent
   4. Deploy the Zarf Agent
   5. (Optional) Deploy the Zarf Git Server
5. (Optional) Deploy the Zarf Git S...
   6. Push package assets to the cluster
6. Push package assets to the clust...
Text is not SVG - cannot display
+
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3...
ns
ns
ns
ns
pod
pod
rs
rs
deploy
deploy
Zarf-Managed Deployments
Zarf-Managed Deployments
ns
ns
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222%22%20value%3D%22%22%20style%3D%22rounded%3D1%3BwhiteSpace%3Dwrap%3Bhtml%3D1%3BlabelBorderColor%3Dnone%3BfillColor%3D%23bac8d3%3BstrokeColor%3D%2323445d%3BgradientColor%3Dnone%3Bglass%3D0%3Bshadow%3D0%3Bsketch%3D0%3B%22%20vertex%3D%221%22%20parent%3D%221%22%3E%3CmxGeometry%20x%3D%22210%22%20y%3D%22-60%22%20width%3D%22770%22%20height%3D%22908%22%20as%3D%22geometry%22%2F%3E%3C%2FmxCell%3E%3C%2Froot%3E%3C%2FmxGraphModel%3E
%3CmxGraphModel%3E%3Croot%3E%3CmxCell%20id%3D%220%22%2F%3E%3CmxCell%20id%3D%221%22%20parent%3D%220%22%2F%3E%3CmxCell%20id%3D%222...
pod
pod
pvc
pvc
nodeport
svc
nodeport...
rs
rs
deploy
deploy
Zarf Registry 
Zarf Registry 
clusterip
svc
clusteri...
rs
rs
deploy
deploy
Zarf Agent
(Mutating Webhook)
Zarf Agent...
pod
pod
pvc
pvc
clusterip
svc
clusteri...
rs
rs
deploy
deploy
Zarf Gitops Service
Zarf Gitops Service
pod
pod
ns
ns
zarf-state
zarf-sta...
NodePort
31999
NodePort...
zarf
zarf
pv
pv
sc
sc
nodeport
svc
nodeport...
Zarf Injector
Zarf Injector
pod
from existing
image
pod...
Dynamic configmaps:
n = tarball size / 512 KB
Dynamic configmaps:...
Dynamic
NodePort

Dynamic...
pv
pv
sc
sc
Zarf Resource
Zarf Resource
Zarf Temporary Resource
Zarf Temporary Resource
Zarf-Managed Resource
Zarf-Managed Resource
Zarf CLI to Cluster Comms
Zarf CLI to Cluster Comms
Image Pull From Zarf Registry
Image Pull From Zarf Registry
Standard K8s Comms
Standard K8s Comms
Standard K8s Controller Comms
Standard K8s Controller Comms
Initial image pulled from zarf-injector nodeport
Initial image pulled from zarf-injector nodep...
Post registry boot all images pull from the registry
Post registry boot all images pull from the regi...
POD Create / Flux GitRepository Create Webhook
POD Create / Flux GitRepository Create Webhook
https://github.com/zarf-dev/zarf
https://github.com/zarf-dev/zarf
   1. Create the Zarf State in the cluster
1. Create the Zarf State in the clu...
   2. Launch the injector system
   2. Launch the injector system
   3. Deploy the Zarf Registry
   3. Deploy the Zarf Registry
   4. Deploy the Zarf Agent
   4. Deploy the Zarf Agent
   5. (Optional) Deploy the Zarf Git Server
5. (Optional) Deploy the Zarf Git S...
   6. Push package assets to the cluster
6. Push package assets to the clust...
Text is not SVG - cannot display
diff --git a/site/public/tutorials/package_create_init.html b/site/public/tutorials/package_create_init.html index 6a21effc08..9c8e80e906 100644 --- a/site/public/tutorials/package_create_init.html +++ b/site/public/tutorials/package_create_init.html @@ -158,7 +158,7 @@ - packages/zarf-agent/manifests/deployment.yaml - packages/zarf-agent/manifests/webhook.yaml images: - - ghcr.io/defenseunicorns/zarf/agent:local + - ghcr.io/zarf-dev/zarf/agent:local - name: git-server description: | Deploys Gitea to provide git repositories for Kubernetes configurations. diff --git a/site/public/tutorials/publish_and_deploy_manifest.html b/site/public/tutorials/publish_and_deploy_manifest.html index 74e8928a4a..19977b88e0 100644 --- a/site/public/tutorials/publish_and_deploy_manifest.html +++ b/site/public/tutorials/publish_and_deploy_manifest.html @@ -51,7 +51,7 @@ $ mkdir -p zarf-publish-tutorial && cd zarf-publish-tutorial # For this tutorial we will use the example package -# located here: https://github.com/defenseunicorns/zarf/blob/main/examples/helm-oci-chart/zarf.yaml +# located here: https://github.com/zarf-dev/zarf/blob/main/examples/helm-oci-chart/zarf.yaml $ cat <<EOF > zarf.yaml kind: ZarfPackageConfig metadata: diff --git a/site/public/tutorials/resource_adoption_deploy.html b/site/public/tutorials/resource_adoption_deploy.html index dac25e7967..814f4c541f 100644 --- a/site/public/tutorials/resource_adoption_deploy.html +++ b/site/public/tutorials/resource_adoption_deploy.html @@ -88,7 +88,7 @@ This package has 1 artifacts with software bill-of-materials (SBOM) included. You can view them now in the zarf-sbom folder in this directory or to go directly to one, open this in your browser: -/Users/jason/src/github.com/defenseunicorns/zarf/zarf-sbom/sbom-viewer-defenseunicorns_zarf-game_multi-tile-dark.html +/Users/jason/src/github.com/zarf-dev/zarf/zarf-sbom/sbom-viewer-defenseunicorns_zarf-game_multi-tile-dark.html * This directory will be removed after package deployment. diff --git a/site/public/tutorials/resource_adoption_package.html b/site/public/tutorials/resource_adoption_package.html index a252ad6632..5a4c744f2e 100644 --- a/site/public/tutorials/resource_adoption_package.html +++ b/site/public/tutorials/resource_adoption_package.html @@ -49,7 +49,7 @@
 $ zarf package create examples/dos-games
 
-Using config file /Users/jason/src/github.com/defenseunicorns/zarf/zarf-config.toml
+Using config file /Users/jason/src/github.com/zarf-dev/zarf/zarf-config.toml
 
 Saving log file to
 /var/folders/bk/rz1xx2sd5zn134c0_j1s2n5r0000gp/T/zarf-2023-05-10-11-24-25-3678510320.log
diff --git a/site/src/content/docs/contribute/nerd-notes.mdx b/site/src/content/docs/contribute/nerd-notes.mdx
index 6264dd5266..33dd73b35d 100644
--- a/site/src/content/docs/contribute/nerd-notes.mdx
+++ b/site/src/content/docs/contribute/nerd-notes.mdx
@@ -10,10 +10,10 @@ Zarf is written entirely in [go](https://go.dev/), except for a single 868Kb bin
 
 - All workloads are installed in the cluster via the [Helm SDK](https://helm.sh/docs/topics/advanced/#go-sdk)
 - The OCI Registries used are both from [Docker](https://github.com/distribution/distribution)
-- Currently, the Registry and Git servers _are not HA_, see [#375](https://github.com/defenseunicorns/zarf/issues/375) and [#376](https://github.com/defenseunicorns/zarf/issues/376) for discussion on this
+- Currently, the Registry and Git servers _are not HA_, see [#375](https://github.com/zarf-dev/zarf/issues/375) and [#376](https://github.com/zarf-dev/zarf/issues/376) for discussion on this
 - To avoid TLS issues, Zarf binds to `127.0.0.1:31999` on each node as a [NodePort](https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport) to allow all nodes to access the pod(s) in the cluster
-- Zarf utilizes a [mutating admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) called the [`zarf-agent`](https://github.com/defenseunicorns/zarf/tree/main/src/internal/agent) to modify the image property within the `PodSpec`. The purpose is to redirect it to Zarf's configured registry instead of the the original registry (such as DockerHub, GCR, or Quay). Additionally, the webhook attaches the appropriate [ImagePullSecret](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) for the seed registry to the pod. This configuration allows the pod to successfully retrieve the image from the seed registry, even when operating in an air-gapped environment.
-- Zarf uses a custom injector system to bootstrap a new cluster. See the PR [#329](https://github.com/defenseunicorns/zarf/pull/329) and [ADR](https://github.com/defenseunicorns/zarf/blob/main/adr/0003-image-injection-into-remote-clusters-without-native-support.md) for more details on how we came to this solution.  The general steps are listed below:
+- Zarf utilizes a [mutating admission webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) called the [`zarf-agent`](https://github.com/zarf-dev/zarf/tree/main/src/internal/agent) to modify the image property within the `PodSpec`. The purpose is to redirect it to Zarf's configured registry instead of the the original registry (such as DockerHub, GCR, or Quay). Additionally, the webhook attaches the appropriate [ImagePullSecret](https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod) for the seed registry to the pod. This configuration allows the pod to successfully retrieve the image from the seed registry, even when operating in an air-gapped environment.
+- Zarf uses a custom injector system to bootstrap a new cluster. See the PR [#329](https://github.com/zarf-dev/zarf/pull/329) and [ADR](https://github.com/zarf-dev/zarf/blob/main/adr/0003-image-injection-into-remote-clusters-without-native-support.md) for more details on how we came to this solution.  The general steps are listed below:
   - Get a list of images in the cluster
   - Attempt to create an ephemeral pod using an image from the list
   - A small rust binary that is compiled using [musl](https://www.musl-libc.org/) to keep the max binary size as minimal as possible
diff --git a/site/src/content/docs/contribute/testing.mdx b/site/src/content/docs/contribute/testing.mdx
index c64c643aa6..6805190229 100644
--- a/site/src/content/docs/contribute/testing.mdx
+++ b/site/src/content/docs/contribute/testing.mdx
@@ -2,7 +2,7 @@
 title: Running Tests
 ---
 
-Currently, we primarily test Zarf through a series of [end-to-end tests](https://github.com/defenseunicorns/zarf/tree/main/src/test/e2e). These tests are called in the `test-*.yml` workflows and undergo automatic execution against several K8s distros whenever a pull request is created or updated.
+Currently, we primarily test Zarf through a series of [end-to-end tests](https://github.com/zarf-dev/zarf/tree/main/src/test/e2e). These tests are called in the `test-*.yml` workflows and undergo automatic execution against several K8s distros whenever a pull request is created or updated.
 
 In addition, Zarf implements unit tests for specific functions where edge cases prove difficult to cover through end-to-end testing alone. Unit tests follow standard Go convention and are `*_test.go` files.
 
diff --git a/site/src/content/docs/faq.mdx b/site/src/content/docs/faq.mdx
index e4bca91bba..5f57435ea2 100644
--- a/site/src/content/docs/faq.mdx
+++ b/site/src/content/docs/faq.mdx
@@ -10,7 +10,7 @@ Defense Unicorns' mission is to advance freedom and independence globally throug
 
 ## What license is Zarf under?
 
-Zarf is under the [Apache License 2.0](https://github.com/defenseunicorns/zarf/blob/main/LICENSE). This is one of the most commonly used licenses for open-source software.
+Zarf is under the [Apache License 2.0](https://github.com/zarf-dev/zarf/blob/main/LICENSE). This is one of the most commonly used licenses for open-source software.
 
 ## Is Zarf free to use?
 
@@ -18,7 +18,7 @@ Yes! Zarf is Free and Open-Source Software (FOSS). And will remain free forever.
 
 ## Do I have to use Homebrew to install Zarf?
 
-No, the Zarf binary and init package can be downloaded from the [Releases Page](https://github.com/defenseunicorns/zarf/releases). Zarf does not need to be installed or available to all users on the system, but it does need to be executable for the current user (i.e. `chmod +x zarf` for Linux/Mac).
+No, the Zarf binary and init package can be downloaded from the [Releases Page](https://github.com/zarf-dev/zarf/releases). Zarf does not need to be installed or available to all users on the system, but it does need to be executable for the current user (i.e. `chmod +x zarf` for Linux/Mac).
 
 ## What dependencies does Zarf have?
 
@@ -26,7 +26,7 @@ Zarf is statically compiled and written in [Go](https://golang.org/) and [Rust](
 
 ## How can I improve the speed of loading large images from Docker on `zarf package create`?
 
-Due to some limitations with how Docker provides access to local image layers, `zarf package create` has to rely on `docker save` under the hood which is [very slow overall](https://github.com/defenseunicorns/zarf/issues/1214) and also takes a long time to report progress. We experimented with many ways to improve this, but for now recommend leveraging a local docker registry to speed up the process.
+Due to some limitations with how Docker provides access to local image layers, `zarf package create` has to rely on `docker save` under the hood which is [very slow overall](https://github.com/zarf-dev/zarf/issues/1214) and also takes a long time to report progress. We experimented with many ways to improve this, but for now recommend leveraging a local docker registry to speed up the process.
 
 This can be done by running a local registry and pushing the images to it before running `zarf package create`. This will allow `zarf package create` to pull the images from the local registry instead of Docker. This can also be combined with [component actions](/ref/actions/) and [`--registry-override`](/commands/zarf_package_create/) to make the process automatic. Given an example image of `registry.enterprise.corp/my-giant-image:v2` you could do something like this:
 
@@ -74,8 +74,8 @@ metadata:
 
 components:
   repos:
-    - https://github.com/defenseunicorns/zarf.git
-    - ssh://git@github.com/defenseunicorns/zarf.git
+    - https://github.com/zarf-dev/zarf.git
+    - ssh://git@github.com/zarf-dev/zarf.git
     - file:///home/zarf/workspace/zarf
     - git://somegithost.com/zarf.git
 ```
diff --git a/site/src/content/docs/getting-started/install.mdx b/site/src/content/docs/getting-started/install.mdx
index 7c115d8cfb..c92b7ba0ef 100644
--- a/site/src/content/docs/getting-started/install.mdx
+++ b/site/src/content/docs/getting-started/install.mdx
@@ -14,12 +14,12 @@ brew tap defenseunicorns/tap && brew install zarf
 
 ## GitHub Releases
 
-All [Zarf releases](https://github.com/defenseunicorns/zarf/releases) on GitHub include prebuilt binaries that you can download and use. We offer range of combinations of OS and architecture for you to choose from.
+All [Zarf releases](https://github.com/zarf-dev/zarf/releases) on GitHub include prebuilt binaries that you can download and use. We offer range of combinations of OS and architecture for you to choose from.
 
 export const downloadScript = (os, arch) => `
-ZARF_VERSION=$(curl -sIX HEAD https://github.com/defenseunicorns/zarf/releases/latest | grep -i ^location: | grep -Eo 'v[0-9]+.[0-9]+.[0-9]+')
+ZARF_VERSION=$(curl -sIX HEAD https://github.com/zarf-dev/zarf/releases/latest | grep -i ^location: | grep -Eo 'v[0-9]+.[0-9]+.[0-9]+')
 
-curl -sL "https://github.com/defenseunicorns/zarf/releases/download/\${ZARF_VERSION}/zarf_\${ZARF_VERSION}_${os}_${arch}" -o zarf
+curl -sL "https://github.com/zarf-dev/zarf/releases/download/\${ZARF_VERSION}/zarf_\${ZARF_VERSION}_${os}_${arch}" -o zarf
 chmod +x zarf
 `
 
@@ -66,9 +66,9 @@ sudo mv zarf /usr/local/bin/zarf
 To download Zarf on Windows you can run the following (replacing `$ZarfVersion` with any release version of Zarf):
 
 export const downloadPowerShellScript = (arch) => `
-$ZarfVersion = (Invoke-RestMethod https://api.github.com/repos/defenseunicorns/zarf/releases/latest).tag_name
+$ZarfVersion = (Invoke-RestMethod https://api.github.com/repos/zarf-dev/zarf/releases/latest).tag_name
 
-Start-BitsTransfer -Source "https://github.com/defenseunicorns/zarf/releases/download/$($ZarfVersion)/zarf_$($ZarfVersion)_Windows_${arch}.exe" -Destination zarf.exe
+Start-BitsTransfer -Source "https://github.com/zarf-dev/zarf/releases/download/$($ZarfVersion)/zarf_$($ZarfVersion)_Windows_${arch}.exe" -Destination zarf.exe
 `
 
 
@@ -96,7 +96,7 @@ The following are unofficial methods of installing Zarf that are maintained by t
 If you want to build the CLI from scratch, you can do that too. Our local builds depend on [Go](https://golang.org/doc/install) and [make](https://www.gnu.org/software/make/).
 
 ```bash
-git clone https://github.com/defenseunicorns/zarf
+git clone https://github.com/zarf-dev/zarf
 cd zarf
 # build the CLI for your current OS and architecture
 make
@@ -134,7 +134,7 @@ $ zarf tools download-init
 $ zarf init --confirm
 ```
 
-The default 'init' package can also be obtained by visiting the [Zarf releases](https://github.com/defenseunicorns/zarf/releases) page and downloading it into your working directory or into `~/.zarf-cache/zarf-init--vX.X.X.tar.zst`.
+The default 'init' package can also be obtained by visiting the [Zarf releases](https://github.com/zarf-dev/zarf/releases) page and downloading it into your working directory or into `~/.zarf-cache/zarf-init--vX.X.X.tar.zst`.
 
 :::tip
 
diff --git a/site/src/content/docs/index.mdx b/site/src/content/docs/index.mdx
index 48ac086e1c..abf11bc393 100644
--- a/site/src/content/docs/index.mdx
+++ b/site/src/content/docs/index.mdx
@@ -45,7 +45,7 @@ Zarf provides a way to package and deploy software in a way that is **repeatable
 - Builtin Git server with [Gitea](https://gitea.com/)
 - Builtin Docker registry
 - Builtin [K9s Dashboard](https://k9scli.io/) for managing a cluster from the terminal
-- [Mutating Webhook](https://github.com/defenseunicorns/zarf/blob/main/adr/0005-mutating-webhook.md) to automatically update Kubernetes pod's image path and pull secrets as well as [Flux Git Repository](https://fluxcd.io/docs/components/source/gitrepositories/) URLs and secret references
+- [Mutating Webhook](https://github.com/zarf-dev/zarf/blob/main/adr/0005-mutating-webhook.md) to automatically update Kubernetes pod's image path and pull secrets as well as [Flux Git Repository](https://fluxcd.io/docs/components/source/gitrepositories/) URLs and secret references
 - Builtin [command to find images](/commands/zarf_dev_find-images/) and resources from a Helm chart
 - Tunneling capability to [connect to Kubernetes resources](/commands/zarf_connect/) without network routing, DNS, TLS or Ingress configuration required
 
diff --git a/site/src/content/docs/ref/components.mdx b/site/src/content/docs/ref/components.mdx
index b8f85d6fc6..50be327f1b 100644
--- a/site/src/content/docs/ref/components.mdx
+++ b/site/src/content/docs/ref/components.mdx
@@ -162,19 +162,19 @@ The [`podinfo-flux`](/ref/examples/podinfo-flux/) example showcases a simple Git
 
 #### Tag-Based Git Repository Clone
 
-Tag-based `git` repository cloning is the **recommended** way of cloning a `git` repository for air-gapped deployments because it wraps meaning around a specific point in git history that can easily be traced back to the online world. Tag-based clones are defined using the `scheme://host/repo@tag` format as seen in the example of the `defenseunicorns/zarf` repository (`https://github.com/defenseunicorns/zarf.git@v0.15.0`).
+Tag-based `git` repository cloning is the **recommended** way of cloning a `git` repository for air-gapped deployments because it wraps meaning around a specific point in git history that can easily be traced back to the online world. Tag-based clones are defined using the `scheme://host/repo@tag` format as seen in the example of the `zarf-dev/zarf` repository (`https://github.com/zarf-dev/zarf.git@v0.15.0`).
 
 A tag-based clone only mirrors the tag defined in the Zarf definition. The tag will be applied on the `git` mirror to a zarf-specific branch name based on the tag name (e.g. the tag `v0.1.0` will be pushed to the `zarf-ref-v0.1.0` branch).  This ensures that this tag will be pushed and received properly by the airgap `git` server.
 
 :::note
 
-If you would like to use a protocol scheme other than http/https, you can do so with something like the following: `ssh://git@github.com/defenseunicorns/zarf.git@v0.15.0`.  Using this you can also clone from a local repo to help you manage larger git repositories: `file:///home/zarf/workspace/zarf@v0.15.0`.
+If you would like to use a protocol scheme other than http/https, you can do so with something like the following: `ssh://git@github.com/zarf-dev/zarf.git@v0.15.0`.  Using this you can also clone from a local repo to help you manage larger git repositories: `file:///home/zarf/workspace/zarf@v0.15.0`.
 
 :::
 
 :::caution
 
-Because Zarf creates long-lived mirrors of repositories in the air gap, it does not support shallow clones (i.e. `git clone --depth x`).  These may be present in build environments (i.e. [GitLab runners](https://github.com/defenseunicorns/zarf/issues/1698)) and should be avoided.  To learn more about shallow and partial clones see the [GitHub blog on the topic](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone).
+Because Zarf creates long-lived mirrors of repositories in the air gap, it does not support shallow clones (i.e. `git clone --depth x`).  These may be present in build environments (i.e. [GitLab runners](https://github.com/zarf-dev/zarf/issues/1698)) and should be avoided.  To learn more about shallow and partial clones see the [GitHub blog on the topic](https://github.blog/2020-12-21-get-up-to-speed-with-partial-clone-and-shallow-clone).
 
 :::
 
@@ -182,7 +182,7 @@ Because Zarf creates long-lived mirrors of repositories in the air gap, it does
 
 #### SHA-Based Git Repository Clone
 
-In addition to tags, Zarf also supports cloning and pushing a specific SHA hash from a `git` repository, but this is **not recommended** as it is less readable/understandable than tag cloning.  Commit SHAs are defined using the same `scheme://host/repo@shasum` format as seen in the example of the `defenseunicorns/zarf` repository (`https://github.com/defenseunicorns/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e`).
+In addition to tags, Zarf also supports cloning and pushing a specific SHA hash from a `git` repository, but this is **not recommended** as it is less readable/understandable than tag cloning.  Commit SHAs are defined using the same `scheme://host/repo@shasum` format as seen in the example of the `zarf-dev/zarf` repository (`https://github.com/zarf-dev/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e`).
 
 A SHA-based clone only mirrors the SHA hash defined in the Zarf definition. The SHA will be applied on the `git` mirror to a zarf-specific branch name based on the SHA hash (e.g. the SHA `c74e2e9626da0400e0a41e78319b3054c53a5d4e` will be pushed to the `zarf-ref-c74e2e9626da0400e0a41e78319b3054c53a5d4e` branch).  This ensures that this tag will be pushed and received properly by the airgap `git` server.
 
diff --git a/site/src/content/docs/ref/dev.mdx b/site/src/content/docs/ref/dev.mdx
index 10fdf34a31..bdf0fe50f7 100644
--- a/site/src/content/docs/ref/dev.mdx
+++ b/site/src/content/docs/ref/dev.mdx
@@ -8,7 +8,7 @@ tableOfContents:
 
 ## Schema Validation
 
-Zarf uses the [Zarf package schema](https://github.com/defenseunicorns/zarf/blob/main/zarf.schema.json) to define its configuration files. This schema is used to describe package configuration options and enable the validation of configuration files prior to their use in building a Zarf Package.
+Zarf uses the [Zarf package schema](https://github.com/zarf-dev/zarf/blob/main/zarf.schema.json) to define its configuration files. This schema is used to describe package configuration options and enable the validation of configuration files prior to their use in building a Zarf Package.
 
 ### `zarf dev lint`
 
@@ -28,7 +28,7 @@ zarf dev lint 
 
 ```json
   "yaml.schemas": {
-    "https://raw.githubusercontent.com/defenseunicorns/zarf/main/zarf.schema.json": "zarf.yaml"
+    "https://raw.githubusercontent.com/zarf-dev/zarf/main/zarf.schema.json": "zarf.yaml"
   }
 ```
 
@@ -43,10 +43,10 @@ When successfully installed, the `yaml.schema` line will match the color of the
 To ensure consistent validation of the Zarf schema version in a `zarf.yaml` file, it can be beneficial to lock it to a specific version. This can be achieved by appending the following statement to the **first line** of any given `zarf.yaml` file:
 
 ```yaml
-# yaml-language-server: $schema=https://raw.githubusercontent.com/defenseunicorns/zarf//zarf.schema.json
+# yaml-language-server: $schema=https://raw.githubusercontent.com/zarf-dev/zarf//zarf.schema.json
 ```
 
-In the above example, `` should be replaced with the specific [Zarf release](https://github.com/defenseunicorns/zarf/releases).
+In the above example, `` should be replaced with the specific [Zarf release](https://github.com/zarf-dev/zarf/releases).
 
 ![yaml schema](https://user-images.githubusercontent.com/92826525/226490465-1e6a56f7-41c4-45bf-923b-5242fa4ab64e.png)
 
diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx
index 8a8daa55de..1af8d3d18e 100644
--- a/site/src/content/docs/ref/init-package.mdx
+++ b/site/src/content/docs/ref/init-package.mdx
@@ -51,7 +51,7 @@ While there is no distro-agnostic method to inject images into a cluster, every
 
 But then we have another problem of how to reassemble the image on the other side, as we don't have any consistent image that exists in the cluster that would have such utilities. This is where the `zarf-injector` Rust binary comes in.
 
-> For compiling the `zarf-injector` binary, refer to its [README.md](https://github.com/defenseunicorns/zarf/tree/main/src/injector/README.md).
+> For compiling the `zarf-injector` binary, refer to its [README.md](https://github.com/zarf-dev/zarf/tree/main/src/injector/README.md).
 
 The `zarf-injector` binary is statically compiled and injected into the cluster as a `configmap` along with the chunks of the `registry:2` image. During the `zarf-seed-registry`'s deployment, the `zarf-injector` binary is run in a pod that mounts the `configmaps` and reassembles the `registry:2` image. It then hosts a temporary, pull-only Docker registry implemented in Rust so that a real registry can be deployed into the cluster from the hosted `registry:2` image.
 
@@ -76,7 +76,7 @@ Doing this keeps Zarf cluster agnostic, however does require that the kubelet be
 
 :::note
 
-The `registry:2` image and the Zarf Agent image can be configured with a custom init package using the `registry_image_*` and `agent_image_*` templates defined in the Zarf repo's [zarf-config.toml](https://github.com/defenseunicorns/zarf/blob/main/zarf-config.toml).  This allows you to swap them for enterprise provided / hardened versions if desired such as those provided by [Iron Bank](https://repo1.dso.mil/dsop/opensource/defenseunicorns/zarf/zarf-agent).
+The `registry:2` image and the Zarf Agent image can be configured with a custom init package using the `registry_image_*` and `agent_image_*` templates defined in the Zarf repo's [zarf-config.toml](https://github.com/zarf-dev/zarf/blob/main/zarf-config.toml).  This allows you to swap them for enterprise provided / hardened versions if desired such as those provided by [Iron Bank](https://repo1.dso.mil/dsop/opensource/defenseunicorns/zarf/zarf-agent).
 
 :::
 
@@ -95,7 +95,7 @@ It leverages the same `docker-registry` chart used in `zarf-seed-registry` but w
 
 You can further customize how the registry behaves by setting variables such as `REGISTRY_PVC_SIZE` with a [config file](/ref/config-files/) or `--set` on `zarf init`.
 
-To see a full list of `variables` you can view the [`zarf.yaml` that defines the registry](https://github.com/defenseunicorns/zarf/blob/main/packages/zarf-registry/zarf.yaml).
+To see a full list of `variables` you can view the [`zarf.yaml` that defines the registry](https://github.com/zarf-dev/zarf/blob/main/packages/zarf-registry/zarf.yaml).
 
 :::
 
@@ -216,7 +216,7 @@ root@machine ~ # zarf init --components k3s --set K3S_ARGS="" --confirm
 
 You can further customize how the git-server behaves by setting variables such as `GIT_SERVER_PVC_SIZE` with a [config file](/ref/config-files/) or `--set` on `zarf init`.
 
-To see a full list of `variables` you can view the [zarf.yaml that defines the git-server](https://github.com/defenseunicorns/zarf/blob/main/packages/gitea/zarf.yaml).
+To see a full list of `variables` you can view the [zarf.yaml that defines the git-server](https://github.com/zarf-dev/zarf/blob/main/packages/gitea/zarf.yaml).
 
 :::
 
@@ -273,7 +273,7 @@ components:
 In order to reproduce / build the following example, you will need to have the Zarf repository cloned locally.
 
 ```bash
-git clone https://github.com/defenseunicorns/zarf.git
+git clone https://github.com/zarf-dev/zarf.git
 cd zarf
 mv zarf.yaml zarf.yaml.bak
 ```
diff --git a/site/src/content/docs/ref/packages.mdx b/site/src/content/docs/ref/packages.mdx
index 723c6ed845..5a2e3562ad 100644
--- a/site/src/content/docs/ref/packages.mdx
+++ b/site/src/content/docs/ref/packages.mdx
@@ -28,7 +28,7 @@ Typically, an init package is the first Zarf Package to be deployed on a cluster
 
 :::tip
 
-Check out our [K3s cluster package](https://github.com/defenseunicorns/zarf/blob/main/packages/distros/k3s/zarf.yaml) to see an example of a Zarf Package that installs a Kubernetes distribution
+Check out our [K3s cluster package](https://github.com/zarf-dev/zarf/blob/main/packages/distros/k3s/zarf.yaml) to see an example of a Zarf Package that installs a Kubernetes distribution
 
 :::
 
diff --git a/site/src/content/docs/roadmap.mdx b/site/src/content/docs/roadmap.mdx
index 68878e6dbb..43a80e41ad 100644
--- a/site/src/content/docs/roadmap.mdx
+++ b/site/src/content/docs/roadmap.mdx
@@ -6,15 +6,15 @@ title: Roadmap
 
 The issue board for Zarf is hosted on a [GitHub Project Board](https://github.com/orgs/defenseunicorns/projects/1) that tracks the issues the Zarf team is working along with future work we are prioritizing.
 
-If you would like to add bug reports or feature requests, please [add an issue](https://github.com/defenseunicorns/zarf/issues) to the GitHub repository under the appropriate template. If you have a more general question about a feature, feel free to ask the team in the [Zarf Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03B6BJAUJ3).
+If you would like to add bug reports or feature requests, please [add an issue](https://github.com/zarf-dev/zarf/issues) to the GitHub repository under the appropriate template. If you have a more general question about a feature, feel free to ask the team in the [Zarf Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03B6BJAUJ3).
 
-We also accept contributions from the community (regardless of where a particular bug or feature is in the queue), so feel free to read our [contributing guidelines](/contribute/contributor-guide) and [submit a PR](https://github.com/defenseunicorns/zarf/pulls)!  You can also ask any development related questions in the [Zarf Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03BP9Z3CMA).
+We also accept contributions from the community (regardless of where a particular bug or feature is in the queue), so feel free to read our [contributing guidelines](/contribute/contributor-guide) and [submit a PR](https://github.com/zarf-dev/zarf/pulls)!  You can also ask any development related questions in the [Zarf Dev Kubernetes Slack Channel](https://kubernetes.slack.com/archives/C03BP9Z3CMA).
 
 ## 2024 General Roadmap
 
 ### Q1: Community Building and Refactoring
 
-- [X] - Establish a [monthly community meetup](https://github.com/defenseunicorns/zarf/issues/2202) to engage members of the community and answer questions.
+- [X] - Establish a [monthly community meetup](https://github.com/zarf-dev/zarf/issues/2202) to engage members of the community and answer questions.
 - [ ] - Refactor and add tests to library code shared with [UDS-CLI](https://github.com/defenseunicorns/uds-cli) and split into a new GitHub repository.
 - [ ] - Gather OpenSSF donation requirements and clear off pre-reqs (additional maintainers and sponsor working group).
 
@@ -81,6 +81,6 @@ Deprecated features are features that are no longer recommended for use and:
 
 ## General Availability (GA) Release
 
-Right now, Zarf itself is still in its 'beta' phase. We are working on some final things before we release the official 1.0 General Availability (GA) release. The work still needed for the GA release can be found in our issues with [this filter](https://github.com/defenseunicorns/zarf/issues?q=is%3Aopen+is%3Aissue+label%3Aga).
+Right now, Zarf itself is still in its 'beta' phase. We are working on some final things before we release the official 1.0 General Availability (GA) release. The work still needed for the GA release can be found in our issues with [this filter](https://github.com/zarf-dev/zarf/issues?q=is%3Aopen+is%3Aissue+label%3Aga).
 
 We are currently targeting Q4 2024 to have Zarf be generally available and will be pushing weekly releases until then to add necessary features and fix bugs as well as improve docs, architecture and test coverage behind the scenes.
diff --git a/site/src/content/docs/support.mdx b/site/src/content/docs/support.mdx
index 2cf650b416..b3d8aa75f1 100644
--- a/site/src/content/docs/support.mdx
+++ b/site/src/content/docs/support.mdx
@@ -8,4 +8,4 @@ title: Support
    - [Getting Started](/getting-started)
 2. Look for an answer in the [Frequently Asked Questions](/faq).
 3. Ask a question in [the Zarf Slack Channel](https://kubernetes.slack.com/archives/C03B6BJAUJ3)
-4. [Read issues, report a bug, or request a new feature](https://github.com/defenseunicorns/zarf/issues)
+4. [Read issues, report a bug, or request a new feature](https://github.com/zarf-dev/zarf/issues)
diff --git a/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx b/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx
index 84634b4386..4300d8c72f 100644
--- a/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx
+++ b/site/src/content/docs/tutorials/0-creating-a-zarf-package.mdx
@@ -27,7 +27,7 @@ In order to create a Zarf package you first need to have an idea of what applica
 
 ### Creating the Package Definition
 
-A `zarf.yaml` file follows the [Zarf Package Schema](https://github.com/defenseunicorns/zarf/blob/main/zarf.schema.json) and allows us to specify package metadata and a set of components for us to deploy. We start a package definition with the `kind` of package we are making and `metadata` that describes the package.  You can start our WordPress package by creating a new `zarf.yaml` with the following content:
+A `zarf.yaml` file follows the [Zarf Package Schema](https://github.com/zarf-dev/zarf/blob/main/zarf.schema.json) and allows us to specify package metadata and a set of components for us to deploy. We start a package definition with the `kind` of package we are making and `metadata` that describes the package.  You can start our WordPress package by creating a new `zarf.yaml` with the following content:
 
 ```yaml
 kind: ZarfPackageConfig # ZarfPackageConfig is the package kind for most normal zarf packages
@@ -40,7 +40,7 @@ metadata:
 
 :::tip
 
-You can run [`zarf dev lint `](/commands/zarf_dev_lint/) to validate against the [`zarf.schema.json`](https://github.com/defenseunicorns/zarf/blob/main/zarf.schema.json), or setup [VSCode](/ref/dev/#vscode) to see errors in real-time.
+You can run [`zarf dev lint `](/commands/zarf_dev_lint/) to validate against the [`zarf.schema.json`](https://github.com/zarf-dev/zarf/blob/main/zarf.schema.json), or setup [VSCode](/ref/dev/#vscode) to see errors in real-time.
 
 :::
 
diff --git a/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx b/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx
index 006d3a5743..deb26aa0ce 100644
--- a/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx
+++ b/site/src/content/docs/tutorials/1-initializing-a-k8s-cluster.mdx
@@ -12,9 +12,9 @@ In this tutorial, we will demonstrate how to initialize Zarf onto a K8s cluster.
 
 Before beginning this tutorial you will need the following:
 
-- The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+- The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 - Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install/))
-- An init-package downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package/)) or ([Download Location](https://github.com/defenseunicorns/zarf/releases))
+- An init-package downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package/)) or ([Download Location](https://github.com/zarf-dev/zarf/releases))
 - A local Kubernetes cluster
 
 ## Initializing the Cluster
diff --git a/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx b/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx
index 9364e98bbe..abfcd55b42 100644
--- a/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx
+++ b/site/src/content/docs/tutorials/3-deploy-a-retro-arcade.mdx
@@ -16,7 +16,7 @@ In previous tutorials, we learned how to [create a package](/tutorials/0-creatin
 
 Before beginning this tutorial you will need the following:
 
-- The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+- The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 - Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install/))
 - [An initialized cluster](/tutorials/1-initializing-a-k8s-cluster/)
 
diff --git a/site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx b/site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx
index 81492468d7..2ca6056dbf 100644
--- a/site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx
+++ b/site/src/content/docs/tutorials/4-creating-a-k8s-cluster-with-zarf.mdx
@@ -20,9 +20,9 @@ The 'k3s' component requires root access (not just `sudo`!) when deploying as it
 
 Before beginning this tutorial you will need the following:
 
-- The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+- The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 - Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install/))
-- An init-package built/downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package/)) or ([Download Location](https://github.com/defenseunicorns/zarf/releases))
+- An init-package built/downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package/)) or ([Download Location](https://github.com/zarf-dev/zarf/releases))
 
 ## Creating the Cluster
 
diff --git a/site/src/content/docs/tutorials/5-big-bang.mdx b/site/src/content/docs/tutorials/5-big-bang.mdx
index b239f73ea3..bf073df59a 100644
--- a/site/src/content/docs/tutorials/5-big-bang.mdx
+++ b/site/src/content/docs/tutorials/5-big-bang.mdx
@@ -36,7 +36,7 @@ To learn more about Big Bang's requirements in general, see their documentation:
 Before beginning this tutorial you will need the following:
 
 - A local copy of the Zarf repository
-  - `git clone https://github.com/defenseunicorns/zarf.git`
+  - `git clone https://github.com/zarf-dev/zarf.git`
 - A kubernetes cluster onto which you can deploy Zarf and Big Bang
 - The latest version of the Zarf `cli`
   - Follow instructions on https://docs.zarf.dev/getting-started/install/
@@ -208,4 +208,4 @@ See the Troubleshooting section of the Big Bang Quick Start for help troubleshoo
 
 Also, ensure that you have followed all of the steps required in the [pre-requisites](#prerequisites) section.
 
-If you feel that the error you are encountering is one with Zarf feel free to [open an issue](https://github.com/defenseunicorns/zarf/issues/new/choose) or reach out via [slack](https://kubernetes.slack.com/archives/C03B6BJAUJ3).
+If you feel that the error you are encountering is one with Zarf feel free to [open an issue](https://github.com/zarf-dev/zarf/issues/new/choose) or reach out via [slack](https://kubernetes.slack.com/archives/C03B6BJAUJ3).
diff --git a/site/src/content/docs/tutorials/7-custom-init-packages.mdx b/site/src/content/docs/tutorials/7-custom-init-packages.mdx
index e99c8f08b6..1f1e78cad9 100644
--- a/site/src/content/docs/tutorials/7-custom-init-packages.mdx
+++ b/site/src/content/docs/tutorials/7-custom-init-packages.mdx
@@ -20,7 +20,7 @@ When creating a Zarf 'init' package, you must have a network connection so that
 
 Before beginning this tutorial you will need the following:
 
-- The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+- The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 - Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install/))
 - (if building a local [`zarf-agent`](/faq#what-is-the-zarf-agent)) The [Docker CLI](https://docs.docker.com/desktop/) installed and the tools to [Build your own CLI](/getting-started/install/#building-from-source)
 
diff --git a/site/src/content/docs/tutorials/9-package-create-differential.mdx b/site/src/content/docs/tutorials/9-package-create-differential.mdx
index 176424805c..56207fab42 100644
--- a/site/src/content/docs/tutorials/9-package-create-differential.mdx
+++ b/site/src/content/docs/tutorials/9-package-create-differential.mdx
@@ -18,7 +18,7 @@ In this tutorial, you will create a differential package using Zarf.  This is us
 For following along locally, please ensure the following prerequisites are met:
 
 - Zarf binary installed on your `$PATH`: ([Installing Zarf](/getting-started/install/))
-- The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+- The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([`git clone` Instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 
 ## Create a Differential Package
 
diff --git a/site/src/content/docs/tutorials/index.mdx b/site/src/content/docs/tutorials/index.mdx
index 9c0b43ef6d..c8db1f80e5 100644
--- a/site/src/content/docs/tutorials/index.mdx
+++ b/site/src/content/docs/tutorials/index.mdx
@@ -11,9 +11,9 @@ This section of the documentation has a collection of tutorials that will help y
 If a tutorial has any prerequisites, they will be listed at the beginning of the tutorial with instructions on how to fulfill them.
 Almost all tutorials will have the following prerequisites/assumptions:
 
-1. The [Zarf](https://github.com/defenseunicorns/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
+1. The [Zarf](https://github.com/zarf-dev/zarf) repository cloned: ([git clone instructions](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository))
 1. You have a Zarf binary installed on your $PATH: ([Installing Zarf](/getting-started/install))
-1. You have an init-package built/downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package)) or ([Download Location](https://github.com/defenseunicorns/zarf/releases))
+1. You have an init-package built/downloaded: ([init-package Build Instructions](/tutorials/0-creating-a-zarf-package)) or ([Download Location](https://github.com/zarf-dev/zarf/releases))
 1. Have a kubernetes cluster running/available (ex. [k3s](https://k3s.io/)/[k3d](https://k3d.io/v5.4.1/)/[Kind](https://kind.sigs.k8s.io/docs/user/quick-start#installation))
 
 ## Setting Up a Local Kubernetes Cluster
diff --git a/src/cmd/common/setup.go b/src/cmd/common/setup.go
index 6fab6e134b..703d9355bf 100644
--- a/src/cmd/common/setup.go
+++ b/src/cmd/common/setup.go
@@ -10,10 +10,10 @@ import (
 	"os"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // LogLevelCLI holds the log level as input from a command
diff --git a/src/cmd/common/vendor.go b/src/cmd/common/vendor.go
index 5f10726204..a8ff1db5ad 100644
--- a/src/cmd/common/vendor.go
+++ b/src/cmd/common/vendor.go
@@ -10,8 +10,8 @@ import (
 
 	"slices"
 
-	"github.com/defenseunicorns/zarf/src/config"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/config"
 )
 
 var vendorCmds = []string{
diff --git a/src/cmd/common/viper.go b/src/cmd/common/viper.go
index b852800611..bbd0f64c0c 100644
--- a/src/cmd/common/viper.go
+++ b/src/cmd/common/viper.go
@@ -8,10 +8,10 @@ import (
 	"os"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/spf13/viper"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // Constants for use when loading configurations from viper config files
diff --git a/src/cmd/connect.go b/src/cmd/connect.go
index 89eb341b45..26f012660e 100644
--- a/src/cmd/connect.go
+++ b/src/cmd/connect.go
@@ -9,10 +9,10 @@ import (
 
 	"github.com/spf13/cobra"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 var (
diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go
index 0faa1d5c3e..a6c45519b0 100644
--- a/src/cmd/destroy.go
+++ b/src/cmd/destroy.go
@@ -12,12 +12,12 @@ import (
 	"regexp"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 
 	"github.com/spf13/cobra"
 )
diff --git a/src/cmd/dev.go b/src/cmd/dev.go
index e304ca2edf..c31e0d5fe8 100644
--- a/src/cmd/dev.go
+++ b/src/cmd/dev.go
@@ -14,19 +14,19 @@ import (
 
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
 	"github.com/pterm/pterm"
 	"github.com/sergi/go-diff/diffmatchpatch"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var extractPath string
diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go
index 2262454866..4d1c61363b 100644
--- a/src/cmd/initialize.go
+++ b/src/cmd/initialize.go
@@ -16,15 +16,15 @@ import (
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 
 	"github.com/spf13/cobra"
 )
diff --git a/src/cmd/internal.go b/src/cmd/internal.go
index b385564151..7ff0315356 100644
--- a/src/cmd/internal.go
+++ b/src/cmd/internal.go
@@ -13,17 +13,17 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent"
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/invopop/jsonschema"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra/doc"
 	"github.com/spf13/pflag"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/cmd/package.go b/src/cmd/package.go
index fa939e10bb..8b1405e25c 100644
--- a/src/cmd/package.go
+++ b/src/cmd/package.go
@@ -12,21 +12,21 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/types"
 
 	"oras.land/oras-go/v2/registry"
 
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/packager"
 	"github.com/spf13/cobra"
 	"github.com/spf13/viper"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/packager"
 )
 
 var packageCmd = &cobra.Command{
diff --git a/src/cmd/root.go b/src/cmd/root.go
index ee40254fe0..086a1cab6c 100644
--- a/src/cmd/root.go
+++ b/src/cmd/root.go
@@ -14,13 +14,13 @@ import (
 	"github.com/pterm/pterm"
 	"github.com/spf13/cobra"
 
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/cmd/tools"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/cmd/tools"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/cmd/tools/archiver.go b/src/cmd/tools/archiver.go
index d62ce32785..e3bf2f2629 100644
--- a/src/cmd/tools/archiver.go
+++ b/src/cmd/tools/archiver.go
@@ -10,13 +10,13 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
 	"github.com/mholt/archiver/v3"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
 )
 
-// ldflags github.com/defenseunicorns/zarf/src/cmd/tools.archiverVersion=x.x.x
+// ldflags github.com/zarf-dev/zarf/src/cmd/tools.archiverVersion=x.x.x
 var archiverVersion string
 
 var archiverCmd = &cobra.Command{
diff --git a/src/cmd/tools/common.go b/src/cmd/tools/common.go
index 0844df9801..7f8f375be0 100644
--- a/src/cmd/tools/common.go
+++ b/src/cmd/tools/common.go
@@ -7,10 +7,10 @@ package tools
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 var toolsCmd = &cobra.Command{
diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go
index ea8924be61..4df8daebb3 100644
--- a/src/cmd/tools/crane.go
+++ b/src/cmd/tools/crane.go
@@ -11,18 +11,18 @@ import (
 	"strings"
 
 	"github.com/AlecAivazis/survey/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/images"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
 	craneCmd "github.com/google/go-containerregistry/cmd/crane/cmd"
 	"github.com/google/go-containerregistry/pkg/crane"
 	"github.com/google/go-containerregistry/pkg/logs"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/images"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func init() {
diff --git a/src/cmd/tools/helm.go b/src/cmd/tools/helm.go
index dd2bf30d76..0d37f8d017 100644
--- a/src/cmd/tools/helm.go
+++ b/src/cmd/tools/helm.go
@@ -7,12 +7,12 @@ package tools
 import (
 	"os"
 
-	"github.com/defenseunicorns/zarf/src/cmd/tools/helm"
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/cmd/tools/helm"
+	"github.com/zarf-dev/zarf/src/config/lang"
 	"helm.sh/helm/v3/pkg/action"
 )
 
-// ldflags github.com/defenseunicorns/zarf/src/cmd/tools.helmVersion=x.x.x
+// ldflags github.com/zarf-dev/zarf/src/cmd/tools.helmVersion=x.x.x
 var helmVersion string
 
 func init() {
diff --git a/src/cmd/tools/helm/repo_add.go b/src/cmd/tools/helm/repo_add.go
index de695bff5c..1d73a05467 100644
--- a/src/cmd/tools/helm/repo_add.go
+++ b/src/cmd/tools/helm/repo_add.go
@@ -31,10 +31,10 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
 	"github.com/gofrs/flock"
 	"github.com/pkg/errors"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
 	"golang.org/x/term"
 	"sigs.k8s.io/yaml"
 
diff --git a/src/cmd/tools/k9s.go b/src/cmd/tools/k9s.go
index ceabe6a547..4866548ce8 100644
--- a/src/cmd/tools/k9s.go
+++ b/src/cmd/tools/k9s.go
@@ -7,9 +7,9 @@ package tools
 import (
 	"os"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
 	k9s "github.com/derailed/k9s/cmd"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/config/lang"
 
 	// This allows for go linkname to be used in this file.  Go linkname is used so that we can pull the CLI flags from k9s and generate proper docs for the vendored tool.
 	_ "unsafe"
diff --git a/src/cmd/tools/kubectl.go b/src/cmd/tools/kubectl.go
index e9eb820ff5..bccabe89f1 100644
--- a/src/cmd/tools/kubectl.go
+++ b/src/cmd/tools/kubectl.go
@@ -7,10 +7,10 @@ package tools
 import (
 	"os"
 
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	kubeCLI "k8s.io/component-base/cli"
 	kubeCmd "k8s.io/kubectl/pkg/cmd"
 
diff --git a/src/cmd/tools/syft.go b/src/cmd/tools/syft.go
index 1b466027a3..57051021a0 100644
--- a/src/cmd/tools/syft.go
+++ b/src/cmd/tools/syft.go
@@ -7,10 +7,10 @@ package tools
 import (
 	"github.com/anchore/clio"
 	syftCLI "github.com/anchore/syft/cmd/syft/cli"
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
-// ldflags github.com/defenseunicorns/zarf/src/cmd/tools.syftVersion=x.x.x
+// ldflags github.com/zarf-dev/zarf/src/cmd/tools.syftVersion=x.x.x
 var syftVersion string
 
 func init() {
diff --git a/src/cmd/tools/wait.go b/src/cmd/tools/wait.go
index bb767ec972..fda0344fbc 100644
--- a/src/cmd/tools/wait.go
+++ b/src/cmd/tools/wait.go
@@ -8,10 +8,10 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	"github.com/spf13/cobra"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 
 	// Import to initialize client auth plugins.
 	_ "k8s.io/client-go/plugin/pkg/client/auth"
diff --git a/src/cmd/tools/yq.go b/src/cmd/tools/yq.go
index 4dbf43ddff..85d72ab767 100644
--- a/src/cmd/tools/yq.go
+++ b/src/cmd/tools/yq.go
@@ -5,8 +5,8 @@
 package tools
 
 import (
-	"github.com/defenseunicorns/zarf/src/config/lang"
 	yq "github.com/mikefarah/yq/v4/cmd"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 func init() {
diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go
index f911b2ec40..3f97be20eb 100644
--- a/src/cmd/tools/zarf.go
+++ b/src/cmd/tools/zarf.go
@@ -18,18 +18,18 @@ import (
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
 
-	"github.com/defenseunicorns/zarf/src/cmd/common"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/internal/packager/template"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/pki"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/cmd/common"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/internal/packager/template"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/pki"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var subAltNames []string
diff --git a/src/cmd/version.go b/src/cmd/version.go
index 589cab9929..26299c7db5 100644
--- a/src/cmd/version.go
+++ b/src/cmd/version.go
@@ -15,8 +15,8 @@ import (
 	goyaml "github.com/goccy/go-yaml"
 	"github.com/spf13/cobra"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 var outputFormat string
diff --git a/src/config/config.go b/src/config/config.go
index 203767f6d8..4f55f48e61 100644
--- a/src/config/config.go
+++ b/src/config/config.go
@@ -12,7 +12,7 @@ import (
 	"strings"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Zarf Global Configuration Constants.
diff --git a/src/extensions/bigbang/banner.go b/src/extensions/bigbang/banner.go
index 07314b3141..e6791eec58 100644
--- a/src/extensions/bigbang/banner.go
+++ b/src/extensions/bigbang/banner.go
@@ -5,8 +5,8 @@
 package bigbang
 
 import (
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 func printBanner() {
diff --git a/src/extensions/bigbang/bigbang.go b/src/extensions/bigbang/bigbang.go
index b89b61f460..b97c8f6e25 100644
--- a/src/extensions/bigbang/bigbang.go
+++ b/src/extensions/bigbang/bigbang.go
@@ -14,15 +14,15 @@ import (
 
 	"github.com/Masterminds/semver/v3"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
-	"github.com/defenseunicorns/zarf/src/types/extensions"
 	fluxHelmCtrl "github.com/fluxcd/helm-controller/api/v2beta1"
 	fluxSrcCtrl "github.com/fluxcd/source-controller/api/v1beta2"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types/extensions"
 	"helm.sh/helm/v3/pkg/chartutil"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/extensions/bigbang/flux.go b/src/extensions/bigbang/flux.go
index e67129c07c..d67097f26c 100644
--- a/src/extensions/bigbang/flux.go
+++ b/src/extensions/bigbang/flux.go
@@ -11,11 +11,11 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
-	"github.com/defenseunicorns/zarf/src/types/extensions"
 	fluxHelmCtrl "github.com/fluxcd/helm-controller/api/v2beta1"
+	"github.com/zarf-dev/zarf/src/internal/packager/kustomize"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types/extensions"
 	"helm.sh/helm/v3/pkg/chartutil"
 	v1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
diff --git a/src/extensions/bigbang/manifests.go b/src/extensions/bigbang/manifests.go
index cec481d6cb..3eb88bee6c 100644
--- a/src/extensions/bigbang/manifests.go
+++ b/src/extensions/bigbang/manifests.go
@@ -11,9 +11,9 @@ import (
 	"strings"
 
 	"github.com/Masterminds/semver/v3"
-	"github.com/defenseunicorns/zarf/src/types/extensions"
 	fluxHelmCtrl "github.com/fluxcd/helm-controller/api/v2beta1"
 	fluxSrcCtrl "github.com/fluxcd/source-controller/api/v1"
+	"github.com/zarf-dev/zarf/src/types/extensions"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
diff --git a/src/extensions/bigbang/test/bigbang_test.go b/src/extensions/bigbang/test/bigbang_test.go
index 4e016c6a6f..641ba566d1 100644
--- a/src/extensions/bigbang/test/bigbang_test.go
+++ b/src/extensions/bigbang/test/bigbang_test.go
@@ -14,10 +14,10 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	test "github.com/defenseunicorns/zarf/src/test"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	test "github.com/zarf-dev/zarf/src/test"
 )
 
 // The Big Bang project ID on Repo1
diff --git a/src/internal/agent/hooks/argocd-application.go b/src/internal/agent/hooks/argocd-application.go
index b9197bae51..8886ba2ff9 100644
--- a/src/internal/agent/hooks/argocd-application.go
+++ b/src/internal/agent/hooks/argocd-application.go
@@ -10,12 +10,12 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
diff --git a/src/internal/agent/hooks/argocd-application_test.go b/src/internal/agent/hooks/argocd-application_test.go
index 6ef39b2730..31ec452959 100644
--- a/src/internal/agent/hooks/argocd-application_test.go
+++ b/src/internal/agent/hooks/argocd-application_test.go
@@ -9,10 +9,10 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 )
diff --git a/src/internal/agent/hooks/argocd-repository.go b/src/internal/agent/hooks/argocd-repository.go
index 23e05df999..2311b50511 100644
--- a/src/internal/agent/hooks/argocd-repository.go
+++ b/src/internal/agent/hooks/argocd-repository.go
@@ -11,12 +11,12 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 )
diff --git a/src/internal/agent/hooks/argocd-repository_test.go b/src/internal/agent/hooks/argocd-repository_test.go
index 4506b682e1..fdc99fe1c2 100644
--- a/src/internal/agent/hooks/argocd-repository_test.go
+++ b/src/internal/agent/hooks/argocd-repository_test.go
@@ -10,10 +10,10 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/internal/agent/hooks/common.go b/src/internal/agent/hooks/common.go
index ed1de69797..52ea3bb509 100644
--- a/src/internal/agent/hooks/common.go
+++ b/src/internal/agent/hooks/common.go
@@ -4,7 +4,7 @@
 // Package hooks contains the mutation hooks for the Zarf agent.
 package hooks
 
-import "github.com/defenseunicorns/zarf/src/internal/agent/operations"
+import "github.com/zarf-dev/zarf/src/internal/agent/operations"
 
 func getLabelPatch(currLabels map[string]string) operations.PatchOperation {
 	if currLabels == nil {
diff --git a/src/internal/agent/hooks/flux-gitrepo.go b/src/internal/agent/hooks/flux-gitrepo.go
index 8256b40aae..079ee6a8e0 100644
--- a/src/internal/agent/hooks/flux-gitrepo.go
+++ b/src/internal/agent/hooks/flux-gitrepo.go
@@ -10,14 +10,14 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	fluxmeta "github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 	v1 "k8s.io/api/admission/v1"
 )
 
diff --git a/src/internal/agent/hooks/flux-gitrepo_test.go b/src/internal/agent/hooks/flux-gitrepo_test.go
index a68cbfe591..dc9c17a093 100644
--- a/src/internal/agent/hooks/flux-gitrepo_test.go
+++ b/src/internal/agent/hooks/flux-gitrepo_test.go
@@ -9,13 +9,13 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	fluxmeta "github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
diff --git a/src/internal/agent/hooks/flux-helmrepo.go b/src/internal/agent/hooks/flux-helmrepo.go
index 6fb0e7e712..90aab22f7a 100644
--- a/src/internal/agent/hooks/flux-helmrepo.go
+++ b/src/internal/agent/hooks/flux-helmrepo.go
@@ -11,14 +11,14 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	"github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 	v1 "k8s.io/api/admission/v1"
 )
 
diff --git a/src/internal/agent/hooks/flux-helmrepo_test.go b/src/internal/agent/hooks/flux-helmrepo_test.go
index 85895bd31b..d0e48a0074 100644
--- a/src/internal/agent/hooks/flux-helmrepo_test.go
+++ b/src/internal/agent/hooks/flux-helmrepo_test.go
@@ -9,13 +9,13 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	fluxmeta "github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/internal/agent/hooks/flux-ocirepo.go b/src/internal/agent/hooks/flux-ocirepo.go
index 00d7ded917..045b315e3a 100644
--- a/src/internal/agent/hooks/flux-ocirepo.go
+++ b/src/internal/agent/hooks/flux-ocirepo.go
@@ -10,14 +10,14 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	"github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1beta2"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 	v1 "k8s.io/api/admission/v1"
 )
 
diff --git a/src/internal/agent/hooks/flux-ocirepo_test.go b/src/internal/agent/hooks/flux-ocirepo_test.go
index 4b2ff6cf57..5cab4d2530 100644
--- a/src/internal/agent/hooks/flux-ocirepo_test.go
+++ b/src/internal/agent/hooks/flux-ocirepo_test.go
@@ -9,13 +9,13 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	fluxmeta "github.com/fluxcd/pkg/apis/meta"
 	flux "github.com/fluxcd/source-controller/api/v1beta2"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/internal/agent/hooks/pods.go b/src/internal/agent/hooks/pods.go
index a152aa4a8a..1eaccb5fbb 100644
--- a/src/internal/agent/hooks/pods.go
+++ b/src/internal/agent/hooks/pods.go
@@ -9,12 +9,12 @@ import (
 	"encoding/json"
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 	v1 "k8s.io/api/admission/v1"
 
 	corev1 "k8s.io/api/core/v1"
diff --git a/src/internal/agent/hooks/pods_test.go b/src/internal/agent/hooks/pods_test.go
index 3a41f9227c..dafa786f8c 100644
--- a/src/internal/agent/hooks/pods_test.go
+++ b/src/internal/agent/hooks/pods_test.go
@@ -9,11 +9,11 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/internal/agent/hooks/utils_test.go b/src/internal/agent/hooks/utils_test.go
index f2d4ed6136..84ee8a4c47 100644
--- a/src/internal/agent/hooks/utils_test.go
+++ b/src/internal/agent/hooks/utils_test.go
@@ -11,10 +11,10 @@ import (
 	"net/http/httptest"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/admission/v1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
diff --git a/src/internal/agent/http/admission/handler.go b/src/internal/agent/http/admission/handler.go
index f1d3cd8ead..56589a7c73 100644
--- a/src/internal/agent/http/admission/handler.go
+++ b/src/internal/agent/http/admission/handler.go
@@ -12,9 +12,9 @@ import (
 	"io"
 	"net/http"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/agent/operations"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/agent/operations"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	corev1 "k8s.io/api/admission/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go
index 2a78b88eea..8a9d939507 100644
--- a/src/internal/agent/http/proxy.go
+++ b/src/internal/agent/http/proxy.go
@@ -14,10 +14,10 @@ import (
 	"net/url"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 )
 
 // ProxyHandler constructs a new httputil.ReverseProxy and returns an http handler.
diff --git a/src/internal/agent/http/server.go b/src/internal/agent/http/server.go
index 62fb72c59d..b5953ee0e2 100644
--- a/src/internal/agent/http/server.go
+++ b/src/internal/agent/http/server.go
@@ -10,11 +10,11 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/internal/agent/hooks"
-	"github.com/defenseunicorns/zarf/src/internal/agent/http/admission"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"github.com/zarf-dev/zarf/src/internal/agent/hooks"
+	"github.com/zarf-dev/zarf/src/internal/agent/http/admission"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // NewAdmissionServer creates a http.Server for the mutating webhook admission handler.
diff --git a/src/internal/agent/operations/hook.go b/src/internal/agent/operations/hook.go
index 5ce42b8810..627ce58094 100644
--- a/src/internal/agent/operations/hook.go
+++ b/src/internal/agent/operations/hook.go
@@ -7,8 +7,8 @@ package operations
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	admission "k8s.io/api/admission/v1"
 )
 
diff --git a/src/internal/agent/start.go b/src/internal/agent/start.go
index 9acd2afcc4..80a04c642f 100644
--- a/src/internal/agent/start.go
+++ b/src/internal/agent/start.go
@@ -12,9 +12,9 @@ import (
 
 	"golang.org/x/sync/errgroup"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	agentHttp "github.com/defenseunicorns/zarf/src/internal/agent/http"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	agentHttp "github.com/zarf-dev/zarf/src/internal/agent/http"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // Heavily influenced by https://github.com/douglasmakey/admissioncontroller and
diff --git a/src/internal/packager/git/checkout.go b/src/internal/packager/git/checkout.go
index 2a39382cda..4441e48873 100644
--- a/src/internal/packager/git/checkout.go
+++ b/src/internal/packager/git/checkout.go
@@ -7,10 +7,10 @@ package git
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // CheckoutTag performs a `git checkout` of the provided tag to a detached HEAD.
diff --git a/src/internal/packager/git/clone.go b/src/internal/packager/git/clone.go
index 0123b81f2f..6e70077511 100644
--- a/src/internal/packager/git/clone.go
+++ b/src/internal/packager/git/clone.go
@@ -9,12 +9,12 @@ import (
 	"errors"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/go-git/go-git/v5"
 	goConfig "github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 // clone performs a `git clone` of a given repo.
diff --git a/src/internal/packager/git/common.go b/src/internal/packager/git/common.go
index 1c03fd3343..6864d3dea5 100644
--- a/src/internal/packager/git/common.go
+++ b/src/internal/packager/git/common.go
@@ -8,9 +8,9 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Git is the main struct for managing git repositories.
diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go
index 7bf9afe53f..a6c2f4b48a 100644
--- a/src/internal/packager/git/gitea.go
+++ b/src/internal/packager/git/gitea.go
@@ -16,10 +16,10 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // CreateTokenResponse is the response given from creating a token in Gitea
diff --git a/src/internal/packager/git/pull.go b/src/internal/packager/git/pull.go
index 79313cde32..bd2472c08c 100644
--- a/src/internal/packager/git/pull.go
+++ b/src/internal/packager/git/pull.go
@@ -9,10 +9,10 @@ import (
 	"path"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 // DownloadRepoToTemp clones or updates a repo into a temp folder to perform ephemeral actions (i.e. process chart repos).
diff --git a/src/internal/packager/git/push.go b/src/internal/packager/git/push.go
index 9bd52e2c79..494ee40db6 100644
--- a/src/internal/packager/git/push.go
+++ b/src/internal/packager/git/push.go
@@ -10,13 +10,13 @@ import (
 	"os"
 	"path"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	"github.com/go-git/go-git/v5"
 	goConfig "github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/transport"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 )
 
 // PushRepo pushes a git repository from the local path to the configured git server.
@@ -145,7 +145,7 @@ func (g *Git) push(repo *git.Repository, spinner *message.Spinner) error {
 		RemoteName: offlineRemoteName,
 		Auth:       &gitCred,
 		Progress:   spinner,
-		// TODO: (@JEFFMCCOY) add the parsing for the `+` force prefix (see https://github.com/defenseunicorns/zarf/issues/1410)
+		// TODO: (@JEFFMCCOY) add the parsing for the `+` force prefix (see https://github.com/zarf-dev/zarf/issues/1410)
 		//Force: isForce,
 		// If a provided refspec doesn't push anything, it is just ignored
 		RefSpecs: []goConfig.RefSpec{
diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go
index 0ff647b60a..f9ac5eefe0 100644
--- a/src/internal/packager/helm/chart.go
+++ b/src/internal/packager/helm/chart.go
@@ -24,9 +24,9 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // InstallOrUpgradeChart performs a helm install of the given chart.
diff --git a/src/internal/packager/helm/common.go b/src/internal/packager/helm/common.go
index 6fba4d364e..e4dd729a19 100644
--- a/src/internal/packager/helm/common.go
+++ b/src/internal/packager/helm/common.go
@@ -14,11 +14,11 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/cli"
diff --git a/src/internal/packager/helm/destroy.go b/src/internal/packager/helm/destroy.go
index 8f7060c7c7..44c519a00a 100644
--- a/src/internal/packager/helm/destroy.go
+++ b/src/internal/packager/helm/destroy.go
@@ -7,8 +7,8 @@ package helm
 import (
 	"regexp"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	"helm.sh/helm/v3/pkg/action"
 )
 
diff --git a/src/internal/packager/helm/images.go b/src/internal/packager/helm/images.go
index 447aefc780..390ce5c8be 100644
--- a/src/internal/packager/helm/images.go
+++ b/src/internal/packager/helm/images.go
@@ -5,8 +5,8 @@ package helm
 
 import (
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/goccy/go-yaml"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	"helm.sh/helm/v3/pkg/chart/loader"
 	"helm.sh/helm/v3/pkg/chartutil"
 )
diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go
index b03aa6a93a..5f45b437a0 100644
--- a/src/internal/packager/helm/post-render.go
+++ b/src/internal/packager/helm/post-render.go
@@ -14,11 +14,11 @@ import (
 	"slices"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 	"helm.sh/helm/v3/pkg/releaseutil"
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/client-go/dynamic"
diff --git a/src/internal/packager/helm/repo.go b/src/internal/packager/helm/repo.go
index 6bc19c8e50..c3ea9c646f 100644
--- a/src/internal/packager/helm/repo.go
+++ b/src/internal/packager/helm/repo.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/cli"
diff --git a/src/internal/packager/helm/utils.go b/src/internal/packager/helm/utils.go
index 4f6119de8e..8d3d5f3202 100644
--- a/src/internal/packager/helm/utils.go
+++ b/src/internal/packager/helm/utils.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	"helm.sh/helm/v3/pkg/action"
 	"helm.sh/helm/v3/pkg/chart"
 	"helm.sh/helm/v3/pkg/chartutil"
diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go
index 91d3fe917f..0381ef4b52 100644
--- a/src/internal/packager/helm/zarf.go
+++ b/src/internal/packager/helm/zarf.go
@@ -17,13 +17,13 @@ import (
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/internal/packager/template"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/internal/packager/template"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // UpdateZarfRegistryValues updates the Zarf registry deployment with the new state values
diff --git a/src/internal/packager/images/common.go b/src/internal/packager/images/common.go
index 8efb18f12d..3e2ad406ff 100644
--- a/src/internal/packager/images/common.go
+++ b/src/internal/packager/images/common.go
@@ -9,13 +9,13 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/crane"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // PullConfig is the configuration for pulling images.
@@ -104,7 +104,7 @@ func createPushOpts(cfg PushConfig, pb *message.ProgressBar) []crane.Option {
 
 	transport := http.DefaultTransport.(*http.Transport).Clone()
 	transport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.Insecure
-	// TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/defenseunicorns/zarf/issues/1444
+	// TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/zarf-dev/zarf/issues/1444
 	transport.ResponseHeaderTimeout = 10 * time.Second
 
 	transportWithProgressBar := helpers.NewTransport(transport, pb)
diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go
index 5636e7229d..7a313c5db0 100644
--- a/src/internal/packager/images/pull.go
+++ b/src/internal/packager/images/pull.go
@@ -19,11 +19,6 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	"github.com/google/go-containerregistry/pkg/crane"
 	"github.com/google/go-containerregistry/pkg/logs"
 	"github.com/google/go-containerregistry/pkg/name"
@@ -37,6 +32,11 @@ import (
 	"github.com/google/go-containerregistry/pkg/v1/types"
 	"github.com/moby/moby/client"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 	"golang.org/x/sync/errgroup"
 )
 
@@ -132,7 +132,7 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
 							ref, utils.ByteFormat(float64(rawImg.Size), 2))
 					}
 
-					// Use unbuffered opener to avoid OOM Kill issues https://github.com/defenseunicorns/zarf/issues/1214.
+					// Use unbuffered opener to avoid OOM Kill issues https://github.com/zarf-dev/zarf/issues/1214.
 					// This will also take forever to load large images.
 					img, err = daemon.Image(reference, daemon.WithUnbufferedOpener())
 					if err != nil {
@@ -254,7 +254,7 @@ func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, er
 
 	// Needed because when pulling from the local docker daemon, while using the docker containerd runtime
 	// Crane incorrectly names the blob of the docker image config to a sha that does not match the contents
-	// https://github.com/defenseunicorns/zarf/issues/2584
+	// https://github.com/zarf-dev/zarf/issues/2584
 	// This is a band aid fix while we wait for crane and or docker to create the permanent fix
 	blobDir := filepath.Join(cfg.DestinationDirectory, "blobs", "sha256")
 	err = filepath.Walk(blobDir, func(path string, fi os.FileInfo, err error) error {
diff --git a/src/internal/packager/images/pull_test.go b/src/internal/packager/images/pull_test.go
index 4ee0ca5478..eef8bbc9e8 100644
--- a/src/internal/packager/images/pull_test.go
+++ b/src/internal/packager/images/pull_test.go
@@ -10,8 +10,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 )
 
 func TestPull(t *testing.T) {
diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go
index 3048218662..08625ab385 100644
--- a/src/internal/packager/images/push.go
+++ b/src/internal/packager/images/push.go
@@ -10,13 +10,13 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	"github.com/google/go-containerregistry/pkg/crane"
 	"github.com/google/go-containerregistry/pkg/logs"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 // Push pushes images to a registry.
diff --git a/src/internal/packager/sbom/catalog.go b/src/internal/packager/sbom/catalog.go
index d60d7a70b7..aadeeea257 100755
--- a/src/internal/packager/sbom/catalog.go
+++ b/src/internal/packager/sbom/catalog.go
@@ -24,12 +24,12 @@ import (
 	"github.com/anchore/syft/syft/sbom"
 	"github.com/anchore/syft/syft/source"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 // Builder is the main struct used to build SBOM artifacts.
diff --git a/src/internal/packager/sbom/tools.go b/src/internal/packager/sbom/tools.go
index 860b50e879..60aa998958 100644
--- a/src/internal/packager/sbom/tools.go
+++ b/src/internal/packager/sbom/tools.go
@@ -9,8 +9,8 @@ import (
 	"path/filepath"
 
 	"github.com/AlecAivazis/survey/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 // ViewSBOMFiles opens a browser to view the SBOM files and pauses for user input.
diff --git a/src/internal/packager/sbom/viewer.go b/src/internal/packager/sbom/viewer.go
index 3362d251ac..8bc9fd9e0e 100644
--- a/src/internal/packager/sbom/viewer.go
+++ b/src/internal/packager/sbom/viewer.go
@@ -9,8 +9,8 @@ import (
 	"fmt"
 	"html/template"
 
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 )
 
 func (b *Builder) createSBOMViewerAsset(identifier string, jsonData []byte) error {
diff --git a/src/internal/packager/template/template.go b/src/internal/packager/template/template.go
index 8b76252aa1..70f7808cc2 100644
--- a/src/internal/packager/template/template.go
+++ b/src/internal/packager/template/template.go
@@ -10,14 +10,14 @@ import (
 	"log/slog"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/interactive"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/interactive"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
 )
 
 const (
diff --git a/src/pkg/cluster/cluster.go b/src/pkg/cluster/cluster.go
index 5d15550482..b4eb28051b 100644
--- a/src/pkg/cluster/cluster.go
+++ b/src/pkg/cluster/cluster.go
@@ -18,7 +18,7 @@ import (
 
 	pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 const (
diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go
index 21a060742e..7b19257040 100644
--- a/src/pkg/cluster/data.go
+++ b/src/pkg/cluster/data.go
@@ -22,12 +22,12 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // HandleDataInjection waits for the target pod(s) to come up and inject the data into them
diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go
index 52824843bf..48552ac5e1 100644
--- a/src/pkg/cluster/injector.go
+++ b/src/pkg/cluster/injector.go
@@ -26,10 +26,10 @@ import (
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 // StartInjection initializes a Zarf injection into the cluster.
diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go
index c65c968d0e..6d4256ef83 100644
--- a/src/pkg/cluster/namespace.go
+++ b/src/pkg/cluster/namespace.go
@@ -12,7 +12,7 @@ import (
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster.
diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go
index d083f6bbf4..3cdeabe826 100644
--- a/src/pkg/cluster/secrets.go
+++ b/src/pkg/cluster/secrets.go
@@ -15,9 +15,9 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // DockerConfig contains the authentication information from the machine's docker config.
diff --git a/src/pkg/cluster/secrets_test.go b/src/pkg/cluster/secrets_test.go
index 20d3fe70f9..80cd33b933 100644
--- a/src/pkg/cluster/secrets_test.go
+++ b/src/pkg/cluster/secrets_test.go
@@ -12,7 +12,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestGenerateRegistryPullCredsWithOutSvc(t *testing.T) {
diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go
index 65fba1431a..bdc14e5989 100644
--- a/src/pkg/cluster/state.go
+++ b/src/pkg/cluster/state.go
@@ -17,11 +17,11 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/pki"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/pki"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Zarf Cluster Constants.
diff --git a/src/pkg/cluster/state_test.go b/src/pkg/cluster/state_test.go
index 524b0e3295..cf52d195dc 100644
--- a/src/pkg/cluster/state_test.go
+++ b/src/pkg/cluster/state_test.go
@@ -17,9 +17,9 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/pki"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/pki"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestInitZarfState(t *testing.T) {
diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go
index 3f8433ed9f..f0a1a660e0 100644
--- a/src/pkg/cluster/tunnel.go
+++ b/src/pkg/cluster/tunnel.go
@@ -23,8 +23,8 @@ import (
 	"k8s.io/client-go/transport/spdy"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Zarf specific connect strings
diff --git a/src/pkg/cluster/tunnel_test.go b/src/pkg/cluster/tunnel_test.go
index fd1866da19..9ce09770e9 100644
--- a/src/pkg/cluster/tunnel_test.go
+++ b/src/pkg/cluster/tunnel_test.go
@@ -12,7 +12,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestListConnections(t *testing.T) {
diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go
index 93d8406ced..7320e4610b 100644
--- a/src/pkg/cluster/zarf.go
+++ b/src/pkg/cluster/zarf.go
@@ -17,9 +17,9 @@ import (
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // GetDeployedZarfPackages gets metadata information about packages that have been deployed to the cluster.
diff --git a/src/pkg/cluster/zarf_test.go b/src/pkg/cluster/zarf_test.go
index 4abfb81e8f..f7e9b481ec 100644
--- a/src/pkg/cluster/zarf_test.go
+++ b/src/pkg/cluster/zarf_test.go
@@ -16,8 +16,8 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // TestPackageSecretNeedsWait verifies that Zarf waits for webhooks to complete correctly.
diff --git a/src/pkg/interactive/components.go b/src/pkg/interactive/components.go
index 4e24f3f083..f48db25043 100644
--- a/src/pkg/interactive/components.go
+++ b/src/pkg/interactive/components.go
@@ -8,10 +8,10 @@ import (
 	"fmt"
 
 	"github.com/AlecAivazis/survey/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // SelectOptionalComponent prompts to confirm optional components
diff --git a/src/pkg/interactive/prompt.go b/src/pkg/interactive/prompt.go
index d832982538..759f520948 100644
--- a/src/pkg/interactive/prompt.go
+++ b/src/pkg/interactive/prompt.go
@@ -8,8 +8,8 @@ import (
 	"fmt"
 
 	"github.com/AlecAivazis/survey/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
 )
 
 // PromptSigPassword prompts the user for the password to their private key
diff --git a/src/pkg/layout/component.go b/src/pkg/layout/component.go
index c933fce6f6..f52e6bbe06 100644
--- a/src/pkg/layout/component.go
+++ b/src/pkg/layout/component.go
@@ -11,9 +11,9 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ComponentPaths contains paths for a component.
diff --git a/src/pkg/layout/image.go b/src/pkg/layout/image.go
index 7d236410bf..13ed0af43d 100644
--- a/src/pkg/layout/image.go
+++ b/src/pkg/layout/image.go
@@ -49,7 +49,7 @@ func (i *Images) AddV1Image(img v1.Image) error {
 		return err
 	}
 	// Cannot use img.ConfigName to get this value because of an upstream bug in crane / docker using the containerd runtime
-	// https://github.com/defenseunicorns/zarf/issues/2584
+	// https://github.com/zarf-dev/zarf/issues/2584
 	i.AddBlob(manifest.Config.Digest.Hex)
 	manifestSha, err := img.Digest()
 	if err != nil {
diff --git a/src/pkg/layout/package.go b/src/pkg/layout/package.go
index 1b99379e81..aef5e849d6 100644
--- a/src/pkg/layout/package.go
+++ b/src/pkg/layout/package.go
@@ -13,14 +13,14 @@ import (
 
 	"github.com/Masterminds/semver/v3"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/interactive"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/google/go-containerregistry/pkg/crane"
 	"github.com/mholt/archiver/v3"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/interactive"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/deprecated"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // PackagePaths is the default package layout.
diff --git a/src/pkg/layout/split.go b/src/pkg/layout/split.go
index aecf295dad..4668107405 100644
--- a/src/pkg/layout/split.go
+++ b/src/pkg/layout/split.go
@@ -13,8 +13,8 @@ import (
 	"os"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // splitFile will split the file into chunks and remove the original file.
diff --git a/src/pkg/layout/split_test.go b/src/pkg/layout/split_test.go
index b7f48fc9f6..718dc7bfee 100644
--- a/src/pkg/layout/split_test.go
+++ b/src/pkg/layout/split_test.go
@@ -10,8 +10,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestSplitFile(t *testing.T) {
diff --git a/src/pkg/message/connect.go b/src/pkg/message/connect.go
index 75f87fdd64..499a9e3602 100644
--- a/src/pkg/message/connect.go
+++ b/src/pkg/message/connect.go
@@ -7,7 +7,7 @@ package message
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // PrintConnectStringTable prints a table of connect strings.
diff --git a/src/pkg/message/credentials.go b/src/pkg/message/credentials.go
index 19f86f8516..74b127a1c6 100644
--- a/src/pkg/message/credentials.go
+++ b/src/pkg/message/credentials.go
@@ -8,8 +8,8 @@ import (
 	"fmt"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Common constants for printing credentials
diff --git a/src/pkg/message/message.go b/src/pkg/message/message.go
index 0ead8b0e01..f9d7d48c3c 100644
--- a/src/pkg/message/message.go
+++ b/src/pkg/message/message.go
@@ -12,9 +12,9 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
 	"github.com/fatih/color"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/config"
 )
 
 // LogLevel is the level of logging to display.
diff --git a/src/pkg/packager/actions/actions.go b/src/pkg/packager/actions/actions.go
index 2f01757d6c..c92457675c 100644
--- a/src/pkg/packager/actions/actions.go
+++ b/src/pkg/packager/actions/actions.go
@@ -13,12 +13,12 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/internal/packager/template"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/internal/packager/template"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Run runs all provided actions.
diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go
index 0f59319ae6..3b7f59f968 100644
--- a/src/pkg/packager/common.go
+++ b/src/pkg/packager/common.go
@@ -15,17 +15,17 @@ import (
 	"github.com/Masterminds/semver/v3"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/template"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/template"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/deprecated"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Packager is the main struct for managing packages.
diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go
index 6927082f9b..ac47544515 100644
--- a/src/pkg/packager/common_test.go
+++ b/src/pkg/packager/common_test.go
@@ -13,9 +13,9 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes/fake"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestValidatePackageArchitecture(t *testing.T) {
diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go
index 69aa5fdb8e..06f67ec816 100644
--- a/src/pkg/packager/composer/list.go
+++ b/src/pkg/packager/composer/list.go
@@ -11,13 +11,13 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/extensions/bigbang"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/extensions/bigbang"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/deprecated"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Node is a node in the import chain
diff --git a/src/pkg/packager/composer/list_test.go b/src/pkg/packager/composer/list_test.go
index 0f96e0beb0..e720fdf57c 100644
--- a/src/pkg/packager/composer/list_test.go
+++ b/src/pkg/packager/composer/list_test.go
@@ -11,10 +11,10 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
-	"github.com/defenseunicorns/zarf/src/types/extensions"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types/extensions"
 )
 
 func TestNewImportChain(t *testing.T) {
diff --git a/src/pkg/packager/composer/oci.go b/src/pkg/packager/composer/oci.go
index f4f02fbaf3..129013d487 100644
--- a/src/pkg/packager/composer/oci.go
+++ b/src/pkg/packager/composer/oci.go
@@ -13,13 +13,13 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
 	"github.com/mholt/archiver/v3"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
 	ocistore "oras.land/oras-go/v2/content/oci"
 )
 
diff --git a/src/pkg/packager/composer/override.go b/src/pkg/packager/composer/override.go
index 4a07a1d936..feb4b4b6f3 100644
--- a/src/pkg/packager/composer/override.go
+++ b/src/pkg/packager/composer/override.go
@@ -7,7 +7,7 @@ package composer
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func overrideMetadata(c *types.ZarfComponent, override types.ZarfComponent) error {
diff --git a/src/pkg/packager/composer/pathfixer.go b/src/pkg/packager/composer/pathfixer.go
index fac110c47c..f13537dfd9 100644
--- a/src/pkg/packager/composer/pathfixer.go
+++ b/src/pkg/packager/composer/pathfixer.go
@@ -8,7 +8,7 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func makePathRelativeTo(path, relativeTo string) string {
diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go
index ee9a7fd2bc..517726eba4 100755
--- a/src/pkg/packager/create.go
+++ b/src/pkg/packager/create.go
@@ -10,10 +10,10 @@ import (
 	"os"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/creator"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/creator"
 )
 
 // Create generates a Zarf package tarball for a given PackageConfig and optional base directory.
diff --git a/src/pkg/packager/creator/compose.go b/src/pkg/packager/creator/compose.go
index 6cd5277388..8844c0305f 100644
--- a/src/pkg/packager/creator/compose.go
+++ b/src/pkg/packager/creator/compose.go
@@ -7,9 +7,9 @@ package creator
 import (
 	"context"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/composer"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/composer"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ComposeComponents composes components and their dependencies into a single Zarf package using an import chain.
diff --git a/src/pkg/packager/creator/compose_test.go b/src/pkg/packager/creator/compose_test.go
index 35e298f553..a4dd2fdf9e 100644
--- a/src/pkg/packager/creator/compose_test.go
+++ b/src/pkg/packager/creator/compose_test.go
@@ -8,8 +8,8 @@ import (
 	"context"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestComposeComponents(t *testing.T) {
diff --git a/src/pkg/packager/creator/creator.go b/src/pkg/packager/creator/creator.go
index aae86172c6..34e511458f 100644
--- a/src/pkg/packager/creator/creator.go
+++ b/src/pkg/packager/creator/creator.go
@@ -7,8 +7,8 @@ package creator
 import (
 	"context"
 
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Creator is an interface for creating Zarf packages.
diff --git a/src/pkg/packager/creator/creator_test.go b/src/pkg/packager/creator/creator_test.go
index 18a4a64c0a..1c2e3b7153 100644
--- a/src/pkg/packager/creator/creator_test.go
+++ b/src/pkg/packager/creator/creator_test.go
@@ -9,9 +9,9 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestLoadPackageDefinition(t *testing.T) {
diff --git a/src/pkg/packager/creator/differential.go b/src/pkg/packager/creator/differential.go
index d2c3c80480..a373bcc4f2 100644
--- a/src/pkg/packager/creator/differential.go
+++ b/src/pkg/packager/creator/differential.go
@@ -8,11 +8,11 @@ import (
 	"context"
 	"os"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // loadDifferentialData sets any images and repos from the existing reference package in the DifferentialData and returns it.
diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go
index 027df385fa..b8135a8c2f 100644
--- a/src/pkg/packager/creator/normal.go
+++ b/src/pkg/packager/creator/normal.go
@@ -17,24 +17,24 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/extensions/bigbang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/internal/packager/images"
-	"github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
-	"github.com/defenseunicorns/zarf/src/internal/packager/sbom"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/actions"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/extensions/bigbang"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/internal/packager/images"
+	"github.com/zarf-dev/zarf/src/internal/packager/kustomize"
+	"github.com/zarf-dev/zarf/src/internal/packager/sbom"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/actions"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/creator/skeleton.go b/src/pkg/packager/creator/skeleton.go
index e0e86126b4..091874c84f 100644
--- a/src/pkg/packager/creator/skeleton.go
+++ b/src/pkg/packager/creator/skeleton.go
@@ -13,17 +13,17 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/extensions/bigbang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/extensions/bigbang"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/internal/packager/kustomize"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/creator/template.go b/src/pkg/packager/creator/template.go
index df2fc86038..5a06651c53 100644
--- a/src/pkg/packager/creator/template.go
+++ b/src/pkg/packager/creator/template.go
@@ -7,12 +7,12 @@ package creator
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/interactive"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/interactive"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // FillActiveTemplate merges user-specified variables into the configuration templates of a zarf.yaml.
diff --git a/src/pkg/packager/creator/utils.go b/src/pkg/packager/creator/utils.go
index 43ab469565..5c3f962766 100644
--- a/src/pkg/packager/creator/utils.go
+++ b/src/pkg/packager/creator/utils.go
@@ -9,9 +9,9 @@ import (
 	"runtime"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/deprecated"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/packager/deprecated"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // recordPackageMetadata records various package metadata during package create.
diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go
index 9044772169..35f31514fd 100644
--- a/src/pkg/packager/deploy.go
+++ b/src/pkg/packager/deploy.go
@@ -23,19 +23,19 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/internal/packager/images"
-	"github.com/defenseunicorns/zarf/src/internal/packager/template"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/actions"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/internal/packager/images"
+	"github.com/zarf-dev/zarf/src/internal/packager/template"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/actions"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/deploy_test.go b/src/pkg/packager/deploy_test.go
index 5bb21bfcca..6e82cbcb10 100644
--- a/src/pkg/packager/deploy_test.go
+++ b/src/pkg/packager/deploy_test.go
@@ -6,10 +6,10 @@ package packager
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestGenerateValuesOverrides(t *testing.T) {
diff --git a/src/pkg/packager/deprecated/common.go b/src/pkg/packager/deprecated/common.go
index 4867ac52b2..f97e07bce0 100644
--- a/src/pkg/packager/deprecated/common.go
+++ b/src/pkg/packager/deprecated/common.go
@@ -12,9 +12,9 @@ import (
 	"slices"
 
 	"github.com/Masterminds/semver/v3"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // BreakingChange represents a breaking change that happened on a specified Zarf version.
@@ -35,7 +35,7 @@ func (bc BreakingChange) String() string {
 
 // List of migrations tracked in the zarf.yaml build data.
 const (
-	// This should be updated when a breaking change is introduced to the Zarf package structure.  See: https://github.com/defenseunicorns/zarf/releases/tag/v0.27.0
+	// This should be updated when a breaking change is introduced to the Zarf package structure.  See: https://github.com/zarf-dev/zarf/releases/tag/v0.27.0
 	LastNonBreakingVersion   = "v0.27.0"
 	ScriptsToActionsMigrated = "scripts-to-actions"
 	PluralizeSetVariable     = "pluralize-set-variable"
diff --git a/src/pkg/packager/deprecated/common_test.go b/src/pkg/packager/deprecated/common_test.go
index 6dec381554..b789f0c2ce 100644
--- a/src/pkg/packager/deprecated/common_test.go
+++ b/src/pkg/packager/deprecated/common_test.go
@@ -9,8 +9,8 @@ import (
 	"testing"
 
 	"github.com/Masterminds/semver/v3"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 func TestPrintBreakingChanges(t *testing.T) {
diff --git a/src/pkg/packager/deprecated/pluralize-set-variable.go b/src/pkg/packager/deprecated/pluralize-set-variable.go
index c3dc13e06c..44ea9c7f9c 100644
--- a/src/pkg/packager/deprecated/pluralize-set-variable.go
+++ b/src/pkg/packager/deprecated/pluralize-set-variable.go
@@ -7,8 +7,8 @@ package deprecated
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func migrateSetVariableToSetVariables(c types.ZarfComponent) (types.ZarfComponent, string) {
diff --git a/src/pkg/packager/deprecated/scripts-to-actions.go b/src/pkg/packager/deprecated/scripts-to-actions.go
index 2040e7eb90..f296e49ec3 100644
--- a/src/pkg/packager/deprecated/scripts-to-actions.go
+++ b/src/pkg/packager/deprecated/scripts-to-actions.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"math"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // migrateScriptsToActions coverts the deprecated scripts to the new actions
diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go
index 138d175109..4363c0b49a 100644
--- a/src/pkg/packager/dev.go
+++ b/src/pkg/packager/dev.go
@@ -13,15 +13,15 @@ import (
 	"runtime"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/creator"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/lint"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/fatih/color"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/creator"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/packager/lint"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // DevDeploy creates + deploys a package in one shot
diff --git a/src/pkg/packager/filters/deploy.go b/src/pkg/packager/filters/deploy.go
index f5ac17916f..40555fb9a2 100644
--- a/src/pkg/packager/filters/deploy.go
+++ b/src/pkg/packager/filters/deploy.go
@@ -11,8 +11,8 @@ import (
 
 	"github.com/agnivade/levenshtein"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/interactive"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/interactive"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ForDeploy creates a new deployment filter.
diff --git a/src/pkg/packager/filters/deploy_test.go b/src/pkg/packager/filters/deploy_test.go
index 1bb548f1cb..5b31a2269e 100644
--- a/src/pkg/packager/filters/deploy_test.go
+++ b/src/pkg/packager/filters/deploy_test.go
@@ -10,8 +10,8 @@ import (
 	"testing"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func componentFromQuery(t *testing.T, q string) types.ZarfComponent {
diff --git a/src/pkg/packager/filters/diff.go b/src/pkg/packager/filters/diff.go
index fe722e9741..bbba9ab789 100644
--- a/src/pkg/packager/filters/diff.go
+++ b/src/pkg/packager/filters/diff.go
@@ -6,10 +6,10 @@ package filters
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ByDifferentialData filters any images and repos already present in the reference package components.
diff --git a/src/pkg/packager/filters/diff_test.go b/src/pkg/packager/filters/diff_test.go
index 8ee64fab84..0b279b8f08 100644
--- a/src/pkg/packager/filters/diff_test.go
+++ b/src/pkg/packager/filters/diff_test.go
@@ -6,8 +6,8 @@ package filters
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestCopyFilter(t *testing.T) {
diff --git a/src/pkg/packager/filters/empty.go b/src/pkg/packager/filters/empty.go
index 860799fb7b..af4eb09663 100644
--- a/src/pkg/packager/filters/empty.go
+++ b/src/pkg/packager/filters/empty.go
@@ -4,7 +4,7 @@
 // Package filters contains core implementations of the ComponentFilterStrategy interface.
 package filters
 
-import "github.com/defenseunicorns/zarf/src/types"
+import "github.com/zarf-dev/zarf/src/types"
 
 // Empty returns a filter that does nothing.
 func Empty() ComponentFilterStrategy {
diff --git a/src/pkg/packager/filters/empty_test.go b/src/pkg/packager/filters/empty_test.go
index 8096219ad5..8d55fd70ad 100644
--- a/src/pkg/packager/filters/empty_test.go
+++ b/src/pkg/packager/filters/empty_test.go
@@ -7,8 +7,8 @@ package filters
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestEmptyFilter_Apply(t *testing.T) {
diff --git a/src/pkg/packager/filters/os.go b/src/pkg/packager/filters/os.go
index b031f35bbc..845c0cb400 100644
--- a/src/pkg/packager/filters/os.go
+++ b/src/pkg/packager/filters/os.go
@@ -7,7 +7,7 @@ package filters
 import (
 	"errors"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ByLocalOS creates a new filter that filters components based on local (runtime) OS.
diff --git a/src/pkg/packager/filters/os_test.go b/src/pkg/packager/filters/os_test.go
index 7d54beecd3..4dcc4b5279 100644
--- a/src/pkg/packager/filters/os_test.go
+++ b/src/pkg/packager/filters/os_test.go
@@ -7,8 +7,8 @@ package filters
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestLocalOSFilter(t *testing.T) {
diff --git a/src/pkg/packager/filters/select.go b/src/pkg/packager/filters/select.go
index 9116efe453..35694f580f 100644
--- a/src/pkg/packager/filters/select.go
+++ b/src/pkg/packager/filters/select.go
@@ -6,7 +6,7 @@ package filters
 
 import (
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // BySelectState creates a new simple included filter.
diff --git a/src/pkg/packager/filters/select_test.go b/src/pkg/packager/filters/select_test.go
index 9d87fbf014..b4d4ac9289 100644
--- a/src/pkg/packager/filters/select_test.go
+++ b/src/pkg/packager/filters/select_test.go
@@ -7,8 +7,8 @@ package filters
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func Test_selectStateFilter_Apply(t *testing.T) {
diff --git a/src/pkg/packager/filters/strat.go b/src/pkg/packager/filters/strat.go
index 497778b2df..1411ae3c5f 100644
--- a/src/pkg/packager/filters/strat.go
+++ b/src/pkg/packager/filters/strat.go
@@ -7,7 +7,7 @@ package filters
 import (
 	"fmt"
 
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ComponentFilterStrategy is a strategy interface for filtering components.
diff --git a/src/pkg/packager/filters/strat_test.go b/src/pkg/packager/filters/strat_test.go
index 5f5391153c..12f7dad86e 100644
--- a/src/pkg/packager/filters/strat_test.go
+++ b/src/pkg/packager/filters/strat_test.go
@@ -7,8 +7,8 @@ package filters
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestCombine(t *testing.T) {
diff --git a/src/pkg/packager/generate.go b/src/pkg/packager/generate.go
index d03e80c88d..905859bc53 100644
--- a/src/pkg/packager/generate.go
+++ b/src/pkg/packager/generate.go
@@ -11,11 +11,11 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	goyaml "github.com/goccy/go-yaml"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Generate generates a Zarf package definition.
diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go
index d68ae5f5de..bfa29d860b 100644
--- a/src/pkg/packager/inspect.go
+++ b/src/pkg/packager/inspect.go
@@ -10,8 +10,8 @@ import (
 	"os"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/internal/packager/sbom"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/internal/packager/sbom"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 // Inspect list the contents of a package.
diff --git a/src/pkg/packager/interactive.go b/src/pkg/packager/interactive.go
index 8c67fd55fe..3a09595eab 100644
--- a/src/pkg/packager/interactive.go
+++ b/src/pkg/packager/interactive.go
@@ -11,11 +11,11 @@ import (
 
 	"github.com/AlecAivazis/survey/v2"
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 func (p *Packager) confirmAction(stage string, warnings []string, sbomViewFiles []string) (confirm bool) {
diff --git a/src/pkg/packager/lint/findings.go b/src/pkg/packager/lint/findings.go
index 5b184146ed..8b48bdea78 100644
--- a/src/pkg/packager/lint/findings.go
+++ b/src/pkg/packager/lint/findings.go
@@ -6,7 +6,7 @@ package lint
 
 import (
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // GroupFindingsByPath groups findings by their package path
diff --git a/src/pkg/packager/lint/findings_test.go b/src/pkg/packager/lint/findings_test.go
index 99f0b7a652..522135eb96 100644
--- a/src/pkg/packager/lint/findings_test.go
+++ b/src/pkg/packager/lint/findings_test.go
@@ -7,8 +7,8 @@ package lint
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestGroupFindingsByPath(t *testing.T) {
diff --git a/src/pkg/packager/lint/lint.go b/src/pkg/packager/lint/lint.go
index 27a823deff..34ab432fe0 100644
--- a/src/pkg/packager/lint/lint.go
+++ b/src/pkg/packager/lint/lint.go
@@ -12,15 +12,15 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/composer"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/creator"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/xeipuuv/gojsonschema"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/composer"
+	"github.com/zarf-dev/zarf/src/pkg/packager/creator"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // ZarfSchema is exported so main.go can embed the schema file
diff --git a/src/pkg/packager/lint/lint_test.go b/src/pkg/packager/lint/lint_test.go
index 67af529a8d..213c0938d2 100644
--- a/src/pkg/packager/lint/lint_test.go
+++ b/src/pkg/packager/lint/lint_test.go
@@ -11,10 +11,10 @@ import (
 	"os"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types"
 	goyaml "github.com/goccy/go-yaml"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestZarfSchema(t *testing.T) {
diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go
index 27ffaf9c18..9cafe38f58 100644
--- a/src/pkg/packager/mirror.go
+++ b/src/pkg/packager/mirror.go
@@ -10,10 +10,10 @@ import (
 	"runtime"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Mirror pulls resources from a package (images, git repositories, etc) and pushes them to remotes in the air gap without deploying them
diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go
index 9a4f7a1280..c71038b2f8 100644
--- a/src/pkg/packager/prepare.go
+++ b/src/pkg/packager/prepare.go
@@ -16,16 +16,16 @@ import (
 	"github.com/goccy/go-yaml"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/internal/packager/images"
-	"github.com/defenseunicorns/zarf/src/internal/packager/kustomize"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/creator"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/google/go-containerregistry/pkg/crane"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/internal/packager/images"
+	"github.com/zarf-dev/zarf/src/internal/packager/kustomize"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/creator"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 	v1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
diff --git a/src/pkg/packager/publish.go b/src/pkg/packager/publish.go
index 6f5e90843c..bf664f2739 100644
--- a/src/pkg/packager/publish.go
+++ b/src/pkg/packager/publish.go
@@ -12,16 +12,16 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/creator"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/creator"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Publish publishes the package to a registry
diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go
index be34b6dda7..dbfbf7d690 100644
--- a/src/pkg/packager/remove.go
+++ b/src/pkg/packager/remove.go
@@ -18,14 +18,14 @@ import (
 	kerrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/internal/packager/helm"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/actions"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/sources"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/internal/packager/helm"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/actions"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/packager/sources"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Remove removes a package that was already deployed onto a cluster, uninstalling all installed helm charts.
diff --git a/src/pkg/packager/sources/cluster.go b/src/pkg/packager/sources/cluster.go
index 5936c5d6f0..bd671120e8 100644
--- a/src/pkg/packager/sources/cluster.go
+++ b/src/pkg/packager/sources/cluster.go
@@ -9,11 +9,11 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/sources/new.go b/src/pkg/packager/sources/new.go
index c9f1f46a14..9018632085 100644
--- a/src/pkg/packager/sources/new.go
+++ b/src/pkg/packager/sources/new.go
@@ -12,12 +12,12 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // PackageSource is an interface for package sources.
diff --git a/src/pkg/packager/sources/new_test.go b/src/pkg/packager/sources/new_test.go
index 9c7629ba42..a1a495b3ca 100644
--- a/src/pkg/packager/sources/new_test.go
+++ b/src/pkg/packager/sources/new_test.go
@@ -17,9 +17,9 @@ import (
 
 	"github.com/stretchr/testify/require"
 
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestNewPackageSource(t *testing.T) {
@@ -51,13 +51,13 @@ func TestNewPackageSource(t *testing.T) {
 		},
 		{
 			name:             "https",
-			src:              "https://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst",
+			src:              "https://github.com/zarf-dev/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst",
 			expectedIdentify: "https",
 			expectedType:     &URLSource{},
 		},
 		{
 			name:             "http",
-			src:              "http://github.com/defenseunicorns/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst",
+			src:              "http://github.com/zarf-dev/zarf/releases/download/v1.0.0/zarf-init-amd64-v1.0.0.tar.zst",
 			expectedIdentify: "http",
 			expectedType:     &URLSource{},
 		},
diff --git a/src/pkg/packager/sources/oci.go b/src/pkg/packager/sources/oci.go
index 84ba85ebaf..99a98137fc 100644
--- a/src/pkg/packager/sources/oci.go
+++ b/src/pkg/packager/sources/oci.go
@@ -12,14 +12,14 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/sources/split.go b/src/pkg/packager/sources/split.go
index 1594a51c20..473aa0008a 100644
--- a/src/pkg/packager/sources/split.go
+++ b/src/pkg/packager/sources/split.go
@@ -15,10 +15,10 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/sources/tarball.go b/src/pkg/packager/sources/tarball.go
index 663a4bd31c..b99253c0a5 100644
--- a/src/pkg/packager/sources/tarball.go
+++ b/src/pkg/packager/sources/tarball.go
@@ -14,12 +14,12 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/sources/url.go b/src/pkg/packager/sources/url.go
index 9fa3cfb8e0..02fc785d81 100644
--- a/src/pkg/packager/sources/url.go
+++ b/src/pkg/packager/sources/url.go
@@ -12,11 +12,11 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/packager/filters"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/packager/filters"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 var (
diff --git a/src/pkg/packager/sources/utils.go b/src/pkg/packager/sources/utils.go
index f880e11d0e..bb2e3227d9 100644
--- a/src/pkg/packager/sources/utils.go
+++ b/src/pkg/packager/sources/utils.go
@@ -12,12 +12,12 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
-	"github.com/defenseunicorns/zarf/src/types"
 	goyaml "github.com/goccy/go-yaml"
 	"github.com/mholt/archiver/v3"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // GetValidPackageExtensions returns the valid package extensions.
diff --git a/src/pkg/packager/sources/validate.go b/src/pkg/packager/sources/validate.go
index 1d8a4f6255..427d05708a 100644
--- a/src/pkg/packager/sources/validate.go
+++ b/src/pkg/packager/sources/validate.go
@@ -14,10 +14,10 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
 )
 
 var (
diff --git a/src/pkg/pki/pki.go b/src/pkg/pki/pki.go
index 6923042393..0eaf5ea82b 100644
--- a/src/pkg/pki/pki.go
+++ b/src/pkg/pki/pki.go
@@ -16,7 +16,7 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // Based off of https://github.com/dmcgowan/quicktls/blob/master/main.go
diff --git a/src/pkg/transform/git_test.go b/src/pkg/transform/git_test.go
index 70145275aa..19c5778966 100644
--- a/src/pkg/transform/git_test.go
+++ b/src/pkg/transform/git_test.go
@@ -13,9 +13,9 @@ import (
 var gitURLs = []string{
 	// Normal git repos and references for pushing/pulling
 	"https://repo1.dso.mil/platform-one/big-bang/apps/security-tools/twistlock.git",
-	"https://github.com/defenseunicorns/zarf.git",
+	"https://github.com/zarf-dev/zarf.git",
 	"https://ghcr.io/stefanprodan/podinfo_fasd-123.git",
-	"git://k3d-cluster.localhost/defenseunicorns/zarf-agent",
+	"git://k3d-cluster.localhost/zarf-dev/zarf-agent",
 	"http://localhost:5000/some-cool-repo",
 	"ssh://ghcr.io/stefanprodan/podinfo@6.0.0",
 	"https://stefanprodan/podinfo.git@adf0fasd10.1.223124123123-asdf",
@@ -23,19 +23,19 @@ var gitURLs = []string{
 	"file:///srv/git/stefanprodan/podinfo@adf0fasd10.1.223124123123-asdf",
 	"https://me0515@dev.azure.com/me0515/zarf-public-test/_git/zarf-public-test",
 	"https://me0515@dev.azure.com/me0515/zarf-public-test/_git/zarf-public-test@524980951ff16e19dc25232e9aea8fd693989ba6",
-	"https://github.com/defenseunicorns/zarf.helm.git",
-	"https://github.com/defenseunicorns/zarf.git@refs/tags/v0.16.0",
+	"https://github.com/zarf-dev/zarf.helm.git",
+	"https://github.com/zarf-dev/zarf.git@refs/tags/v0.16.0",
 	"https://github.com/DoD-Platform-One/big-bang.git@refs/heads/release-1.54.x",
 	"https://github.com/prometheus-community/helm-charts.git@kube-prometheus-stack-47.3.0",
 	"https://github.com/prometheus-community/",
 	"https://github.com/",
 
 	// Smart Git Protocol URLs for proxying (https://www.git-scm.com/docs/http-protocol)
-	"https://github.com/defenseunicorns/zarf.helm.git/info/refs",
-	"https://github.com/defenseunicorns/zarf.helm.git/info/refs?service=git-upload-pack",
-	"https://github.com/defenseunicorns/zarf.helm.git/info/refs?service=git-receive-pack",
-	"https://github.com/defenseunicorns/zarf.helm.git/git-upload-pack",
-	"https://github.com/defenseunicorns/zarf.helm.git/git-receive-pack",
+	"https://github.com/zarf-dev/zarf.helm.git/info/refs",
+	"https://github.com/zarf-dev/zarf.helm.git/info/refs?service=git-upload-pack",
+	"https://github.com/zarf-dev/zarf.helm.git/info/refs?service=git-receive-pack",
+	"https://github.com/zarf-dev/zarf.helm.git/git-upload-pack",
+	"https://github.com/zarf-dev/zarf.helm.git/git-receive-pack",
 }
 
 var badGitURLs = []string{
@@ -48,11 +48,11 @@ func TestMutateGitURLsInText(t *testing.T) {
 	originalText := `
 	# Here we handle invalid URLs (see below comment)
 	# We transform https://*/*.git URLs
-	https://github.com/defenseunicorns/zarf.git
+	https://github.com/zarf-dev/zarf.git
 	# Even URLs with things on either side
-	stuff https://github.com/defenseunicorns/zarf.git andthings
+	stuff https://github.com/zarf-dev/zarf.git andthings
 	# Including ssh://*/*.git URLs
-	ssh://git@github.com/defenseunicorns/zarf.git
+	ssh://git@github.com/zarf-dev/zarf.git
 	# Or non .git URLs
 	https://www.defenseunicorns.com/
 	`
@@ -60,11 +60,11 @@ func TestMutateGitURLsInText(t *testing.T) {
 	expectedText := `
 	# Here we handle invalid URLs (see below comment)
 	# We transform https://*/*.git URLs
-	https://gitlab.com/repo-owner/zarf-1211668992.git
+	https://gitlab.com/repo-owner/zarf-4156197301.git
 	# Even URLs with things on either side
-	stuff https://gitlab.com/repo-owner/zarf-1211668992.git andthings
+	stuff https://gitlab.com/repo-owner/zarf-4156197301.git andthings
 	# Including ssh://*/*.git URLs
-	https://gitlab.com/repo-owner/zarf-2566185087.git
+	https://gitlab.com/repo-owner/zarf-1231196790.git
 	# Or non .git URLs
 	https://www.defenseunicorns.com/
 	`
@@ -77,9 +77,9 @@ func TestGitURLSplitRef(t *testing.T) {
 	var expectedResult = [][]string{
 		// Normal git repos and references for pushing/pulling
 		{"https://repo1.dso.mil/platform-one/big-bang/apps/security-tools/twistlock.git", ""},
-		{"https://github.com/defenseunicorns/zarf.git", ""},
+		{"https://github.com/zarf-dev/zarf.git", ""},
 		{"https://ghcr.io/stefanprodan/podinfo_fasd-123.git", ""},
-		{"git://k3d-cluster.localhost/defenseunicorns/zarf-agent", ""},
+		{"git://k3d-cluster.localhost/zarf-dev/zarf-agent", ""},
 		{"http://localhost:5000/some-cool-repo", ""},
 		{"ssh://ghcr.io/stefanprodan/podinfo", "6.0.0"},
 		{"https://stefanprodan/podinfo.git", "adf0fasd10.1.223124123123-asdf"},
@@ -87,19 +87,19 @@ func TestGitURLSplitRef(t *testing.T) {
 		{"file:///srv/git/stefanprodan/podinfo", "adf0fasd10.1.223124123123-asdf"},
 		{"https://me0515@dev.azure.com/me0515/zarf-public-test/_git/zarf-public-test", ""},
 		{"https://me0515@dev.azure.com/me0515/zarf-public-test/_git/zarf-public-test", "524980951ff16e19dc25232e9aea8fd693989ba6"},
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
-		{"https://github.com/defenseunicorns/zarf.git", "refs/tags/v0.16.0"},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.git", "refs/tags/v0.16.0"},
 		{"https://github.com/DoD-Platform-One/big-bang.git", "refs/heads/release-1.54.x"},
 		{"https://github.com/prometheus-community/helm-charts.git", "kube-prometheus-stack-47.3.0"},
 		{"https://github.com/prometheus-community", ""},
 		{"https://github.com/", ""},
 
 		// Smart Git Protocol URLs for proxying (https://www.git-scm.com/docs/http-protocol)
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
-		{"https://github.com/defenseunicorns/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
+		{"https://github.com/zarf-dev/zarf.helm.git", ""},
 	}
 
 	for idx, url := range gitURLs {
@@ -119,9 +119,9 @@ func TestGitURLtoFolderName(t *testing.T) {
 	var expectedResult = []string{
 		// Normal git repos and references for pushing/pulling
 		"twistlock-1590638614",
-		"zarf-3863619701",
+		"zarf-3457133088",
 		"podinfo_fasd-123-1478387306",
-		"zarf-agent-802453811",
+		"zarf-agent-927663661",
 		"some-cool-repo-1916670310",
 		"podinfo-1350532569",
 		"podinfo-1853010387",
@@ -129,19 +129,19 @@ func TestGitURLtoFolderName(t *testing.T) {
 		"podinfo-122075437",
 		"zarf-public-test-612413317",
 		"zarf-public-test-634307705",
-		"zarf.helm-2570741950",
-		"zarf-2175050463",
+		"zarf.helm-93697844",
+		"zarf-2360044954",
 		"big-bang-2705706079",
 		"helm-charts-1319967699",
 		"prometheus-community-3453166319",
 		"-1276058275",
 
 		// Smart Git Protocol URLs for proxying (https://www.git-scm.com/docs/http-protocol)
-		"zarf.helm-2570741950",
-		"zarf.helm-2570741950",
-		"zarf.helm-2570741950",
-		"zarf.helm-2570741950",
-		"zarf.helm-2570741950",
+		"zarf.helm-93697844",
+		"zarf.helm-93697844",
+		"zarf.helm-93697844",
+		"zarf.helm-93697844",
+		"zarf.helm-93697844",
 	}
 
 	for idx, url := range gitURLs {
@@ -160,9 +160,9 @@ func TestGitURLtoRepoName(t *testing.T) {
 	var expectedResult = []string{
 		// Normal git repos and references for pushing/pulling
 		"twistlock-97328248",
-		"zarf-1211668992",
+		"zarf-4156197301",
 		"podinfo_fasd-123-84577122",
-		"zarf-agent-3633494462",
+		"zarf-agent-1776579160",
 		"some-cool-repo-926913879",
 		"podinfo-2985051089",
 		"podinfo-2197246515",
@@ -170,19 +170,19 @@ func TestGitURLtoRepoName(t *testing.T) {
 		"podinfo-1175499642",
 		"zarf-public-test-2170732467",
 		"zarf-public-test-2170732467",
-		"zarf.helm-842267124",
-		"zarf-1211668992",
+		"zarf.helm-693435256",
+		"zarf-4156197301",
 		"big-bang-2366614037",
 		"helm-charts-3648076006",
 		"prometheus-community-2749132599",
 		"-98306241",
 
 		// Smart Git Protocol URLs for proxying (https://www.git-scm.com/docs/http-protocol)
-		"zarf.helm-842267124",
-		"zarf.helm-842267124",
-		"zarf.helm-842267124",
-		"zarf.helm-842267124",
-		"zarf.helm-842267124",
+		"zarf.helm-693435256",
+		"zarf.helm-693435256",
+		"zarf.helm-693435256",
+		"zarf.helm-693435256",
+		"zarf.helm-693435256",
 	}
 
 	for idx, url := range gitURLs {
@@ -201,9 +201,9 @@ func TestGitURL(t *testing.T) {
 	var expectedResult = []string{
 		// Normal git repos and references for pushing/pulling
 		"https://gitlab.com/repo-owner/twistlock-97328248.git",
-		"https://gitlab.com/repo-owner/zarf-1211668992.git",
+		"https://gitlab.com/repo-owner/zarf-4156197301.git",
 		"https://gitlab.com/repo-owner/podinfo_fasd-123-84577122.git",
-		"https://gitlab.com/repo-owner/zarf-agent-3633494462",
+		"https://gitlab.com/repo-owner/zarf-agent-1776579160",
 		"https://gitlab.com/repo-owner/some-cool-repo-926913879",
 		"https://gitlab.com/repo-owner/podinfo-2985051089",
 		"https://gitlab.com/repo-owner/podinfo-2197246515.git",
@@ -211,19 +211,19 @@ func TestGitURL(t *testing.T) {
 		"https://gitlab.com/repo-owner/podinfo-1175499642",
 		"https://gitlab.com/repo-owner/zarf-public-test-2170732467",
 		"https://gitlab.com/repo-owner/zarf-public-test-2170732467",
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git",
-		"https://gitlab.com/repo-owner/zarf-1211668992.git",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git",
+		"https://gitlab.com/repo-owner/zarf-4156197301.git",
 		"https://gitlab.com/repo-owner/big-bang-2366614037.git",
 		"https://gitlab.com/repo-owner/helm-charts-3648076006.git",
 		"https://gitlab.com/repo-owner/prometheus-community-2749132599",
 		"https://gitlab.com/repo-owner/-98306241",
 
 		// Smart Git Protocol URLs for proxying (https://www.git-scm.com/docs/http-protocol)
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git/info/refs",
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git/info/refs?service=git-upload-pack",
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git/info/refs?service=git-receive-pack",
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git/git-upload-pack",
-		"https://gitlab.com/repo-owner/zarf.helm-842267124.git/git-receive-pack",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git/info/refs",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git/info/refs?service=git-upload-pack",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git/info/refs?service=git-receive-pack",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git/git-upload-pack",
+		"https://gitlab.com/repo-owner/zarf.helm-693435256.git/git-receive-pack",
 	}
 
 	for idx, url := range gitURLs {
diff --git a/src/pkg/transform/image_test.go b/src/pkg/transform/image_test.go
index cc61154efa..13fd300c25 100644
--- a/src/pkg/transform/image_test.go
+++ b/src/pkg/transform/image_test.go
@@ -13,11 +13,11 @@ import (
 var imageRefs = []string{
 	"nginx",
 	"nginx:1.23.3",
-	"defenseunicorns/zarf-agent:v0.22.1",
-	"defenseunicorns/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
+	"zarf-dev/zarf-agent:v0.22.1",
+	"zarf-dev/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
 	"busybox:latest@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79",
 	"ghcr.io/stefanprodan/podinfo:6.3.3",
-	"registry1.dso.mil/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0",
+	"registry1.dso.mil/ironbank/opensource/zarf-dev/zarf/zarf-agent:v0.25.0",
 	"gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023",
 	"oci://10.43.130.183:5000/stefanprodan/manifests/podinfo",
 }
@@ -33,11 +33,11 @@ func TestImageTransformHost(t *testing.T) {
 		// Normal git repos and references for pushing/pulling
 		"gitlab.com/project/library/nginx:latest-zarf-3793515731",
 		"gitlab.com/project/library/nginx:1.23.3-zarf-3793515731",
-		"gitlab.com/project/defenseunicorns/zarf-agent:v0.22.1-zarf-4283503412",
-		"gitlab.com/project/defenseunicorns/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
+		"gitlab.com/project/zarf-dev/zarf-agent:v0.22.1-zarf-2183797434",
+		"gitlab.com/project/zarf-dev/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
 		"gitlab.com/project/library/busybox@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79",
 		"gitlab.com/project/stefanprodan/podinfo:6.3.3-zarf-2985051089",
-		"gitlab.com/project/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0-zarf-2003217571",
+		"gitlab.com/project/ironbank/opensource/zarf-dev/zarf/zarf-agent:v0.25.0-zarf-1211467612",
 		"gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023",
 		"gitlab.com/project/stefanprodan/manifests/podinfo:latest-zarf-531355090",
 	}
@@ -58,11 +58,11 @@ func TestImageTransformHostWithoutChecksum(t *testing.T) {
 	var expectedResult = []string{
 		"gitlab.com/project/library/nginx:latest",
 		"gitlab.com/project/library/nginx:1.23.3",
-		"gitlab.com/project/defenseunicorns/zarf-agent:v0.22.1",
-		"gitlab.com/project/defenseunicorns/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
+		"gitlab.com/project/zarf-dev/zarf-agent:v0.22.1",
+		"gitlab.com/project/zarf-dev/zarf-agent@sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de",
 		"gitlab.com/project/library/busybox@sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79",
 		"gitlab.com/project/stefanprodan/podinfo:6.3.3",
-		"gitlab.com/project/ironbank/opensource/defenseunicorns/zarf/zarf-agent:v0.25.0",
+		"gitlab.com/project/ironbank/opensource/zarf-dev/zarf/zarf-agent:v0.25.0",
 		"gitlab.com/project/gitea/gitea:1.19.3-rootless-zarf-3431384023",
 		"gitlab.com/project/stefanprodan/manifests/podinfo:latest",
 	}
@@ -83,11 +83,11 @@ func TestParseImageRef(t *testing.T) {
 	var expectedResult = [][]string{
 		{"docker.io/", "library/nginx", "latest", ""},
 		{"docker.io/", "library/nginx", "1.23.3", ""},
-		{"docker.io/", "defenseunicorns/zarf-agent", "v0.22.1", ""},
-		{"docker.io/", "defenseunicorns/zarf-agent", "", "sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de"},
+		{"docker.io/", "zarf-dev/zarf-agent", "v0.22.1", ""},
+		{"docker.io/", "zarf-dev/zarf-agent", "", "sha256:84605f731c6a18194794c51e70021c671ab064654b751aa57e905bce55be13de"},
 		{"docker.io/", "library/busybox", "latest", "sha256:3fbc632167424a6d997e74f52b878d7cc478225cffac6bc977eedfe51c7f4e79"},
 		{"ghcr.io/", "stefanprodan/podinfo", "6.3.3", ""},
-		{"registry1.dso.mil/", "ironbank/opensource/defenseunicorns/zarf/zarf-agent", "v0.25.0", ""},
+		{"registry1.dso.mil/", "ironbank/opensource/zarf-dev/zarf/zarf-agent", "v0.25.0", ""},
 		{"gitlab.com/", "project/gitea/gitea", "1.19.3-rootless-zarf-3431384023", ""},
 		{"10.43.130.183:5000/", "stefanprodan/manifests/podinfo", "latest", ""},
 	}
diff --git a/src/pkg/utils/auth.go b/src/pkg/utils/auth.go
index 69a313cc13..c66b29b548 100644
--- a/src/pkg/utils/auth.go
+++ b/src/pkg/utils/auth.go
@@ -12,8 +12,8 @@ import (
 	"path/filepath"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // Credential represents authentication for a given host.
diff --git a/src/pkg/utils/auth_test.go b/src/pkg/utils/auth_test.go
index 6260e7af0d..3493cdf919 100644
--- a/src/pkg/utils/auth_test.go
+++ b/src/pkg/utils/auth_test.go
@@ -7,9 +7,9 @@ package utils
 import (
 	"testing"
 
-	mocks "github.com/defenseunicorns/zarf/src/test/mocks"
 	"github.com/go-git/go-git/v5/plumbing/transport/http"
 	"github.com/stretchr/testify/require"
+	mocks "github.com/zarf-dev/zarf/src/test/mocks"
 )
 
 func TestCredentialParser(t *testing.T) {
diff --git a/src/pkg/utils/bytes.go b/src/pkg/utils/bytes.go
index 4ff6c06d59..032cc65334 100644
--- a/src/pkg/utils/bytes.go
+++ b/src/pkg/utils/bytes.go
@@ -13,7 +13,7 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // RoundUp rounds a float64 to the given number of decimal places.
diff --git a/src/pkg/utils/cosign.go b/src/pkg/utils/cosign.go
index fc66d92ba9..ae8e553cda 100644
--- a/src/pkg/utils/cosign.go
+++ b/src/pkg/utils/cosign.go
@@ -12,13 +12,13 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/name"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	"github.com/pkg/errors"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 
 	"github.com/sigstore/cosign/v2/cmd/cosign/cli/fulcio"
 	"github.com/sigstore/cosign/v2/cmd/cosign/cli/options"
diff --git a/src/pkg/utils/image.go b/src/pkg/utils/image.go
index a6490375dc..828fea84ad 100644
--- a/src/pkg/utils/image.go
+++ b/src/pkg/utils/image.go
@@ -11,10 +11,10 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/layout"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
 )
 
 // LoadOCIImage returns a v1.Image with the image ref specified from a location provided, or an error if the image cannot be found.
diff --git a/src/pkg/utils/io.go b/src/pkg/utils/io.go
index 16c1abf377..06885d623a 100755
--- a/src/pkg/utils/io.go
+++ b/src/pkg/utils/io.go
@@ -10,8 +10,8 @@ import (
 	"path/filepath"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 const (
diff --git a/src/pkg/utils/network.go b/src/pkg/utils/network.go
index 8a4a93821f..e17c086161 100644
--- a/src/pkg/utils/network.go
+++ b/src/pkg/utils/network.go
@@ -15,8 +15,8 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 func parseChecksum(src string) (string, string, error) {
diff --git a/src/pkg/utils/network_test.go b/src/pkg/utils/network_test.go
index c2f059d7fe..d2357f907d 100644
--- a/src/pkg/utils/network_test.go
+++ b/src/pkg/utils/network_test.go
@@ -21,7 +21,7 @@ import (
 func TestParseChecksum(t *testing.T) {
 	t.Parallel()
 
-	adr := "https://raw.githubusercontent.com/defenseunicorns/zarf/main/.adr-dir"
+	adr := "https://raw.githubusercontent.com/zarf-dev/zarf/main/.adr-dir"
 	sum := "930f4d5a191812e57b39bd60fca789ace07ec5acd36d63e1047604c8bdf998a3"
 
 	tests := []struct {
diff --git a/src/pkg/utils/wait.go b/src/pkg/utils/wait.go
index 4ccc3d2481..fd2d7f1422 100644
--- a/src/pkg/utils/wait.go
+++ b/src/pkg/utils/wait.go
@@ -14,9 +14,9 @@ import (
 	"strings"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 // isJSONPathWaitType checks if the condition is a JSONPath or condition.
diff --git a/src/pkg/utils/wait_test.go b/src/pkg/utils/wait_test.go
index 1c66400ae1..30ca050a1e 100644
--- a/src/pkg/utils/wait_test.go
+++ b/src/pkg/utils/wait_test.go
@@ -7,9 +7,9 @@ package utils
 import (
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 type TestIsJSONPathWaitTypeSuite struct {
diff --git a/src/pkg/utils/yaml.go b/src/pkg/utils/yaml.go
index bc49d46502..e61083bc51 100644
--- a/src/pkg/utils/yaml.go
+++ b/src/pkg/utils/yaml.go
@@ -15,13 +15,13 @@ import (
 	"regexp"
 	"strings"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	"github.com/fatih/color"
 	goyaml "github.com/goccy/go-yaml"
 	"github.com/goccy/go-yaml/lexer"
 	"github.com/goccy/go-yaml/printer"
 	"github.com/pterm/pterm"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
 	kubeyaml "k8s.io/apimachinery/pkg/util/yaml"
diff --git a/src/pkg/variables/types.go b/src/pkg/variables/types.go
index f6f51a6917..3fb8d18d80 100644
--- a/src/pkg/variables/types.go
+++ b/src/pkg/variables/types.go
@@ -8,7 +8,7 @@ import (
 	"fmt"
 	"regexp"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 // VariableType represents a type of a Zarf package variable
diff --git a/src/pkg/zoci/common.go b/src/pkg/zoci/common.go
index 8a19d58cc7..41cf415d1b 100644
--- a/src/pkg/zoci/common.go
+++ b/src/pkg/zoci/common.go
@@ -8,9 +8,9 @@ import (
 	"log/slog"
 
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 )
 
 const (
diff --git a/src/pkg/zoci/copier.go b/src/pkg/zoci/copier.go
index d6ed8dffb4..74c8636a74 100644
--- a/src/pkg/zoci/copier.go
+++ b/src/pkg/zoci/copier.go
@@ -10,8 +10,8 @@ import (
 	"fmt"
 
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/message"
 	"oras.land/oras-go/v2/content"
 )
 
diff --git a/src/pkg/zoci/fetch.go b/src/pkg/zoci/fetch.go
index 33f155059f..3a2d04bb9f 100644
--- a/src/pkg/zoci/fetch.go
+++ b/src/pkg/zoci/fetch.go
@@ -8,9 +8,9 @@ import (
 	"context"
 
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/types"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // FetchZarfYAML fetches the zarf.yaml file from the remote repository.
diff --git a/src/pkg/zoci/pull.go b/src/pkg/zoci/pull.go
index bd259259e1..c74e66588d 100644
--- a/src/pkg/zoci/pull.go
+++ b/src/pkg/zoci/pull.go
@@ -11,11 +11,11 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 	"oras.land/oras-go/v2/content/file"
 )
 
diff --git a/src/pkg/zoci/push.go b/src/pkg/zoci/push.go
index 19ee37a761..63a80b425f 100644
--- a/src/pkg/zoci/push.go
+++ b/src/pkg/zoci/push.go
@@ -10,10 +10,10 @@ import (
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/types"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/types"
 	"oras.land/oras-go/v2"
 	"oras.land/oras-go/v2/content/file"
 )
diff --git a/src/pkg/zoci/utils.go b/src/pkg/zoci/utils.go
index bcd5da45d4..e9ac1f57e3 100644
--- a/src/pkg/zoci/utils.go
+++ b/src/pkg/zoci/utils.go
@@ -10,7 +10,7 @@ import (
 	"strings"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/types"
+	"github.com/zarf-dev/zarf/src/types"
 	"oras.land/oras-go/v2/registry"
 )
 
diff --git a/src/test/common.go b/src/test/common.go
index 70492f014c..0339fe0f48 100644
--- a/src/test/common.go
+++ b/src/test/common.go
@@ -17,8 +17,8 @@ import (
 	"slices"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 // ZarfE2ETest Struct holding common fields most of the tests will utilize.
diff --git a/src/test/e2e/05_tarball_test.go b/src/test/e2e/05_tarball_test.go
index e4ebfde06d..ab91e06e4d 100644
--- a/src/test/e2e/05_tarball_test.go
+++ b/src/test/e2e/05_tarball_test.go
@@ -12,10 +12,10 @@ import (
 	"testing"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestMultiPartPackage(t *testing.T) {
diff --git a/src/test/e2e/07_create_git_test.go b/src/test/e2e/07_create_git_test.go
index 6c1e3a38c6..af475a3593 100644
--- a/src/test/e2e/07_create_git_test.go
+++ b/src/test/e2e/07_create_git_test.go
@@ -9,8 +9,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 func TestCreateGit(t *testing.T) {
diff --git a/src/test/e2e/08_create_differential_test.go b/src/test/e2e/08_create_differential_test.go
index ff0a1a698c..d692d1b9cc 100644
--- a/src/test/e2e/08_create_differential_test.go
+++ b/src/test/e2e/08_create_differential_test.go
@@ -9,12 +9,12 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/mholt/archiver/v3"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 // TestCreateDifferential creates several differential packages and ensures the reference package images and repos are not included in the new package.
@@ -63,7 +63,7 @@ func TestCreateDifferential(t *testing.T) {
 	expectedGitRepos := []string{
 		"https://github.com/stefanprodan/podinfo.git",
 		"https://github.com/kelseyhightower/nocode.git",
-		"https://github.com/defenseunicorns/zarf.git@refs/tags/v0.26.0",
+		"https://github.com/zarf-dev/zarf.git@refs/tags/v0.26.0",
 	}
 	require.Len(t, actualGitRepos, 4, "zarf.yaml from the differential package does not contain the correct number of repos")
 	for _, expectedRepo := range expectedGitRepos {
diff --git a/src/test/e2e/12_lint_test.go b/src/test/e2e/12_lint_test.go
index dc31217601..a577bb397f 100644
--- a/src/test/e2e/12_lint_test.go
+++ b/src/test/e2e/12_lint_test.go
@@ -9,8 +9,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config/lang"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 func TestLint(t *testing.T) {
diff --git a/src/test/e2e/13_zarf_package_generate_test.go b/src/test/e2e/13_zarf_package_generate_test.go
index ad613ba96a..305141739c 100644
--- a/src/test/e2e/13_zarf_package_generate_test.go
+++ b/src/test/e2e/13_zarf_package_generate_test.go
@@ -8,10 +8,10 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestZarfDevGenerate(t *testing.T) {
diff --git a/src/test/e2e/20_zarf_init_test.go b/src/test/e2e/20_zarf_init_test.go
index f54fc54f85..572cedea0f 100644
--- a/src/test/e2e/20_zarf_init_test.go
+++ b/src/test/e2e/20_zarf_init_test.go
@@ -12,8 +12,8 @@ import (
 
 	"encoding/json"
 
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestZarfInit(t *testing.T) {
diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go
index 8a2a1f65ba..b96d6c579b 100644
--- a/src/test/e2e/21_connect_creds_test.go
+++ b/src/test/e2e/21_connect_creds_test.go
@@ -13,8 +13,8 @@ import (
 	"strings"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
 )
 
 type RegistryResponse struct {
diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go
index 572702dbe4..559bb79e3f 100644
--- a/src/test/e2e/22_git_and_gitops_test.go
+++ b/src/test/e2e/22_git_and_gitops_test.go
@@ -13,10 +13,10 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/internal/packager/git"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/internal/packager/git"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/types"
 )
 
 func TestGit(t *testing.T) {
diff --git a/src/test/e2e/23_data_injection_test.go b/src/test/e2e/23_data_injection_test.go
index 9e4bbef7c3..08f913fc41 100644
--- a/src/test/e2e/23_data_injection_test.go
+++ b/src/test/e2e/23_data_injection_test.go
@@ -12,9 +12,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 func TestDataInjection(t *testing.T) {
diff --git a/src/test/e2e/25_helm_test.go b/src/test/e2e/25_helm_test.go
index 280ba3d7ec..071732da10 100644
--- a/src/test/e2e/25_helm_test.go
+++ b/src/test/e2e/25_helm_test.go
@@ -132,7 +132,7 @@ func testHelmUninstallRollback(t *testing.T) {
 	// Ensure this leaves behind a dos-games chart.
 	// We do not want to uninstall charts that had failed installs/upgrades
 	// to prevent unintentional deletion and/or data loss in production environments.
-	// https://github.com/defenseunicorns/zarf/issues/2455
+	// https://github.com/zarf-dev/zarf/issues/2455
 	helmOut, err := exec.Command("helm", "list", "-n", "dos-games").Output()
 	require.NoError(t, err)
 	require.Contains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866")
diff --git a/src/test/e2e/26_simple_packages_test.go b/src/test/e2e/26_simple_packages_test.go
index 97a67423e3..05a95d97d2 100644
--- a/src/test/e2e/26_simple_packages_test.go
+++ b/src/test/e2e/26_simple_packages_test.go
@@ -11,8 +11,8 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
 )
 
 func TestDosGames(t *testing.T) {
diff --git a/src/test/e2e/27_deploy_regression_test.go b/src/test/e2e/27_deploy_regression_test.go
index fec8d55433..49808addff 100644
--- a/src/test/e2e/27_deploy_regression_test.go
+++ b/src/test/e2e/27_deploy_regression_test.go
@@ -9,8 +9,8 @@ import (
 	"fmt"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 )
 
 func TestGHCRDeploy(t *testing.T) {
diff --git a/src/test/e2e/28_wait_test.go b/src/test/e2e/28_wait_test.go
index 0de68f243e..e1b22b4f28 100644
--- a/src/test/e2e/28_wait_test.go
+++ b/src/test/e2e/28_wait_test.go
@@ -10,8 +10,8 @@ import (
 
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/test"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/test"
 )
 
 type zarfCommandResult struct {
diff --git a/src/test/e2e/50_oci_publish_deploy_test.go b/src/test/e2e/50_oci_publish_deploy_test.go
index f3d5d89428..81e5e39f54 100644
--- a/src/test/e2e/50_oci_publish_deploy_test.go
+++ b/src/test/e2e/50_oci_publish_deploy_test.go
@@ -13,9 +13,9 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/oci"
-	"github.com/defenseunicorns/zarf/src/pkg/zoci"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+	"github.com/zarf-dev/zarf/src/pkg/zoci"
 	"oras.land/oras-go/v2/registry"
 	"oras.land/oras-go/v2/registry/remote"
 )
diff --git a/src/test/e2e/51_oci_compose_test.go b/src/test/e2e/51_oci_compose_test.go
index 124dbb953a..b38db002a6 100644
--- a/src/test/e2e/51_oci_compose_test.go
+++ b/src/test/e2e/51_oci_compose_test.go
@@ -13,12 +13,12 @@ import (
 	"testing"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/layout"
-	"github.com/defenseunicorns/zarf/src/pkg/transform"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/types"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+	"github.com/zarf-dev/zarf/src/pkg/layout"
+	"github.com/zarf-dev/zarf/src/pkg/transform"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/types"
 	corev1 "k8s.io/api/core/v1"
 	"oras.land/oras-go/v2/registry"
 )
diff --git a/src/test/e2e/99_yolo_test.go b/src/test/e2e/99_yolo_test.go
index 01671b8600..93a9b9ed60 100644
--- a/src/test/e2e/99_yolo_test.go
+++ b/src/test/e2e/99_yolo_test.go
@@ -10,8 +10,8 @@ import (
 	"net/http"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
 )
 
 func TestYOLOMode(t *testing.T) {
diff --git a/src/test/e2e/main_test.go b/src/test/e2e/main_test.go
index 6c9c283a55..9fe02d40df 100644
--- a/src/test/e2e/main_test.go
+++ b/src/test/e2e/main_test.go
@@ -10,9 +10,9 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/pkg/message"
-	"github.com/defenseunicorns/zarf/src/test"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/pkg/message"
+	"github.com/zarf-dev/zarf/src/test"
 )
 
 var (
diff --git a/src/test/external/common.go b/src/test/external/common.go
index 2e9cd8bb68..36e8405a25 100644
--- a/src/test/external/common.go
+++ b/src/test/external/common.go
@@ -12,10 +12,10 @@ import (
 	"testing"
 	"time"
 
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	"github.com/defenseunicorns/zarf/src/test"
 	"github.com/otiai10/copy"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/test"
 )
 
 var zarfBinPath = path.Join("../../../build", test.GetCLIName())
diff --git a/src/test/external/ext_in_cluster_test.go b/src/test/external/ext_in_cluster_test.go
index 655cb7c59a..97a52fda0a 100644
--- a/src/test/external/ext_in_cluster_test.go
+++ b/src/test/external/ext_in_cluster_test.go
@@ -15,10 +15,10 @@ import (
 	"time"
 
 	pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes"
-	"github.com/defenseunicorns/zarf/src/pkg/cluster"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+	"github.com/zarf-dev/zarf/src/pkg/cluster"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"sigs.k8s.io/cli-utils/pkg/object"
 )
diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go
index b32960c318..dfcc9da580 100644
--- a/src/test/external/ext_out_cluster_test.go
+++ b/src/test/external/ext_out_cluster_test.go
@@ -16,10 +16,10 @@ import (
 	"testing"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/pkg/utils"
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
 	"github.com/stretchr/testify/require"
 	"github.com/stretchr/testify/suite"
+	"github.com/zarf-dev/zarf/src/pkg/utils"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
 	"helm.sh/helm/v3/pkg/repo"
 )
 
diff --git a/src/test/nightly/ecr_publish_test.go b/src/test/nightly/ecr_publish_test.go
index 8d696660c8..7716ebf270 100644
--- a/src/test/nightly/ecr_publish_test.go
+++ b/src/test/nightly/ecr_publish_test.go
@@ -11,9 +11,9 @@ import (
 	"path/filepath"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/config"
-	"github.com/defenseunicorns/zarf/src/test"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config"
+	"github.com/zarf-dev/zarf/src/test"
 )
 
 var (
diff --git a/src/test/packages/08-differential-package/zarf.yaml b/src/test/packages/08-differential-package/zarf.yaml
index c1e5d4ab71..5f5fae02c0 100644
--- a/src/test/packages/08-differential-package/zarf.yaml
+++ b/src/test/packages/08-differential-package/zarf.yaml
@@ -11,8 +11,8 @@ components:
       - ghcr.io/stefanprodan/podinfo:6.0.0
       - ghcr.io/zarf-dev/zarf/agent:###ZARF_PKG_TMPL_PACKAGE_VERSION###
     repos:
-      - https://github.com/defenseunicorns/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e
-      - https://github.com/defenseunicorns/zarf.git@refs/tags/###ZARF_PKG_TMPL_PACKAGE_VERSION###
+      - https://github.com/zarf-dev/zarf.git@c74e2e9626da0400e0a41e78319b3054c53a5d4e
+      - https://github.com/zarf-dev/zarf.git@refs/tags/###ZARF_PKG_TMPL_PACKAGE_VERSION###
 
   - name: generalized-assets
     required: true
@@ -22,4 +22,4 @@ components:
       # Do a full Git Repo Mirror
       - https://github.com/stefanprodan/podinfo.git
       - https://github.com/kelseyhightower/nocode.git
-      - https://github.com/defenseunicorns/zarf.git@refs/heads/main
+      - https://github.com/zarf-dev/zarf.git@refs/heads/main
diff --git a/src/test/packages/51-import-everything/zarf.yaml b/src/test/packages/51-import-everything/zarf.yaml
index b69ef73086..2f29435f1e 100644
--- a/src/test/packages/51-import-everything/zarf.yaml
+++ b/src/test/packages/51-import-everything/zarf.yaml
@@ -39,7 +39,7 @@ components:
       - source: ../09-composable-packages/files/coffee-ipsum.txt
         target: ../09-composable-packages/coffee-ipsum.txt
         # Import of a file from a URL
-      - source: https://raw.githubusercontent.com/defenseunicorns/zarf/main/README.md
+      - source: https://raw.githubusercontent.com/zarf-dev/zarf/main/README.md
         target: files/zarf-readme.md
     actions:
       onDeploy:
diff --git a/src/test/upgrade/previously_built_test.go b/src/test/upgrade/previously_built_test.go
index 179bad0432..a7c42e87a1 100644
--- a/src/test/upgrade/previously_built_test.go
+++ b/src/test/upgrade/previously_built_test.go
@@ -9,9 +9,9 @@ import (
 	"path"
 	"testing"
 
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	test "github.com/defenseunicorns/zarf/src/test"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	test "github.com/zarf-dev/zarf/src/test"
 )
 
 func kubectl(args ...string) (string, string, error) {
diff --git a/src/types/component.go b/src/types/component.go
index 958d60e74d..4ccf392970 100644
--- a/src/types/component.go
+++ b/src/types/component.go
@@ -5,10 +5,10 @@
 package types
 
 import (
-	"github.com/defenseunicorns/zarf/src/pkg/utils/exec"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
-	"github.com/defenseunicorns/zarf/src/types/extensions"
 	"github.com/invopop/jsonschema"
+	"github.com/zarf-dev/zarf/src/pkg/utils/exec"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
+	"github.com/zarf-dev/zarf/src/types/extensions"
 )
 
 // ZarfComponent is the primary functional grouping of assets to deploy by Zarf.
diff --git a/src/types/k8s.go b/src/types/k8s.go
index 2c2c6b3527..a8f61856ce 100644
--- a/src/types/k8s.go
+++ b/src/types/k8s.go
@@ -9,7 +9,7 @@ import (
 	"time"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 // WebhookStatus defines the status of a Component Webhook operating on a Zarf package secret.
diff --git a/src/types/package.go b/src/types/package.go
index cff00da592..c78f1fe461 100644
--- a/src/types/package.go
+++ b/src/types/package.go
@@ -4,7 +4,7 @@
 // Package types contains all the types used by Zarf.
 package types
 
-import "github.com/defenseunicorns/zarf/src/pkg/variables"
+import "github.com/zarf-dev/zarf/src/pkg/variables"
 
 // ZarfPackageKind is an enum of the different kinds of Zarf packages.
 type ZarfPackageKind string
diff --git a/src/types/validate.go b/src/types/validate.go
index 131c556e39..4d39d36b23 100644
--- a/src/types/validate.go
+++ b/src/types/validate.go
@@ -12,7 +12,7 @@ import (
 	"slices"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/config/lang"
 )
 
 const (
diff --git a/src/types/validate_test.go b/src/types/validate_test.go
index 64af534f58..f283302675 100644
--- a/src/types/validate_test.go
+++ b/src/types/validate_test.go
@@ -11,9 +11,9 @@ import (
 	"testing"
 
 	"github.com/defenseunicorns/pkg/helpers/v2"
-	"github.com/defenseunicorns/zarf/src/config/lang"
-	"github.com/defenseunicorns/zarf/src/pkg/variables"
 	"github.com/stretchr/testify/require"
+	"github.com/zarf-dev/zarf/src/config/lang"
+	"github.com/zarf-dev/zarf/src/pkg/variables"
 )
 
 func TestZarfPackageValidate(t *testing.T) {
diff --git a/zarf.schema.json b/zarf.schema.json
index 3004a424b1..8658787b3b 100644
--- a/zarf.schema.json
+++ b/zarf.schema.json
@@ -1,6 +1,6 @@
 {
   "$schema": "https://json-schema.org/draft/2020-12/schema",
-  "$id": "https://github.com/defenseunicorns/zarf/src/types/zarf-package",
+  "$id": "https://github.com/zarf-dev/zarf/src/types/zarf-package",
   "$defs": {
     "BigBang": {
       "properties": {

From 290d0662ec77a02af51a90001b1bc14d2acbf295 Mon Sep 17 00:00:00 2001
From: schristoff <28318173+schristoff@users.noreply.github.com>
Date: Sun, 21 Jul 2024 16:04:50 -0400
Subject: [PATCH 126/132] chore: move context.TODO to context.Background()
 (#2742)

Signed-off-by: schristoff-du <167717759+schristoff-du@users.noreply.github.com>
Co-authored-by: schristoff-du <167717759+schristoff-du@users.noreply.github.com>
---
 src/cmd/dev.go                     |  4 ++--
 src/extensions/bigbang/bigbang.go  | 11 ++++++-----
 src/internal/packager/git/clone.go | 10 +++++-----
 src/internal/packager/git/pull.go  |  9 +++++----
 src/internal/packager/helm/repo.go | 13 +++++++------
 src/pkg/packager/creator/normal.go | 14 +++++++-------
 src/pkg/packager/generate.go       |  5 +++--
 src/pkg/packager/prepare.go        |  6 +++---
 8 files changed, 38 insertions(+), 34 deletions(-)

diff --git a/src/cmd/dev.go b/src/cmd/dev.go
index c31e0d5fe8..b10f2dde51 100644
--- a/src/cmd/dev.go
+++ b/src/cmd/dev.go
@@ -71,7 +71,7 @@ var devGenerateCmd = &cobra.Command{
 	Args:    cobra.ExactArgs(1),
 	Short:   lang.CmdDevGenerateShort,
 	Example: lang.CmdDevGenerateExample,
-	RunE: func(_ *cobra.Command, args []string) error {
+	RunE: func(cmd *cobra.Command, args []string) error {
 		pkgConfig.GenerateOpts.Name = args[0]
 
 		pkgConfig.CreateOpts.BaseDir = "."
@@ -83,7 +83,7 @@ var devGenerateCmd = &cobra.Command{
 		}
 		defer pkgClient.ClearTempPaths()
 
-		err = pkgClient.Generate()
+		err = pkgClient.Generate(cmd.Context())
 		if err != nil {
 			return err
 		}
diff --git a/src/extensions/bigbang/bigbang.go b/src/extensions/bigbang/bigbang.go
index b97c8f6e25..e312f382f9 100644
--- a/src/extensions/bigbang/bigbang.go
+++ b/src/extensions/bigbang/bigbang.go
@@ -5,6 +5,7 @@
 package bigbang
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"path"
@@ -42,7 +43,7 @@ var tenMins = metav1.Duration{
 
 // Run mutates a component that should deploy Big Bang to a set of manifests
 // that contain the flux deployment of Big Bang
-func Run(YOLO bool, tmpPaths *layout.ComponentPaths, c types.ZarfComponent) (types.ZarfComponent, error) {
+func Run(ctx context.Context, YOLO bool, tmpPaths *layout.ComponentPaths, c types.ZarfComponent) (types.ZarfComponent, error) {
 	cfg := c.Extensions.BigBang
 	manifests := []types.ZarfManifest{}
 
@@ -99,7 +100,7 @@ func Run(YOLO bool, tmpPaths *layout.ComponentPaths, c types.ZarfComponent) (typ
 	)
 
 	// Download the chart from Git and save it to a temporary directory.
-	err = helmCfg.PackageChartFromGit(c.DeprecatedCosignKeyPath)
+	err = helmCfg.PackageChartFromGit(ctx, c.DeprecatedCosignKeyPath)
 	if err != nil {
 		return c, fmt.Errorf("unable to download Big Bang Chart: %w", err)
 	}
@@ -220,7 +221,7 @@ func Run(YOLO bool, tmpPaths *layout.ComponentPaths, c types.ZarfComponent) (typ
 			gitRepo := gitRepos[hr.NamespacedSource]
 			values := hrValues[namespacedName]
 
-			images, err := findImagesforBBChartRepo(gitRepo, values)
+			images, err := findImagesforBBChartRepo(ctx, gitRepo, values)
 			if err != nil {
 				return c, fmt.Errorf("unable to find images for chart repo: %w", err)
 			}
@@ -523,7 +524,7 @@ func addBigBangManifests(YOLO bool, manifestDir string, cfg *extensions.BigBang)
 }
 
 // findImagesforBBChartRepo finds and returns the images for the Big Bang chart repo
-func findImagesforBBChartRepo(repo string, values chartutil.Values) (images []string, err error) {
+func findImagesforBBChartRepo(ctx context.Context, repo string, values chartutil.Values) (images []string, err error) {
 	matches := strings.Split(repo, "@")
 	if len(matches) < 2 {
 		return images, fmt.Errorf("cannot convert git repo %s to helm chart without a version tag", repo)
@@ -532,7 +533,7 @@ func findImagesforBBChartRepo(repo string, values chartutil.Values) (images []st
 	spinner := message.NewProgressSpinner("Discovering images in %s", repo)
 	defer spinner.Stop()
 
-	gitPath, err := helm.DownloadChartFromGitToTemp(repo, spinner)
+	gitPath, err := helm.DownloadChartFromGitToTemp(ctx, repo, spinner)
 	if err != nil {
 		return images, err
 	}
diff --git a/src/internal/packager/git/clone.go b/src/internal/packager/git/clone.go
index 6e70077511..bd38cbcdd4 100644
--- a/src/internal/packager/git/clone.go
+++ b/src/internal/packager/git/clone.go
@@ -18,7 +18,7 @@ import (
 )
 
 // clone performs a `git clone` of a given repo.
-func (g *Git) clone(gitURL string, ref plumbing.ReferenceName, shallow bool) error {
+func (g *Git) clone(ctx context.Context, gitURL string, ref plumbing.ReferenceName, shallow bool) error {
 	cloneOptions := &git.CloneOptions{
 		URL:        gitURL,
 		Progress:   g.Spinner,
@@ -47,7 +47,7 @@ func (g *Git) clone(gitURL string, ref plumbing.ReferenceName, shallow bool) err
 	repo, err := git.PlainClone(g.GitPath, false, cloneOptions)
 	if err != nil {
 		message.Notef("Falling back to host 'git', failed to clone the repo %q with Zarf: %s", gitURL, err.Error())
-		return g.gitCloneFallback(gitURL, ref, shallow)
+		return g.gitCloneFallback(ctx, gitURL, ref, shallow)
 	}
 
 	// If we're cloning the whole repo, we need to also fetch the other branches besides the default.
@@ -72,7 +72,7 @@ func (g *Git) clone(gitURL string, ref plumbing.ReferenceName, shallow bool) err
 }
 
 // gitCloneFallback is a fallback if go-git fails to clone a repo.
-func (g *Git) gitCloneFallback(gitURL string, ref plumbing.ReferenceName, shallow bool) error {
+func (g *Git) gitCloneFallback(ctx context.Context, gitURL string, ref plumbing.ReferenceName, shallow bool) error {
 	// If we can't clone with go-git, fallback to the host clone
 	// Only support "all tags" due to the azure clone url format including a username
 	cloneArgs := []string{"clone", "--origin", onlineRemoteName, gitURL, g.GitPath}
@@ -96,7 +96,7 @@ func (g *Git) gitCloneFallback(gitURL string, ref plumbing.ReferenceName, shallo
 
 	message.Command("git %s", strings.Join(cloneArgs, " "))
 
-	_, _, err := exec.CmdWithContext(context.TODO(), cloneExecConfig, "git", cloneArgs...)
+	_, _, err := exec.CmdWithContext(ctx, cloneExecConfig, "git", cloneArgs...)
 	if err != nil {
 		return err
 	}
@@ -113,7 +113,7 @@ func (g *Git) gitCloneFallback(gitURL string, ref plumbing.ReferenceName, shallo
 
 		message.Command("git %s", strings.Join(fetchArgs, " "))
 
-		_, _, err := exec.CmdWithContext(context.TODO(), fetchExecConfig, "git", fetchArgs...)
+		_, _, err := exec.CmdWithContext(ctx, fetchExecConfig, "git", fetchArgs...)
 		if err != nil {
 			return err
 		}
diff --git a/src/internal/packager/git/pull.go b/src/internal/packager/git/pull.go
index bd2472c08c..5d9e6f1006 100644
--- a/src/internal/packager/git/pull.go
+++ b/src/internal/packager/git/pull.go
@@ -5,6 +5,7 @@
 package git
 
 import (
+	"context"
 	"fmt"
 	"path"
 	"strings"
@@ -16,7 +17,7 @@ import (
 )
 
 // DownloadRepoToTemp clones or updates a repo into a temp folder to perform ephemeral actions (i.e. process chart repos).
-func (g *Git) DownloadRepoToTemp(gitURL string) error {
+func (g *Git) DownloadRepoToTemp(ctx context.Context, gitURL string) error {
 	g.Spinner.Updatef("g.DownloadRepoToTemp(%s)", gitURL)
 
 	path, err := utils.MakeTempDir(config.CommonOptions.TempDirectory)
@@ -26,7 +27,7 @@ func (g *Git) DownloadRepoToTemp(gitURL string) error {
 
 	// If downloading to temp, set this as a shallow clone to only pull the exact
 	// gitURL w/ ref that was specified since we will throw away git history anyway
-	if err = g.Pull(gitURL, path, true); err != nil {
+	if err = g.Pull(ctx, gitURL, path, true); err != nil {
 		return fmt.Errorf("unable to pull the git repo at %s: %w", gitURL, err)
 	}
 
@@ -34,7 +35,7 @@ func (g *Git) DownloadRepoToTemp(gitURL string) error {
 }
 
 // Pull clones or updates a git repository into the target folder.
-func (g *Git) Pull(gitURL, targetFolder string, shallow bool) error {
+func (g *Git) Pull(ctx context.Context, gitURL, targetFolder string, shallow bool) error {
 	g.Spinner.Updatef("Processing git repo %s", gitURL)
 
 	// Split the remote url and the zarf reference
@@ -59,7 +60,7 @@ func (g *Git) Pull(gitURL, targetFolder string, shallow bool) error {
 	g.GitPath = path.Join(targetFolder, repoFolder)
 
 	// Clone the git repository.
-	err = g.clone(gitURLNoRef, ref, shallow)
+	err = g.clone(ctx, gitURLNoRef, ref, shallow)
 	if err != nil {
 		return fmt.Errorf("not a valid git repo or unable to clone (%s): %w", gitURL, err)
 	}
diff --git a/src/internal/packager/helm/repo.go b/src/internal/packager/helm/repo.go
index c3ea9c646f..148a4176a4 100644
--- a/src/internal/packager/helm/repo.go
+++ b/src/internal/packager/helm/repo.go
@@ -5,6 +5,7 @@
 package helm
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -32,7 +33,7 @@ import (
 )
 
 // PackageChart creates a chart archive from a path to a chart on the host os and builds chart dependencies
-func (h *Helm) PackageChart(cosignKeyPath string) error {
+func (h *Helm) PackageChart(ctx context.Context, cosignKeyPath string) error {
 	if len(h.chart.URL) > 0 {
 		url, refPlain, err := transform.GitURLSplitRef(h.chart.URL)
 		// check if the chart is a git url with a ref (if an error is returned url will be empty)
@@ -47,7 +48,7 @@ func (h *Helm) PackageChart(cosignKeyPath string) error {
 				h.chart.URL = fmt.Sprintf("%s@%s", h.chart.URL, h.chart.Version)
 			}
 
-			err = h.PackageChartFromGit(cosignKeyPath)
+			err = h.PackageChartFromGit(ctx, cosignKeyPath)
 			if err != nil {
 				return fmt.Errorf("unable to pull the chart %q from git: %w", h.chart.Name, err)
 			}
@@ -113,12 +114,12 @@ func (h *Helm) PackageChartFromLocalFiles(cosignKeyPath string) error {
 }
 
 // PackageChartFromGit is a special implementation of chart archiving that supports the https://p1.dso.mil/#/products/big-bang/ model.
-func (h *Helm) PackageChartFromGit(cosignKeyPath string) error {
+func (h *Helm) PackageChartFromGit(ctx context.Context, cosignKeyPath string) error {
 	spinner := message.NewProgressSpinner("Processing helm chart %s", h.chart.Name)
 	defer spinner.Stop()
 
 	// Retrieve the repo containing the chart
-	gitPath, err := DownloadChartFromGitToTemp(h.chart.URL, spinner)
+	gitPath, err := DownloadChartFromGitToTemp(ctx, h.chart.URL, spinner)
 	if err != nil {
 		return err
 	}
@@ -232,12 +233,12 @@ func (h *Helm) DownloadPublishedChart(cosignKeyPath string) error {
 }
 
 // DownloadChartFromGitToTemp downloads a chart from git into a temp directory
-func DownloadChartFromGitToTemp(url string, spinner *message.Spinner) (string, error) {
+func DownloadChartFromGitToTemp(ctx context.Context, url string, spinner *message.Spinner) (string, error) {
 	// Create the Git configuration and download the repo
 	gitCfg := git.NewWithSpinner(types.GitServerInfo{}, spinner)
 
 	// Download the git repo to a temporary directory
-	err := gitCfg.DownloadRepoToTemp(url)
+	err := gitCfg.DownloadRepoToTemp(ctx, url)
 	if err != nil {
 		return "", fmt.Errorf("unable to download the git repo %s: %w", url, err)
 	}
diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go
index b8135a8c2f..3b34b7e846 100644
--- a/src/pkg/packager/creator/normal.go
+++ b/src/pkg/packager/creator/normal.go
@@ -86,7 +86,7 @@ func (pc *PackageCreator) LoadPackageDefinition(ctx context.Context, src *layout
 	warnings = append(warnings, templateWarnings...)
 
 	// After templates are filled process any create extensions
-	pkg.Components, err = pc.processExtensions(pkg.Components, src, pkg.Metadata.YOLO)
+	pkg.Components, err = pc.processExtensions(ctx, pkg.Components, src, pkg.Metadata.YOLO)
 	if err != nil {
 		return types.ZarfPackage{}, nil, err
 	}
@@ -142,7 +142,7 @@ func (pc *PackageCreator) Assemble(ctx context.Context, dst *layout.PackagePaths
 			}
 		}
 
-		if err := pc.addComponent(component, dst); err != nil {
+		if err := pc.addComponent(ctx, component, dst); err != nil {
 			onFailure()
 			return fmt.Errorf("unable to add component %q: %w", component.Name, err)
 		}
@@ -327,7 +327,7 @@ func (pc *PackageCreator) Output(ctx context.Context, dst *layout.PackagePaths,
 	return nil
 }
 
-func (pc *PackageCreator) processExtensions(components []types.ZarfComponent, layout *layout.PackagePaths, isYOLO bool) (processedComponents []types.ZarfComponent, err error) {
+func (pc *PackageCreator) processExtensions(ctx context.Context, components []types.ZarfComponent, layout *layout.PackagePaths, isYOLO bool) (processedComponents []types.ZarfComponent, err error) {
 	// Create component paths and process extensions for each component.
 	for _, c := range components {
 		componentPaths, err := layout.Components.Create(c)
@@ -337,7 +337,7 @@ func (pc *PackageCreator) processExtensions(components []types.ZarfComponent, la
 
 		// Big Bang
 		if c.Extensions.BigBang != nil {
-			if c, err = bigbang.Run(isYOLO, componentPaths, c); err != nil {
+			if c, err = bigbang.Run(ctx, isYOLO, componentPaths, c); err != nil {
 				return nil, fmt.Errorf("unable to process bigbang extension: %w", err)
 			}
 		}
@@ -348,7 +348,7 @@ func (pc *PackageCreator) processExtensions(components []types.ZarfComponent, la
 	return processedComponents, nil
 }
 
-func (pc *PackageCreator) addComponent(component types.ZarfComponent, dst *layout.PackagePaths) error {
+func (pc *PackageCreator) addComponent(ctx context.Context, component types.ZarfComponent, dst *layout.PackagePaths) error {
 	message.HeaderInfof("📦 %s COMPONENT", strings.ToUpper(component.Name))
 
 	componentPaths, err := dst.Components.Create(component)
@@ -364,7 +364,7 @@ func (pc *PackageCreator) addComponent(component types.ZarfComponent, dst *layou
 	// If any helm charts are defined, process them.
 	for _, chart := range component.Charts {
 		helmCfg := helm.New(chart, componentPaths.Charts, componentPaths.Values)
-		if err := helmCfg.PackageChart(componentPaths.Charts); err != nil {
+		if err := helmCfg.PackageChart(ctx, componentPaths.Charts); err != nil {
 			return err
 		}
 	}
@@ -514,7 +514,7 @@ func (pc *PackageCreator) addComponent(component types.ZarfComponent, dst *layou
 		for _, url := range component.Repos {
 			// Pull all the references if there is no `@` in the string.
 			gitCfg := git.NewWithSpinner(types.GitServerInfo{}, spinner)
-			if err := gitCfg.Pull(url, componentPaths.Repos, false); err != nil {
+			if err := gitCfg.Pull(ctx, url, componentPaths.Repos, false); err != nil {
 				return fmt.Errorf("unable to pull git repo %s: %w", url, err)
 			}
 		}
diff --git a/src/pkg/packager/generate.go b/src/pkg/packager/generate.go
index 905859bc53..d1fc593033 100644
--- a/src/pkg/packager/generate.go
+++ b/src/pkg/packager/generate.go
@@ -5,6 +5,7 @@
 package packager
 
 import (
+	"context"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -19,7 +20,7 @@ import (
 )
 
 // Generate generates a Zarf package definition.
-func (p *Packager) Generate() (err error) {
+func (p *Packager) Generate(ctx context.Context) (err error) {
 	generatedZarfYAMLPath := filepath.Join(p.cfg.GenerateOpts.Output, layout.ZarfYAML)
 	spinner := message.NewProgressSpinner("Generating package for %q at %s", p.cfg.GenerateOpts.Name, generatedZarfYAMLPath)
 
@@ -61,7 +62,7 @@ func (p *Packager) Generate() (err error) {
 		},
 	}
 
-	images, err := p.findImages()
+	images, err := p.findImages(ctx)
 	if err != nil {
 		// purposefully not returning error here, as we can still generate the package without images
 		message.Warnf("Unable to find images: %s", err.Error())
diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go
index c71038b2f8..340ac1c85a 100644
--- a/src/pkg/packager/prepare.go
+++ b/src/pkg/packager/prepare.go
@@ -70,10 +70,10 @@ func (p *Packager) FindImages(ctx context.Context) (map[string][]string, error)
 		message.Warn(warning)
 	}
 
-	return p.findImages()
+	return p.findImages(ctx)
 }
 
-func (p *Packager) findImages() (imgMap map[string][]string, err error) {
+func (p *Packager) findImages(ctx context.Context) (imgMap map[string][]string, err error) {
 	repoHelmChartPath := p.cfg.FindImagesOpts.RepoHelmChartPath
 	kubeVersionOverride := p.cfg.FindImagesOpts.KubeVersionOverride
 	whyImage := p.cfg.FindImagesOpts.Why
@@ -172,7 +172,7 @@ func (p *Packager) findImages() (imgMap map[string][]string, err error) {
 				helm.WithVariableConfig(p.variableConfig),
 			)
 
-			err = helmCfg.PackageChart(component.DeprecatedCosignKeyPath)
+			err = helmCfg.PackageChart(ctx, component.DeprecatedCosignKeyPath)
 			if err != nil {
 				return nil, fmt.Errorf("unable to package the chart %s: %w", chart.Name, err)
 			}

From 1d8a86b094a8bd3eeacbd21b10a68f9975aa5db1 Mon Sep 17 00:00:00 2001
From: Kenny Paul <44849153+KennyPaul@users.noreply.github.com>
Date: Sun, 21 Jul 2024 15:48:36 -0700
Subject: [PATCH 127/132] docs: charter update (#2731)

Signed-off-by: Kenny Paul 
Co-authored-by: schristoff <28318173+schristoff@users.noreply.github.com>
---
 CHARTER.pdf | Bin 0 -> 396835 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 CHARTER.pdf

diff --git a/CHARTER.pdf b/CHARTER.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..6834ea3fb555a6cf01a4ade5bd4ae63e267c342b
GIT binary patch
literal 396835
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 literal 0 HcmV?d00001 From b0c6d569354c910fbc792edc51ee11cd7f25cc03 Mon Sep 17 00:00:00 2001 From: schristoff <28318173+schristoff@users.noreply.github.com> Date: Mon, 22 Jul 2024 11:24:15 -0600 Subject: [PATCH 128/132] chore: update CODEOWNERS to protect TSC files (#2744) Signed-off-by: schristoff-du <167717759+schristoff-du@users.noreply.github.com> Co-authored-by: schristoff-du <167717759+schristoff-du@users.noreply.github.com> --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/CODEOWNERS b/CODEOWNERS index 976d425740..1f66adaee1 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,3 +3,4 @@ /CODEOWNERS @zarf-dev/tsc /cosign.pub @zarf-dev/tsc /LICENSE @zarf-dev/tsc +/CHARTER.pdf @zarf-dev/tsc From 612bd8e6254b2c7c802ec8704801d42340e33926 Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 22 Jul 2024 21:06:54 +0200 Subject: [PATCH 129/132] fix: replace debug logs with returning errors (#2719) Signed-off-by: Philip Laine --- src/cmd/destroy.go | 2 +- src/internal/agent/http/proxy.go | 4 +- src/internal/packager/git/clone.go | 5 +- src/internal/packager/helm/post-render.go | 6 +- src/pkg/packager/common.go | 5 +- src/pkg/packager/deprecated/common.go | 13 ++-- src/pkg/packager/deprecated/common_test.go | 3 +- src/pkg/utils/auth.go | 73 ++++++++++------------ src/pkg/utils/auth_test.go | 34 +++++----- src/test/mocks/read_closer_mock.go | 24 ------- 10 files changed, 81 insertions(+), 88 deletions(-) delete mode 100644 src/test/mocks/read_closer_mock.go diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index a6c45519b0..1e3d1bb646 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -68,7 +68,7 @@ var destroyCmd = &cobra.Command{ // Don't remove scripts we can't execute so the user can try to manually run continue } else if err != nil { - message.Debugf("Received error when trying to execute the script (%s): %#v", script, err) + return fmt.Errorf("received an error when executing the script %s: %w", script, err) } // Try to remove the script, but ignore any errors diff --git a/src/internal/agent/http/proxy.go b/src/internal/agent/http/proxy.go index 8a9d939507..719ef17281 100644 --- a/src/internal/agent/http/proxy.go +++ b/src/internal/agent/http/proxy.go @@ -112,7 +112,9 @@ func proxyResponseTransform(resp *http.Response) error { message.Debugf("Before Resp Location %#v", resp.Header.Get("Location")) locationURL, err := url.Parse(resp.Header.Get("Location")) - message.Debugf("%#v", err) + if err != nil { + return err + } locationURL.Path = transform.NoTransform + locationURL.Path locationURL.Host = resp.Request.Header.Get("X-Forwarded-Host") diff --git a/src/internal/packager/git/clone.go b/src/internal/packager/git/clone.go index bd38cbcdd4..2f607670e7 100644 --- a/src/internal/packager/git/clone.go +++ b/src/internal/packager/git/clone.go @@ -38,7 +38,10 @@ func (g *Git) clone(ctx context.Context, gitURL string, ref plumbing.ReferenceNa } // Setup git credentials if we have them, ignore if we don't. - gitCred := utils.FindAuthForHost(gitURL) + gitCred, err := utils.FindAuthForHost(gitURL) + if err != nil { + return err + } if gitCred != nil { cloneOptions.Auth = &gitCred.Auth } diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 5f45b437a0..8d6c157427 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -285,6 +285,10 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti return err } resource, err := dc.Resource(mapping.Resource).Namespace(deployedNamespace).Get(ctx, rawData.GetName(), metav1.GetOptions{}) + // Ignore resources that are yet to be created + if kerrors.IsNotFound(err) { + return nil + } if err != nil { return err } @@ -308,7 +312,7 @@ func (r *renderer) editHelmResources(ctx context.Context, resources []releaseuti return nil }() if err != nil { - message.Debugf("Unable to adopt resource %s: %s", rawData.GetName(), err.Error()) + return fmt.Errorf("unable to adopt the resource %s: %w", rawData.GetName(), err) } } // Finally place this back onto the output buffer diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index 3b7f59f968..d790275d12 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -192,7 +192,10 @@ func (p *Packager) attemptClusterChecks(ctx context.Context) (err error) { // Check for any breaking changes between the initialized Zarf version and this CLI if existingInitPackage, _ := p.cluster.GetDeployedPackage(ctx, "init"); existingInitPackage != nil { // Use the build version instead of the metadata since this will support older Zarf versions - deprecated.PrintBreakingChanges(os.Stderr, existingInitPackage.Data.Build.Version, config.CLIVersion) + err := deprecated.PrintBreakingChanges(os.Stderr, existingInitPackage.Data.Build.Version, config.CLIVersion) + if err != nil { + return err + } } spinner.Success() diff --git a/src/pkg/packager/deprecated/common.go b/src/pkg/packager/deprecated/common.go index f97e07bce0..e0dd708c27 100644 --- a/src/pkg/packager/deprecated/common.go +++ b/src/pkg/packager/deprecated/common.go @@ -5,6 +5,7 @@ package deprecated import ( + "errors" "fmt" "io" "strings" @@ -78,11 +79,14 @@ func MigrateComponent(build types.ZarfBuildData, component types.ZarfComponent) } // PrintBreakingChanges prints the breaking changes between the provided version and the current CLIVersion. -func PrintBreakingChanges(w io.Writer, deployedZarfVersion, cliVersion string) { +func PrintBreakingChanges(w io.Writer, deployedZarfVersion, cliVersion string) error { deployedSemver, err := semver.NewVersion(deployedZarfVersion) + // Dev versions of Zarf are not semver. + if errors.Is(err, semver.ErrInvalidSemVer) { + return nil + } if err != nil { - message.Debugf("Unable to check for breaking changes between Zarf versions") - return + return fmt.Errorf("unable to check for breaking changes between Zarf versions: %w", err) } // List of breaking changes to warn the user of. @@ -103,7 +107,7 @@ func PrintBreakingChanges(w io.Writer, deployedZarfVersion, cliVersion string) { } if len(applicableBreakingChanges) == 0 { - return + return nil } // Print header information @@ -123,4 +127,5 @@ func PrintBreakingChanges(w io.Writer, deployedZarfVersion, cliVersion string) { } message.HorizontalRule() + return nil } diff --git a/src/pkg/packager/deprecated/common_test.go b/src/pkg/packager/deprecated/common_test.go index b789f0c2ce..ed562593ad 100644 --- a/src/pkg/packager/deprecated/common_test.go +++ b/src/pkg/packager/deprecated/common_test.go @@ -48,7 +48,8 @@ func TestPrintBreakingChanges(t *testing.T) { t.Parallel() var output bytes.Buffer message.InitializePTerm(&output) - PrintBreakingChanges(&output, tt.deployedVersion, tt.cliVersion) + err := PrintBreakingChanges(&output, tt.deployedVersion, tt.cliVersion) + require.NoError(t, err) for _, bc := range tt.breakingChanges { require.Contains(t, output.String(), bc.String()) } diff --git a/src/pkg/utils/auth.go b/src/pkg/utils/auth.go index c66b29b548..0bc5cf501a 100644 --- a/src/pkg/utils/auth.go +++ b/src/pkg/utils/auth.go @@ -6,14 +6,13 @@ package utils import ( "bufio" - "io" + "errors" "net/url" "os" "path/filepath" "strings" "github.com/go-git/go-git/v5/plumbing/transport/http" - "github.com/zarf-dev/zarf/src/pkg/message" ) // Credential represents authentication for a given host. @@ -23,47 +22,47 @@ type Credential struct { } // FindAuthForHost finds the authentication scheme for a given host using .git-credentials then .netrc. -func FindAuthForHost(baseURL string) *Credential { +func FindAuthForHost(baseURL string) (*Credential, error) { homePath, _ := os.UserHomeDir() // Read the ~/.git-credentials file credentialsPath := filepath.Join(homePath, ".git-credentials") - // Dogsled the error since we are ok if this file doesn't exist (error message debugged on close) - credentialsFile, _ := os.Open(credentialsPath) - gitCreds := credentialParser(credentialsFile) + gitCreds, err := credentialParser(credentialsPath) + if err != nil { + return nil, err + } // Read the ~/.netrc file netrcPath := filepath.Join(homePath, ".netrc") - // Dogsled the error since we are ok if this file doesn't exist (error message debugged on close) - netrcFile, _ := os.Open(netrcPath) - netrcCreds := netrcParser(netrcFile) + netrcCreds, err := netrcParser(netrcPath) + if err != nil { + return nil, err + } // Combine the creds together (.netrc second because it could have a default) creds := append(gitCreds, netrcCreds...) - - // Look for a match for the given host path in the creds file for _, cred := range creds { // An empty credPath means that we have reached the default from the .netrc hasPath := strings.Contains(baseURL, cred.Path) || cred.Path == "" if hasPath { - return &cred + return &cred, nil } } - - return nil + return nil, nil } // credentialParser parses a user's .git-credentials file to find git creds for hosts. -func credentialParser(file io.ReadCloser) []Credential { - var credentials []Credential - - defer func(file io.ReadCloser) { - err := file.Close() - if err != nil { - message.Debugf("Unable to load an existing git credentials file: %s", err.Error()) - } - }(file) +func credentialParser(path string) ([]Credential, error) { + file, err := os.Open(path) + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, err + } + defer file.Close() + var credentials []Credential scanner := bufio.NewScanner(file) for scanner.Scan() { gitURL, err := url.Parse(scanner.Text()) @@ -80,27 +79,25 @@ func credentialParser(file io.ReadCloser) []Credential { } credentials = append(credentials, credential) } - - return credentials + return credentials, nil } // netrcParser parses a user's .netrc file using the method curl did pre 7.84.0: https://daniel.haxx.se/blog/2022/05/31/netrc-pains/. -func netrcParser(file io.ReadCloser) []Credential { - var credentials []Credential - - defer func(file io.ReadCloser) { - err := file.Close() - if err != nil { - message.Debugf("Unable to load an existing netrc file: %s", err.Error()) - } - }(file) +func netrcParser(path string) ([]Credential, error) { + file, err := os.Open(path) + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + if err != nil { + return nil, err + } + defer file.Close() + var credentials []Credential scanner := bufio.NewScanner(file) - activeMacro := false activeCommand := "" var activeMachine map[string]string - for scanner.Scan() { line := scanner.Text() @@ -154,13 +151,11 @@ func netrcParser(file io.ReadCloser) []Credential { } } } - // Append the last machine (if exists) at the end of the file if activeMachine != nil { credentials = appendNetrcMachine(activeMachine, credentials) } - - return credentials + return credentials, nil } func appendNetrcMachine(machine map[string]string, credentials []Credential) []Credential { diff --git a/src/pkg/utils/auth_test.go b/src/pkg/utils/auth_test.go index 3493cdf919..bad85c2c4a 100644 --- a/src/pkg/utils/auth_test.go +++ b/src/pkg/utils/auth_test.go @@ -5,23 +5,25 @@ package utils import ( + "os" + "path/filepath" "testing" "github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/stretchr/testify/require" - mocks "github.com/zarf-dev/zarf/src/test/mocks" ) func TestCredentialParser(t *testing.T) { - credentialsFile := &mocks.MockReadCloser{ - MockData: []byte( - `https://wayne:password@github.com/ + t.Parallel() + + data := `https://wayne:password@github.com/ bad line https://wayne:p%40ss%20word%2520@zarf.dev -http://google.com`, - ), - } +http://google.com` + path := filepath.Join(t.TempDir(), "file") + err := os.WriteFile(path, []byte(data), 0o644) + require.NoError(t, err) expectedCreds := []Credential{ { @@ -47,15 +49,15 @@ http://google.com`, }, } - gitCredentials := credentialParser(credentialsFile) + gitCredentials, err := credentialParser(path) + require.NoError(t, err) require.Equal(t, expectedCreds, gitCredentials) } func TestNetRCParser(t *testing.T) { + t.Parallel() - netrcFile := &mocks.MockReadCloser{ - MockData: []byte( - `# top of file comment + data := `# top of file comment machine github.com login wayne password password @@ -70,9 +72,10 @@ machine google.com #comment password fun and login info! default login anonymous - password password`, - ), - } + password password` + path := filepath.Join(t.TempDir(), "file") + err := os.WriteFile(path, []byte(data), 0o644) + require.NoError(t, err) expectedCreds := []Credential{ { @@ -105,6 +108,7 @@ default }, } - netrcCredentials := netrcParser(netrcFile) + netrcCredentials, err := netrcParser(path) + require.NoError(t, err) require.Equal(t, expectedCreds, netrcCredentials) } diff --git a/src/test/mocks/read_closer_mock.go b/src/test/mocks/read_closer_mock.go deleted file mode 100644 index 494215f41e..0000000000 --- a/src/test/mocks/read_closer_mock.go +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package mocks contains all the mocks used in Zarf tests. -package mocks - -// MockReadCloser is a mock for the go ReadCloser object. -type ( - MockReadCloser struct { - MockData []byte - MockReadErr error - MockCloseErr error - } -) - -// Read copies a tests expected data and returns an expectedError. -func (mrc *MockReadCloser) Read(buf []byte) (n int, err error) { - numBytes := copy(buf, mrc.MockData) - mrc.MockData = mrc.MockData[numBytes:len(mrc.MockData)] - return numBytes, mrc.MockReadErr -} - -// Close simply returns the expected close err from a test. -func (mrc *MockReadCloser) Close() error { return mrc.MockCloseErr } From 1c3e426b4e8aa4de2d7a0c28d191075e85de6ecd Mon Sep 17 00:00:00 2001 From: Philip Laine Date: Mon, 22 Jul 2024 21:17:57 +0200 Subject: [PATCH 130/132] fix: data injection to return errors (#2720) Signed-off-by: Philip Laine --- src/pkg/cluster/data.go | 182 +++++++++++++++++++------------------ src/pkg/packager/deploy.go | 21 +++-- 2 files changed, 103 insertions(+), 100 deletions(-) diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index 7b19257040..cd46dbb28b 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -13,7 +13,6 @@ import ( "sort" "strconv" "strings" - "sync" "time" corev1 "k8s.io/api/core/v1" @@ -32,12 +31,10 @@ import ( // HandleDataInjection waits for the target pod(s) to come up and inject the data into them // todo: this currently requires kubectl but we should have enough k8s work to make this native now. -func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { - defer wg.Done() +func (c *Cluster) HandleDataInjection(ctx context.Context, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) error { injectionCompletionMarker := filepath.Join(componentPath.DataInjections, config.GetDataInjectionMarker()) if err := os.WriteFile(injectionCompletionMarker, []byte("🦄"), helpers.ReadWriteUser); err != nil { - message.WarnErrf(err, "Unable to create the data injection completion marker") - return + return fmt.Errorf("unable to create the data injection completion marker: %w", err) } tarCompressFlag := "" @@ -59,103 +56,110 @@ func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, d shell, shellArgs := exec.GetOSShell(exec.Shell{Windows: "cmd"}) if _, _, err := exec.Cmd(shell, append(shellArgs, "tar --version")...); err != nil { - message.WarnErr(err, "Unable to execute tar on this system. Please ensure it is installed and on your $PATH.") - return + return fmt.Errorf("unable to execute tar, ensure it is installed in the $PATH: %w", err) } -iterator: - // The eternal loop because some data injections can take a very long time for { - message.Debugf("Attempting to inject data into %s", data.Target) - source := filepath.Join(componentPath.DataInjections, filepath.Base(data.Target.Path)) - if helpers.InvalidPath(source) { - // The path is likely invalid because of how we compose OCI components, add an index suffix to the filename - source = filepath.Join(componentPath.DataInjections, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path)) + select { + case <-ctx.Done(): + return ctx.Err() + default: + message.Debugf("Attempting to inject data into %s", data.Target) + source := filepath.Join(componentPath.DataInjections, filepath.Base(data.Target.Path)) if helpers.InvalidPath(source) { - message.Warnf("Unable to find the data injection source path %s", source) - return + // The path is likely invalid because of how we compose OCI components, add an index suffix to the filename + source = filepath.Join(componentPath.DataInjections, strconv.Itoa(dataIdx), filepath.Base(data.Target.Path)) + if helpers.InvalidPath(source) { + return fmt.Errorf("could not find the data injection source path %s", source) + } } - } - target := podLookup{ - Namespace: data.Target.Namespace, - Selector: data.Target.Selector, - Container: data.Target.Container, - } - - // Wait until the pod we are injecting data into becomes available - pods := waitForPodsAndContainers(ctx, c.Clientset, target, podFilterByInitContainer) - if len(pods) < 1 { - continue - } + target := podLookup{ + Namespace: data.Target.Namespace, + Selector: data.Target.Selector, + Container: data.Target.Container, + } - // Inject into all the pods - for _, pod := range pods { - // Try to use the embedded kubectl if we can - zarfCommand, err := utils.GetFinalExecutableCommand() - kubectlBinPath := "kubectl" + // Wait until the pod we are injecting data into becomes available + pods, err := waitForPodsAndContainers(ctx, c.Clientset, target, podFilterByInitContainer) if err != nil { - message.Warnf("Unable to get the zarf executable path, falling back to host kubectl: %s", err) - } else { - kubectlBinPath = fmt.Sprintf("%s tools kubectl", zarfCommand) + return err + } + if len(pods) < 1 { + continue } - kubectlCmd := fmt.Sprintf("%s exec -i -n %s %s -c %s ", kubectlBinPath, data.Target.Namespace, pod.Name, data.Target.Container) - // Note that each command flag is separated to provide the widest cross-platform tar support - tarCmd := fmt.Sprintf("tar -c %s -f -", tarCompressFlag) - untarCmd := fmt.Sprintf("tar -x %s -v -f - -C %s", tarCompressFlag, data.Target.Path) + // Inject into all the pods + for _, pod := range pods { + // Try to use the embedded kubectl if we can + zarfCommand, err := utils.GetFinalExecutableCommand() + kubectlBinPath := "kubectl" + if err != nil { + message.Warnf("Unable to get the zarf executable path, falling back to host kubectl: %s", err) + } else { + kubectlBinPath = fmt.Sprintf("%s tools kubectl", zarfCommand) + } + kubectlCmd := fmt.Sprintf("%s exec -i -n %s %s -c %s ", kubectlBinPath, data.Target.Namespace, pod.Name, data.Target.Container) - // Must create the target directory before trying to change to it for untar - mkdirCmd := fmt.Sprintf("%s -- mkdir -p %s", kubectlCmd, data.Target.Path) - if err := exec.CmdWithPrint(shell, append(shellArgs, mkdirCmd)...); err != nil { - message.Warnf("Unable to create the data injection target directory %s in pod %s", data.Target.Path, pod.Name) - continue iterator - } + // Note that each command flag is separated to provide the widest cross-platform tar support + tarCmd := fmt.Sprintf("tar -c %s -f -", tarCompressFlag) + untarCmd := fmt.Sprintf("tar -x %s -v -f - -C %s", tarCompressFlag, data.Target.Path) - cpPodCmd := fmt.Sprintf("%s -C %s . | %s -- %s", - tarCmd, - source, - kubectlCmd, - untarCmd, - ) - - // Do the actual data injection - if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { - message.Warnf("Error copying data into the pod %#v: %#v\n", pod.Name, err) - continue iterator - } + // Must create the target directory before trying to change to it for untar + mkdirCmd := fmt.Sprintf("%s -- mkdir -p %s", kubectlCmd, data.Target.Path) + if err := exec.CmdWithPrint(shell, append(shellArgs, mkdirCmd)...); err != nil { + return fmt.Errorf("unable to create the data injection target directory %s in pod %s: %w", data.Target.Path, pod.Name, err) + } + + cpPodCmd := fmt.Sprintf("%s -C %s . | %s -- %s", + tarCmd, + source, + kubectlCmd, + untarCmd, + ) - // Leave a marker in the target container for pods to track the sync action - cpPodCmd = fmt.Sprintf("%s -C %s %s | %s -- %s", - tarCmd, - componentPath.DataInjections, - config.GetDataInjectionMarker(), - kubectlCmd, - untarCmd, - ) - - if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { - message.Warnf("Error saving the zarf sync completion file after injection into pod %#v\n", pod.Name) - continue iterator + // Do the actual data injection + if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { + return fmt.Errorf("could not copy data into the pod %s: %w", pod.Name, err) + } + + // Leave a marker in the target container for pods to track the sync action + cpPodCmd = fmt.Sprintf("%s -C %s %s | %s -- %s", + tarCmd, + componentPath.DataInjections, + config.GetDataInjectionMarker(), + kubectlCmd, + untarCmd, + ) + + if err := exec.CmdWithPrint(shell, append(shellArgs, cpPodCmd)...); err != nil { + return fmt.Errorf("could not save the Zarf sync completion file after injection into pod %s: %w", pod.Name, err) + } } - } - // Do not look for a specific container after injection in case they are running an init container - podOnlyTarget := podLookup{ - Namespace: data.Target.Namespace, - Selector: data.Target.Selector, - } + // Do not look for a specific container after injection in case they are running an init container + podOnlyTarget := podLookup{ + Namespace: data.Target.Namespace, + Selector: data.Target.Selector, + } - // Block one final time to make sure at least one pod has come up and injected the data - // Using only the pod as the final selector because we don't know what the container name will be - // Still using the init container filter to make sure we have the right running pod - _ = waitForPodsAndContainers(ctx, c.Clientset, podOnlyTarget, podFilterByInitContainer) + // Block one final time to make sure at least one pod has come up and injected the data + // Using only the pod as the final selector because we don't know what the container name will be + // Still using the init container filter to make sure we have the right running pod + _, err = waitForPodsAndContainers(ctx, c.Clientset, podOnlyTarget, podFilterByInitContainer) + if err != nil { + return err + } - // Cleanup now to reduce disk pressure - _ = os.RemoveAll(source) + // Cleanup now to reduce disk pressure + err = os.RemoveAll(source) + if err != nil { + return err + } - // Return to stop the loop - return + // Return to stop the loop + return nil + } } } @@ -173,7 +177,7 @@ type podFilter func(pod corev1.Pod) bool // It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names // If the timeout is reached, an empty list will be returned. // TODO: Test, refactor and/or remove. -func waitForPodsAndContainers(ctx context.Context, clientset kubernetes.Interface, target podLookup, include podFilter) []corev1.Pod { +func waitForPodsAndContainers(ctx context.Context, clientset kubernetes.Interface, target podLookup, include podFilter) ([]corev1.Pod, error) { waitCtx, cancel := context.WithTimeout(ctx, 90*time.Second) defer cancel() @@ -183,16 +187,14 @@ func waitForPodsAndContainers(ctx context.Context, clientset kubernetes.Interfac for { select { case <-waitCtx.Done(): - message.Debug("Pod lookup failed: %v", ctx.Err()) - return nil + return nil, ctx.Err() case <-timer.C: listOpts := metav1.ListOptions{ LabelSelector: target.Selector, } podList, err := clientset.CoreV1().Pods(target.Namespace).List(ctx, listOpts) if err != nil { - message.Debug("Unable to find matching pods: %w", err) - return nil + return nil, err } message.Debug("Found %d pods for target %#v", len(podList.Items), target) @@ -245,7 +247,7 @@ func waitForPodsAndContainers(ctx context.Context, clientset kubernetes.Interfac } } if len(readyPods) > 0 { - return readyPods + return readyPods, nil } timer.Reset(3 * time.Second) } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index 35f31514fd..5c174a684f 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -14,9 +14,10 @@ import ( "runtime" "strconv" "strings" - "sync" "time" + "golang.org/x/sync/errgroup" + corev1 "k8s.io/api/core/v1" kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -293,7 +294,6 @@ func (p *Packager) deployComponent(ctx context.Context, component types.ZarfComp hasCharts := len(component.Charts) > 0 hasManifests := len(component.Manifests) > 0 hasRepos := len(component.Repos) > 0 - hasDataInjections := len(component.DataInjections) > 0 hasFiles := len(component.Files) > 0 onDeploy := component.Actions.OnDeploy @@ -344,14 +344,11 @@ func (p *Packager) deployComponent(ctx context.Context, component types.ZarfComp } } - if hasDataInjections { - waitGroup := sync.WaitGroup{} - defer waitGroup.Wait() - - for idx, data := range component.DataInjections { - waitGroup.Add(1) - go p.cluster.HandleDataInjection(ctx, &waitGroup, data, componentPath, idx) - } + g, gCtx := errgroup.WithContext(ctx) + for idx, data := range component.DataInjections { + g.Go(func() error { + return p.cluster.HandleDataInjection(gCtx, data, componentPath, idx) + }) } if hasCharts || hasManifests { @@ -364,6 +361,10 @@ func (p *Packager) deployComponent(ctx context.Context, component types.ZarfComp return charts, fmt.Errorf("unable to run component after action: %w", err) } + err = g.Wait() + if err != nil { + return nil, err + } return charts, nil } From 0d3d0c3e89bbc222103fe9c110176eaa108c331b Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:34:23 -0400 Subject: [PATCH 131/132] feat: revert "feat: remove .metadata.image from schema (#2606)" (#2618) --- src/types/package.go | 1 + zarf.schema.json | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/types/package.go b/src/types/package.go index c78f1fe461..17419dd6e3 100644 --- a/src/types/package.go +++ b/src/types/package.go @@ -47,6 +47,7 @@ type ZarfMetadata struct { Description string `json:"description,omitempty" jsonschema:"description=Additional information about this package"` Version string `json:"version,omitempty" jsonschema:"description=Generic string set by a package author to track the package version (Note: ZarfInitConfigs will always be versioned to the CLIVersion they were created with)"` URL string `json:"url,omitempty" jsonschema:"description=Link to package information when online"` + Image string `json:"image,omitempty" jsonschema:"description=An image URL to embed in this package (Reserved for future use in Zarf UI)"` Uncompressed bool `json:"uncompressed,omitempty" jsonschema:"description=Disable compression of this package"` Architecture string `json:"architecture,omitempty" jsonschema:"description=The target cluster architecture for this package,example=arm64,example=amd64"` YOLO bool `json:"yolo,omitempty" jsonschema:"description=Yaml OnLy Online (YOLO): True enables deploying a Zarf package without first running zarf init against the cluster. This is ideal for connected environments where you want to use existing VCS and container registries."` diff --git a/zarf.schema.json b/zarf.schema.json index 8658787b3b..b5c7a971d4 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -1023,6 +1023,10 @@ "type": "string", "description": "Link to package information when online" }, + "image": { + "type": "string", + "description": "An image URL to embed in this package (Reserved for future use in Zarf UI)" + }, "uncompressed": { "type": "boolean", "description": "Disable compression of this package" From 05ef439336f0d02771d51f0c388ef994acf201e1 Mon Sep 17 00:00:00 2001 From: Austin Abro <37223396+AustinAbro321@users.noreply.github.com> Date: Mon, 22 Jul 2024 16:50:14 -0400 Subject: [PATCH 132/132] chore: updating permissions of eks & ecr nightly tests (#2745) Signed-off-by: Austin Abro --- .github/workflows/nightly-ecr.yml | 5 +++-- .github/workflows/nightly-eks.yml | 8 ++++---- packages/distros/eks/eks.yaml | 17 +++++++++++++++++ src/test/nightly/ecr_publish_test.go | 4 ++-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nightly-ecr.yml b/.github/workflows/nightly-ecr.yml index 655d3c4dd1..f922e89dba 100644 --- a/.github/workflows/nightly-ecr.yml +++ b/.github/workflows/nightly-ecr.yml @@ -2,7 +2,6 @@ name: Test ECR Publishing on: schedule: - cron: '0 7 * * * ' ## Every day at 0700 UTC - workflow_dispatch: ## Give us the ability to run this manually @@ -28,11 +27,13 @@ jobs: - name: Build the Zarf binary run: make build-cli-linux-amd - - name: Configure AWS Credentials + - name: Auth with AWS uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_NIGHTLY_ROLE }} + role-session-name: ${{ github.job || github.event.client_payload.pull_request.head.sha || github.sha }} aws-region: us-east-1 + role-duration-seconds: 3600 # NOTE: The aws cli will need to be explicitly installed on self-hosted runners - name: Login to the ECR Registry diff --git a/.github/workflows/nightly-eks.yml b/.github/workflows/nightly-eks.yml index c4bee3432f..1fe4f4ad7c 100644 --- a/.github/workflows/nightly-eks.yml +++ b/.github/workflows/nightly-eks.yml @@ -2,7 +2,6 @@ name: Test EKS Cluster on: schedule: - cron: '0 7 * * *' ## Every day at 0700 UTC - workflow_dispatch: ## Give us the ability to run this manually inputs: cluster_name: @@ -36,12 +35,13 @@ jobs: - name: Build binary and zarf packages uses: ./.github/actions/packages - - name: Configure AWS Credentials + - name: Auth with AWS uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1 with: role-to-assume: ${{ secrets.AWS_NIGHTLY_ROLE }} + role-session-name: ${{ github.job || github.event.client_payload.pull_request.head.sha || github.sha }} aws-region: us-east-1 - role-duration-seconds: 14400 + role-duration-seconds: 3600 - name: Build the eks package run: ./build/zarf package create packages/distros/eks -o build --confirm @@ -55,7 +55,7 @@ jobs: --confirm - name: Run tests - run: make test-e2e ARCH=amd64 + run: make test-e2e-with-cluster ARCH=amd64 - name: Teardown the cluster if: always() diff --git a/packages/distros/eks/eks.yaml b/packages/distros/eks/eks.yaml index 50ae84a197..530da71f8c 100644 --- a/packages/distros/eks/eks.yaml +++ b/packages/distros/eks/eks.yaml @@ -5,15 +5,28 @@ metadata: name: ###ZARF_VAR_EKS_CLUSTER_NAME### region: ###ZARF_VAR_EKS_CLUSTER_REGION### version: "###ZARF_VAR_EKS_CLUSTER_VERSION###" + tags: + PermissionsBoundary: "zarf_dev_base_policy" iam: withOIDC: true + serviceRolePermissionsBoundary: "arn:aws:iam::173911864621:policy/zarf_dev_base_policy" addons: - name: aws-ebs-csi-driver version: "###ZARF_VAR_EBS_DRIVER_VERSION###" attachPolicyARNs: - arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy + permissionsBoundary: "arn:aws:iam::173911864621:policy/zarf_dev_base_policy" + tags: + PermissionsBoundary: "zarf_dev_base_policy" + + - name: vpc-cni + attachPolicyARNs: + - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy + permissionsBoundary: "arn:aws:iam::173911864621:policy/zarf_dev_base_policy" + tags: + PermissionsBoundary: "zarf_dev_base_policy" managedNodeGroups: - instanceType: ###ZARF_VAR_EKS_INSTANCE_TYPE### @@ -21,3 +34,7 @@ managedNodeGroups: minSize: 3 maxSize: 6 spot: true + tags: + PermissionsBoundary: "zarf_dev_base_policy" + iam: + instanceRolePermissionsBoundary: "arn:aws:iam::173911864621:policy/zarf_dev_base_policy" diff --git a/src/test/nightly/ecr_publish_test.go b/src/test/nightly/ecr_publish_test.go index 7716ebf270..2d872c16e1 100644 --- a/src/test/nightly/ecr_publish_test.go +++ b/src/test/nightly/ecr_publish_test.go @@ -45,8 +45,8 @@ func TestECRPublishing(t *testing.T) { testPackageVersion := "0.0.1" testPackageFileName := fmt.Sprintf("zarf-package-%s-%s-%s.tar.zst", testPackageName, e2e.Arch, testPackageVersion) testPackageLocation := filepath.Join(tmpDir, testPackageFileName) - registryURL := "oci://public.ecr.aws/t8y5r5z5/zarf-nightly" - upstreamPackageURL := fmt.Sprintf("%s/%s:%s-%s", registryURL, testPackageName, testPackageVersion, e2e.Arch) + registryURL := "oci://public.ecr.aws/z6q5p6f7/zarf-nightly" + upstreamPackageURL := fmt.Sprintf("%s/%s:%s", registryURL, testPackageName, testPackageVersion) keyFlag := fmt.Sprintf("--key=%s", "./src/test/packages/zarf-test.pub") // Build the package with our test signature