diff --git a/.github/workflows/e2e-nightly.yaml b/.github/workflows/e2e-nightly.yaml new file mode 100644 index 0000000000..60245716b9 --- /dev/null +++ b/.github/workflows/e2e-nightly.yaml @@ -0,0 +1,91 @@ +name: E2E (nightly) + +on: + # schedule: + # Nightly at 03:00 UTC. + # - cron: "0 3 * * *" + workflow_dispatch: + +permissions: + contents: read + +jobs: + e2e: + runs-on: ubuntu-latest + # Test provisions kind cluster, cert-manager, Argo CD and Kargo, so the full run takes a while. + timeout-minutes: 180 + steps: + - name: Harden the runner (Audit all outbound calls) + uses: step-security/harden-runner@b09bb98e06d4d774595224525879c09bc6e98c40 # v2.20.1 + with: + egress-policy: audit + + - name: Checkout code + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install Go + uses: actions/setup-go@v5 + with: + # Use the e2e module's toolchain version. + go-version-file: hack/test/e2e/go.mod + cache-dependency-path: | + go.sum + hack/test/e2e/go.sum + + # The test shells out to helm, kind, kargo and argocd; install them all into + # hack/bin, which is added to PATH for the subsequent steps. + - name: Add hack/bin to PATH + run: echo "${GITHUB_WORKSPACE}/hack/bin" >> "${GITHUB_PATH}" + + - name: Install helm + run: make install-helm + + - name: Build kind + run: go build -o hack/bin/kind sigs.k8s.io/kind + + - name: Build kargo CLI + run: go build -o hack/bin/kargo ./cmd/cli + + - name: Install argocd CLI + run: | + curl -fsSL -o hack/bin/argocd \ + https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64 + chmod +x hack/bin/argocd + + - name: Verify tools + run: | + go version + helm version + kind version + kargo version --client || true + argocd version --client + + # nightly_config.yaml ships with an empty `context` section. Populate it from + # repository secrets so the git- and http-dependent suites can run. Each key + # is only written when its secret is set; suites whose context is missing + # skip or fail on their own. + - name: Configure e2e context + env: + E2E_KARGO_DEMO_GITOPS_REPO: ${{ secrets.E2E_KARGO_DEMO_GITOPS_REPO }} + E2E_GIT_PAT: ${{ secrets.E2E_GIT_PAT }} + ## TODO: We need to set up the endpoint to ebable that + # E2E_HTTP_ENDPOINT: ${{ secrets.E2E_HTTP_ENDPOINT }} + run: | + cfg=hack/test/e2e/envs/nightly_config.yaml + # Drop the existing (last) context section and re-add it from secrets. + sed -i '/^context:/,$d' "${cfg}" + { + echo "context:" + [ -n "${E2E_KARGO_DEMO_GITOPS_REPO}" ] && echo " kargo_demo_gitops_repo: ${E2E_KARGO_DEMO_GITOPS_REPO}" + [ -n "${E2E_GIT_PAT}" ] && echo " git_pat: ${E2E_GIT_PAT}" + [ -n "${E2E_HTTP_ENDPOINT}" ] && echo " http_endpoint: ${E2E_HTTP_ENDPOINT}" + } >> "${cfg}" + + - name: Build kargo image + run: | + make hack-build + + - name: Run e2e tests + working-directory: hack/test/e2e + run: | + go test -tags=e2e,shared ./suites/shared -timeout 120m -args -env-file=nightly_config.yaml diff --git a/hack/test/e2e/README.md b/hack/test/e2e/README.md index 8cadab29b8..8680cc657f 100644 --- a/hack/test/e2e/README.md +++ b/hack/test/e2e/README.md @@ -12,6 +12,12 @@ To run tests using your home-directory kargo config, aquired with `kargo login` go test -args -env-file=home_config.yaml ``` +## Prerequisites + +- `go` - tests are run with `go test` +- `kubectl`, `kargo` and `argocd` CLI tools installed - some tests and helper functions require those CLIs +- `kind` and `helm` CLI tools installed to run in local `kind` cluster + ## Test framework used This project uses the https://github.com/kubernetes-sigs/e2e-framework to set up environment and test runners. @@ -76,9 +82,30 @@ Please see `kargo_promotion_fail` as an example for running promotions in a proj `envs` folder contains YAML files with configurations accessible to the tests, each `go test` run can be run with a particular environment. Environment configurations mainly used by the environment configuration functions in the `envfuncs` package. +## Provisioning a local cluster + +By default the tests run against an already-running Kargo instance (see `home_config.yaml`). The `envfuncs` package can instead stand up a self-contained environment on a local [kind](https://kind.sigs.k8s.io/) cluster using the `sigs.k8s.io/e2e-framework` kind and Helm helpers: + +- `CreateKindCluster` / `DestroyKindCluster` -- create and delete the cluster +- `InstallCertManager` -- installs cert-manager (a prerequisite for Kargo) +- `InstallArgoCD` -- installs the Argo CD Helm chart +- `InstallKargo` -- installs the Kargo Helm chart (optionally loading a locally built image into the cluster first) + +`ClusterSetupFuncs()` and `ClusterTeardownFuncs()` return these as ordered `env.Func` slices. They are driven entirely by the env file: they run only when it contains a `cluster` section and otherwise no-op, so they are safe to add to a `funcsloader` unconditionally. See `envs/kind_config.yaml` for all configurable fields (cluster name/image, chart repos, versions, values files, `--set` overrides, and an optional Kargo image to load). + +`prerequisites`: the `kind` and `helm` binaries must be available on `PATH`. + +These env functions will be called for each test, but require env configuration with `cluster`, `cert_manager`, `argocd` and `kargo` keys to instruct the functions to actually set up envorinments. +**See envs/kind_config.yaml file for configuration reference** + ## Dependency modules This folder contains the main e2e test module `github.com/akuity/kargo/hack/test/e2e` and a few helper packages, each in its own module: `envs`, `envfuncs`, `funcsloader`. The purpose of that is to be able to override `envs` and `funcsloader` in dependent `e2e` test modules to provide different environment configuration and setup/teardown functions to test in more environments than this package provides. +## TODO + +- Allow disabling teardown on errors. +- Allow separate context values file from init congigurations +- Surface errors better diff --git a/hack/test/e2e/envs/home_config.yaml b/hack/test/e2e/envs/home_config.yaml index 70fa766cbd..003f3c49b6 100644 --- a/hack/test/e2e/envs/home_config.yaml +++ b/hack/test/e2e/envs/home_config.yaml @@ -8,4 +8,7 @@ argocd_cli: config_file: ~/.config/argocd/config context: - kargo_demo_gitops_repo: "https://github.com/hairyhum/kargo-demo-gitops.git" \ No newline at end of file + # kargo_demo_gitops_repo: + # git_pat: + # http_endpoint: + diff --git a/hack/test/e2e/envs/kind_config.yaml b/hack/test/e2e/envs/kind_config.yaml new file mode 100644 index 0000000000..b8a945e070 --- /dev/null +++ b/hack/test/e2e/envs/kind_config.yaml @@ -0,0 +1,85 @@ +## Example environment for running e2e tests against a self-managed kind cluster +## with Kargo and Argo CD installed via Helm. +## +## The `cluster` section opts the run into managing its own cluster: the +## envfuncs.ClusterSetupFuncs (CreateKindCluster -> InstallCertManager -> +## InstallArgoCD -> InstallKargo) run only when it is present, and +## envfuncs.ClusterTeardownFuncs (DestroyKindCluster) tears it down afterwards. +## Wire these into a funcsloader (see framework/funcsloader) to use them. +## +## Every field below is optional; the values shown are the built-in defaults. +description: "Provision a kind cluster and install Kargo + Argo CD via Helm" + +cluster: + name: kargo-e2e + ## Optional kind node image, e.g. kindest/node:v1.31.2 + image: "" + ## Optional path to a kind cluster config file (supports a leading ~). + config_file: "" + +## cert-manager is a prerequisite for Kargo's self-signed certificates. +cert_manager: + release_name: cert-manager + namespace: cert-manager + chart: jetstack/cert-manager + chart_repo_name: jetstack + chart_repo_url: https://charts.jetstack.io + version: "" + timeout: 10m + ## Defaults to ["crds.enabled=true"] when omitted. + set: [] + values_files: [] + +## argo-rollouts are required for AnalysisTemplate in kargo +argo-rollouts: + namespace: argo-rollouts + timeout: 10m + +argocd: + release_name: argocd + namespace: argocd + chart: argo/argo-cd + chart_repo_name: argo + chart_repo_url: https://argoproj.github.io/argo-helm + version: "" + timeout: 10m + set: [] + values_files: [ + ../../values.argocd.test.yaml + ] + +kargo: + release_name: kargo + namespace: kargo + ## Defaults to the published OCI chart. Point at a local path (e.g. + ## ../../../charts/kargo) to test this checkout's chart. + chart: oci://ghcr.io/akuity/kargo-charts/kargo + ## For a classic (non-OCI) repo, set chart_repo_name + chart_repo_url instead. + chart_repo_name: "" + chart_repo_url: "" + version: "" + timeout: 10m + ## Optional locally built image to load into the kind cluster before install, + ## e.g. kargo:dev (pair with `set` entries pointing the chart at it). + image: "" + set: [] + values_files: [ + ../../values.test.yaml + ] + +kargo_cli: + ## Indicate that we need to run `kargo login` to login into a kargo instance in `kind` cluster + kargo_login: + ## Use tmp directory to store kargo config + use_tmp_config_home: true + +argocd_cli: + ## Using system-local argocd config created by `argocd login` + config_file: ~/.config/argocd/config + ## Indicate that we need to run `argocd login` to login to an argocd instance in `kind` cluster + argocd_login: true + +context: + # kargo_demo_gitops_repo: + # git_pat: + # http_endpoint: \ No newline at end of file diff --git a/hack/test/e2e/envs/nightly_config.yaml b/hack/test/e2e/envs/nightly_config.yaml new file mode 100644 index 0000000000..644701975c --- /dev/null +++ b/hack/test/e2e/envs/nightly_config.yaml @@ -0,0 +1,51 @@ +## Environment template used to configure nightly e2e runs. + +## It is based on kind_config.yaml and is going to use `kind` and `helm` to set up kargo and argocd instances. +description: "Nightly test wth kind cluster" + +cluster: + name: kargo-e2e + +## cert-manager is a prerequisite for Kargo's self-signed certificates. +cert_manager: + namespace: cert-manager + timeout: 10m + +argocd: + namespace: argocd + timeout: 10m + values_files: [ + ../../values.argocd.test.yaml + ] + +argo-rollouts: + namespace: argo-rollouts + timeout: 10m + +kargo: + namespace: kargo + ## Using a chart directly from the repo + chart: ../../../../../charts/kargo + ## Using an image built with make hack-build + image: docker.io/library/kargo:dev + timeout: 10m + values_files: [ + ../../values.test.yaml + ] + +kargo_cli: + ## Indicate that we need to run `kargo login` to login into a kargo instance in `kind` cluster + kargo_login: + ## Use tmp directory to store kargo config + use_tmp_config_home: true + +argocd_cli: + ## Using system-local argocd config created by `argocd login` + config_file: ~/.config/argocd/config + ## Indicate that we need to run `argocd login` to login to an argocd instance in `kind` cluster + argocd_login: true + +context: + # kargo_demo_gitops_repo: + # git_pat: + # http_endpoint: \ No newline at end of file diff --git a/hack/test/e2e/framework/envfuncs/argocd_cli.go b/hack/test/e2e/framework/envfuncs/argocd_cli.go index 1e09b1a2c8..f2fb726fba 100644 --- a/hack/test/e2e/framework/envfuncs/argocd_cli.go +++ b/hack/test/e2e/framework/envfuncs/argocd_cli.go @@ -3,16 +3,26 @@ package envfuncs import ( "context" "fmt" + "io" "os" "strings" "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/utils" ) const ArgoCDConfigFile ContextKey = "argocd_config_file" +const ArgocdHostKey ContextKey = "argocd_host" +const ArgocdPasswordKey ContextKey = "argocd_password" +const ArgocdUsernameKey ContextKey = "argocd_username" func LoadArgocdConfig(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - // TODO: other ways to discover/setup argocd config + if argocdConfigFileVal := ctx.Value(ArgoCDConfigFile); argocdConfigFileVal != nil { + // Config file already set, noop + return ctx, nil + } + + // TODO: other ways to discover/setup argocd config (such as using tempdir) if argocdEnvConfig, err := GetEnv(ctx, []string{"argocd_cli", "config_file"}); err == nil { fileName := argocdEnvConfig.(string) if strings.HasPrefix(fileName, "~") { @@ -25,3 +35,52 @@ func LoadArgocdConfig(ctx context.Context, cfg *envconf.Config) (context.Context // Argocd config is optional. Do not fail here return ctx, nil } + + +func ArgocdLogin(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + _, err := GetEnv(ctx, []string{"argocd_cli", "argocd_login"}) + if err != nil { + fmt.Printf("Argocd login disabled, skipping \n") + return ctx, nil + } + + argocdHost, err := GetValueOrEnv(ctx, ArgocdHostKey, []string{"argocd_cli", "argocd_login", "argocd_host"}) + if err != nil { + fmt.Printf("%v is not set, skipping argocd login \n", ArgocdHostKey) + return ctx, nil + } + + argocdPassword, err := GetValueOrEnv(ctx, ArgocdPasswordKey, []string{"argocd_cli", "argocd_login", "argocd_password"}) + if err != nil { + fmt.Printf("%v is not set, skipping argocd login \n", ArgocdPasswordKey) + return ctx, nil + } + + argocdUsername, err := GetValueOrEnv(ctx, ArgocdUsernameKey, []string{"argocd_cli", "argocd_login", "argocd_username"}) + if err != nil { + fmt.Printf("%v is not set, skipping argocd login \n", ArgocdUsernameKey) + return ctx, nil + } + + argocdConfigFile, ok := ctx.Value(ArgoCDConfigFile).(string) + if !ok { + fmt.Printf("%v is not set, skipping argocd login \n", ArgoCDConfigFile) + return ctx, nil + } + + fmt.Printf("Argocd login \n") + + cmd := fmt.Sprintf("argocd login --insecure %s --username %s --password %s --config %s", + argocdHost, argocdUsername, argocdPassword, argocdConfigFile) + p := utils.RunCommandContext(ctx, cmd) + if p.Err() != nil { + outBytes, outErr := io.ReadAll(p.Out()) + if outErr != nil { + return ctx, fmt.Errorf("argocd login failed: %w %w", p.Err(), outErr) + } + return ctx, fmt.Errorf("argocd login failed: %w : %s", p.Err(), outBytes) + } + + return ctx, nil + +} diff --git a/hack/test/e2e/framework/envfuncs/cluster.go b/hack/test/e2e/framework/envfuncs/cluster.go new file mode 100644 index 0000000000..ff60af4977 --- /dev/null +++ b/hack/test/e2e/framework/envfuncs/cluster.go @@ -0,0 +1,403 @@ +//nolint:forcetypeassert +package envfuncs + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "sigs.k8s.io/e2e-framework/pkg/env" + "sigs.k8s.io/e2e-framework/pkg/envconf" + fwenvfuncs "sigs.k8s.io/e2e-framework/pkg/envfuncs" + "sigs.k8s.io/e2e-framework/pkg/utils" + "sigs.k8s.io/e2e-framework/support" + "sigs.k8s.io/e2e-framework/support/kind" + "sigs.k8s.io/e2e-framework/third_party/helm" +) + +// ClusterNameKey holds the name of the kind cluster created for the test run. +// Its presence signals that the run manages its own cluster; the install +// functions key off it and no-op when it is absent. +const ClusterNameKey ContextKey = "cluster_name" + +// KargoHostKey holds a hostname used in KargoLogin. Populated by InstallKargo +const KargoHostKey ContextKey = "kargo_host" + +// KargoPasswordKey holds a password used in KargoLogin. Populated by InstallKargo +const KargoPasswordKey ContextKey = "kargo_password" + +const defaultClusterName = "kargo-e2e" + +// ClusterSetupFuncs returns the ordered env setup functions that create a kind +// cluster and install cert-manager, Argo CD and Kargo via Helm. Each function +// no-ops when the env file does not configure a `cluster` section, so the slice +// is safe to include in a funcsloader unconditionally. +func ClusterSetupFuncs() []env.Func { + return []env.Func{ + CreateKindCluster, + InstallCertManager, + InstallArgoCD, + InstallArgoRollouts, + InstallKargo, + } +} + +// ClusterTeardownFuncs returns the env finish functions that tear down the +// cluster created by ClusterSetupFuncs. +func ClusterTeardownFuncs() []env.Func { + return []env.Func{DestroyKindCluster} +} + +// CreateKindCluster creates a kind cluster named after the `cluster.name` env +// value (default "kargo-e2e"), optionally using a node image (`cluster.image`) +// and a kind config file (`cluster.config_file`). It no-ops when the env file +// has no `cluster` section, leaving runs that target an external cluster +// untouched. The cluster name is stored in the context under ClusterNameKey. +func CreateKindCluster(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if _, err := GetEnvMap(ctx, []string{"cluster"}); err != nil { + fmt.Println("No `cluster` section in env; skipping kind cluster creation") + return ctx, nil + } + + name := optionalString(ctx, []string{"cluster", "name"}, defaultClusterName) + image := optionalString(ctx, []string{"cluster", "image"}, "") + configFile := optionalString(ctx, []string{"cluster", "config_file"}, "") + + var opts []support.ClusterOpts + if image != "" { + opts = append(opts, kind.WithImage(image)) + } + + provider := kind.NewProvider() + + tempdir := ctx.Value(TmpDirKey) + if tempdir == nil { + return ctx, fmt.Errorf("Temp dir is not set up. Cannot create kubeconfig") + } + + kubeconfig := filepath.Join(tempdir.(string), "kubeconfig.yaml") + // NOTE: e2e framework does not support configuring --kubeconfig for kind in its helpers. + // We set KUBECONFIG here for kind command to pick up. + oldKubeconfig := os.Getenv("KUBECONFIG") + os.Setenv("KUBECONFIG", kubeconfig) + defer func() { + if oldKubeconfig != "" { + os.Setenv("KUBECONFIG", oldKubeconfig) + } else { + os.Unsetenv("KUBECONFIG") + } + }() + + create := fwenvfuncs.CreateClusterWithOpts(provider, name, opts...) + if configFile != "" { + create = fwenvfuncs.CreateClusterWithConfig(provider, name, expandHome(configFile), opts...) + } + + fmt.Printf("Creating kind cluster %q with config %v\n", name, configFile) + ctx, err := create(ctx, cfg) + if err != nil { + return ctx, fmt.Errorf("creating kind cluster %q: %w", name, err) + } + + fmt.Printf("Kubeconfig file for tests: %v\n", cfg.KubeconfigFile()) + return context.WithValue(ctx, ClusterNameKey, name), nil +} + +// DestroyKindCluster deletes the cluster created by CreateKindCluster. It +// no-ops when no managed cluster is present in the context. +func DestroyKindCluster(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + name, ok := managedClusterName(ctx) + if !ok { + return ctx, nil + } + fmt.Printf("Destroying kind cluster %q\n", name) + return fwenvfuncs.DestroyCluster(name)(ctx, cfg) +} + +// InstallCertManager installs cert-manager (a prerequisite for Kargo's +// self-signed certificates) via Helm. It no-ops when no managed cluster is +// present in the context. +func InstallCertManager(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if _, ok := managedClusterName(ctx); !ok { + return ctx, nil + } + if _, err := GetEnvMap(ctx, []string{"cert_manager"}); err != nil { + fmt.Println("No `cert_manager` section in env; skipping cert_manager installation") + return ctx, nil + } + chart := helmChart{ + releaseName: optionalString(ctx, []string{"cert_manager", "release_name"}, "cert-manager"), + chart: optionalString(ctx, []string{"cert_manager", "chart"}, "jetstack/cert-manager"), + namespace: optionalString(ctx, []string{"cert_manager", "namespace"}, "cert-manager"), + version: optionalString(ctx, []string{"cert_manager", "version"}, ""), + repoName: optionalString(ctx, []string{"cert_manager", "chart_repo_name"}, "jetstack"), + repoURL: optionalString(ctx, []string{"cert_manager", "chart_repo_url"}, "https://charts.jetstack.io"), + timeout: optionalString(ctx, []string{"cert_manager", "timeout"}, "10m"), + valuesFiles: expandHomeAll(optionalStringSlice(ctx, []string{"cert_manager", "values_files"})), + setValues: defaultStrings(optionalStringSlice(ctx, []string{"cert_manager", "set"}), "crds.enabled=true"), + } + fmt.Println("Installing cert-manager") + if err := chart.install(cfg.KubeconfigFile()); err != nil { + return ctx, fmt.Errorf("installing cert-manager: %w", err) + } + return ctx, nil +} + +// InstallArgoCD installs the Argo CD Helm chart. It no-ops when no managed +// cluster is present in the context. +func InstallArgoCD(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if _, ok := managedClusterName(ctx); !ok { + return ctx, nil + } + if _, err := GetEnvMap(ctx, []string{"argocd"}); err != nil { + fmt.Println("No `argocd` section in env; skipping argocd installation") + return ctx, nil + } + chart := helmChart{ + releaseName: optionalString(ctx, []string{"argocd", "release_name"}, "argocd"), + chart: optionalString(ctx, []string{"argocd", "chart"}, "argo/argo-cd"), + namespace: optionalString(ctx, []string{"argocd", "namespace"}, "argocd"), + version: optionalString(ctx, []string{"argocd", "version"}, ""), + repoName: optionalString(ctx, []string{"argocd", "chart_repo_name"}, "argo"), + repoURL: optionalString(ctx, []string{"argocd", "chart_repo_url"}, "https://argoproj.github.io/argo-helm"), + timeout: optionalString(ctx, []string{"argocd", "timeout"}, "10m"), + valuesFiles: expandHomeAll(optionalStringSlice(ctx, []string{"argocd", "values_files"})), + setValues: optionalStringSlice(ctx, []string{"argocd", "set"}), + } + fmt.Println("Installing Argo CD") + if err := chart.install(cfg.KubeconfigFile()); err != nil { + return ctx, fmt.Errorf("installing Argo CD: %w", err) + } + + // kubectl port-forward svc/argocd-server -n argocd 8080:443 + // TODO: make the port configurable + err := portForward(ctx, cfg.KubeconfigFile(), chart.namespace, "svc/argocd-server", 8080, 443) + if err != nil { + return ctx, fmt.Errorf("port-forwarding Argocd: %w", err) + } + // Port from portForward above + ctx = context.WithValue(ctx, ArgocdHostKey, "localhost:8080") + // Auth info. + // FIXME: the values require setValues to have configs.secret.argocdServerAdminPassword set + // Currently set in values.argocd.test.yaml + ctx = context.WithValue(ctx, ArgocdUsernameKey, "admin") + ctx = context.WithValue(ctx, ArgocdPasswordKey, "admin") + + return ctx, nil +} + +// InstallKargo installs the Kargo Helm chart. When `kargo.image` is set, that +// image is first loaded into the kind cluster (useful for testing a locally +// built image). It no-ops when no managed cluster is present in the context. +func InstallKargo(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + name, ok := managedClusterName(ctx) + if !ok { + return ctx, nil + } + if _, err := GetEnvMap(ctx, []string{"kargo"}); err != nil { + fmt.Println("No `kargo` section in env; skipping kargo installation") + return ctx, nil + } + + if image := optionalString(ctx, []string{"kargo", "image"}, ""); image != "" { + fmt.Printf("Loading Kargo image %q into cluster %q\n", image, name) + var err error + if ctx, err = fwenvfuncs.LoadImageToCluster(name, image)(ctx, cfg); err != nil { + return ctx, fmt.Errorf("loading Kargo image %q: %w", image, err) + } + } + + chart := helmChart{ + releaseName: optionalString(ctx, []string{"kargo", "release_name"}, "kargo"), + chart: optionalString(ctx, []string{"kargo", "chart"}, "oci://ghcr.io/akuity/kargo-charts/kargo"), + namespace: optionalString(ctx, []string{"kargo", "namespace"}, "kargo"), + version: optionalString(ctx, []string{"kargo", "version"}, ""), + repoName: optionalString(ctx, []string{"kargo", "chart_repo_name"}, ""), + repoURL: optionalString(ctx, []string{"kargo", "chart_repo_url"}, ""), + timeout: optionalString(ctx, []string{"kargo", "timeout"}, "10m"), + valuesFiles: expandHomeAll(optionalStringSlice(ctx, []string{"kargo", "values_files"})), + setValues: optionalStringSlice(ctx, []string{"kargo", "set"}), + } + + fmt.Println("Installing Kargo") + if err := chart.install(cfg.KubeconfigFile()); err != nil { + return ctx, fmt.Errorf("installing Kargo: %w", err) + } + // kubectl port-forward --namespace kargo svc/kargo-api 3000:80 + // TODO: make the port configurable + err := portForward(ctx, cfg.KubeconfigFile(), chart.namespace, "svc/kargo-api", 3000, 80) + if err != nil { + return ctx, fmt.Errorf("port-forwarding Kargo API: %w", err) + } + // Port from portForward above + ctx = context.WithValue(ctx, KargoHostKey, "http://localhost:3000") + // FIXME: "admin" value requires passwordHash set in values + // Currently set in values.test.yaml + ctx = context.WithValue(ctx, KargoPasswordKey, "admin") + + return ctx, nil +} + +// InstallArgoRollouts installs the Argo Rollouts Helm chart. +// It no-ops when no managed cluster is present in the context. +func InstallArgoRollouts(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if _, ok := managedClusterName(ctx); !ok { + return ctx, nil + } + if _, err := GetEnvMap(ctx, []string{"argo-rollouts"}); err != nil { + fmt.Println("No `argo-rollouts` section in env; skipping argo rollouts installation") + return ctx, nil + } + chart := helmChart{ + releaseName: optionalString(ctx, []string{"argo-rollouts", "release_name"}, "argo-rollouts"), + chart: optionalString(ctx, []string{"argo-rollouts", "chart"}, "argo/argo-rollouts"), + namespace: optionalString(ctx, []string{"argo-rollouts", "namespace"}, "argo-rollouts"), + version: optionalString(ctx, []string{"argo-rollouts", "version"}, ""), + repoName: optionalString(ctx, []string{"argo-rollouts", "chart_repo_name"}, "argo"), + repoURL: optionalString(ctx, []string{"argo-rollouts", "chart_repo_url"}, "https://argoproj.github.io/argo-helm"), + timeout: optionalString(ctx, []string{"argo-rollouts", "timeout"}, "10m"), + valuesFiles: expandHomeAll(optionalStringSlice(ctx, []string{"argo-rollouts", "values_files"})), + setValues: optionalStringSlice(ctx, []string{"argo-rollouts", "set"}), + } + fmt.Println("Installing Argo Rollouts") + if err := chart.install(cfg.KubeconfigFile()); err != nil { + return ctx, fmt.Errorf("installing Argo Rollouts: %w", err) + } + + return ctx, nil +} + +func portForward(ctx context.Context, kubeconfig, namespace, service string, outport, inport int) error { + // Run port-forward in background. + // This is a simplified approach when we just run a background shell. + // There is no error handling here, it might fail silently. + // FIXME: replace that with goroutine and error channels? + // FIXME: implement forwarding to an non-predefined port + cmd := fmt.Sprintf("sh -c \"kubectl port-forward --kubeconfig %s --namespace %s %s %d:%d > /dev/null 2>&1 &\"", + kubeconfig, namespace, service, outport, inport) + + fmt.Printf("Port forwarding %s to %d\n", service, outport) + + p := utils.RunCommandContext(ctx, cmd) + if p.Err() != nil { + outBytes, outErr := io.ReadAll(p.Out()) + if outErr != nil { + return fmt.Errorf("kubectl: failed to port-forward: %w %w", p.Err(), outErr) + } + return fmt.Errorf("kubectl: failed to port-forward: %w : %s", p.Err(), outBytes) + } + return nil +} + +// helmChart describes a Helm release to install into the cluster. +type helmChart struct { + releaseName string + chart string + namespace string + version string + // repoName and repoURL, when both set, cause `helm repo add`/`update` to run + // before install. Leave empty for OCI or local-path charts. + repoName string + repoURL string + timeout string + valuesFiles []string + setValues []string +} + +// install performs an idempotent `helm upgrade --install` of the chart, adding +// its repository first when one is configured. +func (h helmChart) install(kubeconfig string) error { + m := helm.New(kubeconfig) + + if h.repoName != "" && h.repoURL != "" { + if err := m.RunRepo(helm.WithArgs("add", h.repoName, h.repoURL, "--force-update")); err != nil { + return fmt.Errorf("helm repo add %q: %w", h.repoName, err) + } + if err := m.RunRepo(helm.WithArgs("update")); err != nil { + return fmt.Errorf("helm repo update: %w", err) + } + } + + args := []string{"--install", "--create-namespace"} + for _, valuesFile := range h.valuesFiles { + args = append(args, "--values", valuesFile) + } + for _, setValue := range h.setValues { + args = append(args, "--set", setValue) + } + + opts := []helm.Option{ + helm.WithName(h.releaseName), + helm.WithChart(h.chart), + helm.WithNamespace(h.namespace), + helm.WithArgs(args...), + helm.WithWait(), + } + if h.version != "" { + opts = append(opts, helm.WithVersion(h.version)) + } + if h.timeout != "" { + opts = append(opts, helm.WithTimeout(h.timeout)) + } + return m.RunUpgrade(opts...) +} + +func managedClusterName(ctx context.Context) (string, bool) { + name, ok := ctx.Value(ClusterNameKey).(string) + return name, ok && name != "" +} + +func optionalString(ctx context.Context, path []string, def string) string { + val, err := GetEnv(ctx, path) + if err != nil { + return def + } + if s, ok := val.(string); ok && s != "" { + return s + } + return def +} + +func optionalStringSlice(ctx context.Context, path []string) []string { + val, err := GetEnv(ctx, path) + if err != nil { + return nil + } + items, ok := val.([]any) + if !ok { + return nil + } + out := make([]string, 0, len(items)) + for _, item := range items { + if s, ok := item.(string); ok { + out = append(out, s) + } + } + return out +} + +// defaultStrings returns values when non-empty, otherwise the provided defaults. +func defaultStrings(values []string, defaults ...string) []string { + if len(values) > 0 { + return values + } + return defaults +} + +func expandHomeAll(paths []string) []string { + out := make([]string, len(paths)) + for i, p := range paths { + out[i] = expandHome(p) + } + return out +} + +func expandHome(path string) string { + if strings.HasPrefix(path, "~") { + return filepath.Join(os.Getenv("HOME"), strings.TrimPrefix(path, "~")) + } + return path +} diff --git a/hack/test/e2e/framework/envfuncs/go.mod b/hack/test/e2e/framework/envfuncs/go.mod index 4b0e3b6955..68763434a6 100644 --- a/hack/test/e2e/framework/envfuncs/go.mod +++ b/hack/test/e2e/framework/envfuncs/go.mod @@ -33,7 +33,6 @@ require ( github.com/go-openapi/swag/cmdutils v0.27.3 // indirect github.com/go-openapi/swag/conv v0.27.3 // indirect github.com/go-openapi/swag/fileutils v0.27.3 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect github.com/go-openapi/swag/jsonutils v0.27.3 // indirect github.com/go-openapi/swag/loading v0.27.3 // indirect github.com/go-openapi/swag/mangling v0.27.3 // indirect @@ -42,8 +41,6 @@ require ( github.com/go-openapi/swag/stringutils v0.27.3 // indirect github.com/go-openapi/swag/typeutils v0.27.3 // indirect github.com/go-openapi/swag/yamlutils v0.27.3 // indirect - github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -52,14 +49,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.24.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/vladimirvivien/gexe v0.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect diff --git a/hack/test/e2e/framework/envfuncs/go.sum b/hack/test/e2e/framework/envfuncs/go.sum index 3326f54fe4..19a2f520b9 100644 --- a/hack/test/e2e/framework/envfuncs/go.sum +++ b/hack/test/e2e/framework/envfuncs/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -12,111 +14,67 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= github.com/go-openapi/swag v0.27.3 h1:i6oVKkGZeFgETHMiBHGtj9gIQ1aLtWDdJnT/SRZeets= github.com/go-openapi/swag v0.27.3/go.mod h1:qEXs3GcyyQTDCFQ4ykqnLPDh8qT+zBcjbVWdxCAW0Us= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= github.com/go-openapi/swag/cmdutils v0.27.3 h1:sjuL0TvW81i9R9GRMO/fy+c3mOW+7zxRYwy/7fobZt4= github.com/go-openapi/swag/cmdutils v0.27.3/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU= github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= github.com/go-openapi/swag/fileutils v0.27.3 h1:3UVoZ2RLaIs1lt+2jcKzL8RM3Yk0rmsDE9FLA/HGxFE= github.com/go-openapi/swag/fileutils v0.27.3/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc= github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0= github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= github.com/go-openapi/swag/mangling v0.27.3 h1:gRzzD1PAUoLTtGMgI3KpBmCSOlTuLTFWnviLxLcTnyg= github.com/go-openapi/swag/mangling v0.27.3/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= github.com/go-openapi/swag/netutils v0.27.3 h1:IoBvfCoprsE6E87kAIm9basnISqDDqB79mJ8MN+f5PU= github.com/go-openapi/swag/netutils v0.27.3/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk= github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI= github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4= github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs= github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= -github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= @@ -129,33 +87,21 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= -github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= +github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -164,145 +110,76 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY= -github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A= +github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= +k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= -k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= -k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= -sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM= -sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k= sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM= sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/hack/test/e2e/framework/envfuncs/kargo_cli.go b/hack/test/e2e/framework/envfuncs/kargo_cli.go index 7a7d868b09..58aacda439 100644 --- a/hack/test/e2e/framework/envfuncs/kargo_cli.go +++ b/hack/test/e2e/framework/envfuncs/kargo_cli.go @@ -3,13 +3,15 @@ package envfuncs import ( "context" "encoding/json" - "fmt" env "envs" + "fmt" + "io" "os" "path/filepath" "strings" "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/utils" "github.com/akuity/kargo/pkg/cli/config" "sigs.k8s.io/yaml" @@ -17,7 +19,94 @@ import ( const KargoConfigKey ContextKey = "kargo_config" +const ConfigHomeVar string = "XDG_CONFIG_HOME" + +func KargoLogin(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + loginConfigVal, err := GetEnv(ctx, []string{"kargo_cli", "kargo_login"}) + if err != nil { + fmt.Printf("Kargo login disabled, skipping \n") + return ctx, nil + } + + kargoHost, err := GetValueOrEnv(ctx, KargoHostKey, []string{"kargo_cli", "kargo_login", "kargo_host"}) + if err != nil { + fmt.Printf("%v is not set, skipping kargo login \n", KargoHostKey) + return ctx, nil + } + + kargoPassword, err := GetValueOrEnv(ctx, KargoPasswordKey, []string{"kargo_cli", "kargo_login", "kargo_password"}) + if err != nil { + fmt.Printf("%v is not set, skipping kargo login \n", KargoPasswordKey) + return ctx, nil + } + + ctx, finalize, err := processLoginConfig(ctx, loginConfigVal) + if finalize != nil { + defer finalize() + } + if err != nil { + return ctx, err + } + + fmt.Printf("Kargo login \n") + + cmd := fmt.Sprintf("kargo login %s --admin --password %s", kargoHost, kargoPassword) + p := utils.RunCommandContext(ctx, cmd) + if p.Err() != nil { + outBytes, outErr := io.ReadAll(p.Out()) + if outErr != nil { + return ctx, fmt.Errorf("kargo login failed: %w %w", p.Err(), outErr) + } + return ctx, fmt.Errorf("kargo login failed: %w : %s", p.Err(), outBytes) + } + + return ctx, nil +} + +func processLoginConfig(ctx context.Context, loginConfigVal any) (context.Context, func(), error) { + if loginConfig, ok := loginConfigVal.(map[string]any); ok { + // There are extra fields in the config + // Using tmpdir for config home + if useTmpConfigHome, ok := loginConfig["use_tmp_config_home"]; ok && useTmpConfigHome.(bool) { + oldConfigHome := os.Getenv(ConfigHomeVar) + + tempdir := ctx.Value(TmpDirKey) + if tempdir == nil { + return ctx, nil, fmt.Errorf("Temp dir is not set up. Cannot create tmp confighome") + } + + tmpConfigHome := filepath.Join(tempdir.(string), "config") + + configFile := filepath.Join(tmpConfigHome, "kargo", "config") + ctx = context.WithValue(ctx, KargoConfigKey, configFile) + + os.Setenv(ConfigHomeVar, tmpConfigHome) + return ctx, func() { + if oldConfigHome == "" { + os.Unsetenv(ConfigHomeVar) + } else { + os.Setenv(ConfigHomeVar, oldConfigHome) + } + }, nil + } + } + return ctx, nil, nil +} + func LoadKargoConfig(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + if kargoConfig := ctx.Value(KargoConfigKey); kargoConfig != nil { + // Config already set + if fileName, ok := kargoConfig.(string); ok { + // Config is set as a file + kargoConfig, err := loadConfigFromFile(fileName) + if err != nil { + return ctx, err + } + return withKargoConfig(ctx, kargoConfig), nil + } + // Config is already parsed + return ctx, nil + } if kargoEnv, err := GetEnvMap(ctx, []string{"kargo_cli"}); err == nil { if kargoConfigFile, ok := kargoEnv["config_file"].(string); ok { kargoConfig, err := loadConfigFromFile(kargoConfigFile) @@ -27,7 +116,7 @@ func LoadKargoConfig(ctx context.Context, cfg *envconf.Config) (context.Context, return withKargoConfig(ctx, kargoConfig), nil } if kargoConfigEnv, ok := kargoEnv["kargo_config"].(map[string]any); ok { - kargoConfig, err := loadConfigFromEnv(kargoConfigEnv) + kargoConfig, err := loadConfigFromEnv(kargoConfigEnv) if err != nil { return ctx, err } @@ -64,7 +153,7 @@ func loadConfigFromFile(fileName string) (cfg config.CLIConfig, err error) { fmt.Printf("Reading kargo config from embedded env %v\n", fileName) configBytes, err = env.Envs.ReadFile(filepath.Join("envs", fileName)) } - + if err != nil { return config.CLIConfig{}, err } @@ -72,4 +161,4 @@ func loadConfigFromFile(fileName string) (cfg config.CLIConfig, err error) { return config.CLIConfig{}, err } return cfg, nil -} \ No newline at end of file +} diff --git a/hack/test/e2e/framework/envfuncs/load_env.go b/hack/test/e2e/framework/envfuncs/load_env.go index f3d524f785..d74d983e22 100644 --- a/hack/test/e2e/framework/envfuncs/load_env.go +++ b/hack/test/e2e/framework/envfuncs/load_env.go @@ -4,6 +4,10 @@ import ( "context" "flag" "fmt" + "math/rand" + "os" + "path/filepath" + "strconv" env "envs" @@ -14,6 +18,7 @@ import ( type ContextKey string const EnvKey ContextKey = "env" +const TmpDirKey ContextKey = "tmpdir" var envFileName string @@ -58,4 +63,34 @@ func GetEnvMap(ctx context.Context, path []string) (map[string]any, error) { return nil, fmt.Errorf("cannot convert env to map %v", env) } return nil, err +} + +func GetValueOrEnv(ctx context.Context, valueKey ContextKey, path []string) (any, error) { + if value := ctx.Value(valueKey); value != nil { + return value, nil + } + return GetEnv(ctx, path) +} + +func SetupTempDir(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + rand := strconv.Itoa(rand.Int()) + tmp := os.TempDir() + tempDir := filepath.Join(tmp, rand) + err := os.Mkdir(tempDir, 755) + if err != nil { + return ctx, err + } + return context.WithValue(ctx, TmpDirKey, tempDir), nil +} + + +func TeardownTempDir(ctx context.Context, cfg *envconf.Config) (context.Context, error) { + tempDir := ctx.Value(TmpDirKey) + if tempDir != nil { + err := os.RemoveAll(tempDir.(string)) + if err != nil { + return ctx, err + } + } + return ctx, nil } \ No newline at end of file diff --git a/hack/test/e2e/framework/funcsloader/funcsloader.go b/hack/test/e2e/framework/funcsloader/funcsloader.go index 55d19f459c..cc4ddcaf0c 100644 --- a/hack/test/e2e/framework/funcsloader/funcsloader.go +++ b/hack/test/e2e/framework/funcsloader/funcsloader.go @@ -1,26 +1,50 @@ -package funcsloader + // This package defines the set of environment configuration functions. + // It is a separate package to allow replacing it without changing the main `InitEnv` + // This allows calling test for OSS packages from EE codebase. + package funcsloader import ( - "context" + "slices" - "sigs.k8s.io/e2e-framework/pkg/env" - "sigs.k8s.io/e2e-framework/pkg/envconf" "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "sigs.k8s.io/e2e-framework/pkg/env" ) +// GetFuncs provides an ordered list Setup and Teardown functions for test env (see scaffolding.go) +// All functions will be added to the environment and called when test is run, +// however function definitions may decide to return early and not perform a setup +// based on configuration. func GetFuncs() ([]env.Func, []env.Func) { - // All setup functions should be added here - return []env.Func{ - envfuncs.LoadEnvFile, - envfuncs.LoadKargoConfig, - envfuncs.LoadArgocdConfig, - }, - []env.Func{ - noopFunc, - } + -} + baseSetup := []env.Func{ + // Load a yaml file set by `--env-file` and makes it accessible via context. + envfuncs.LoadEnvFile, + // Create a temporary directory for the test run + // e2e-framework doesn't make it easy to access the tests tempdir so we set one here + envfuncs.SetupTempDir, + } + baseTeardown := []env.Func{ + envfuncs.TeardownTempDir, + } + + // Load kargo and argocd config. Optionally log in. See envfuncs/kargo_cli.go and envfuncs/argocd_cli.go + // Functions may skip setup based on configuration from env file. + configSetup := []env.Func{ + // We load config file AFTER login because login sets it up (different from argocd) + envfuncs.KargoLogin, + envfuncs.LoadKargoConfig, + // We load config file BEFORE login because it's used as an argument (different from kargo) + envfuncs.LoadArgocdConfig, + envfuncs.ArgocdLogin, + } + + // Functions setting up kind cluster and installing kargo and argocd as helm charts. See envfuncs/cluster.go + // Functions may skip setup based on configuration from env file. + clusterSetup := envfuncs.ClusterSetupFuncs() + clusterTeardown := envfuncs.ClusterTeardownFuncs() + + return slices.Concat(baseSetup, clusterSetup, configSetup), + slices.Concat(clusterTeardown, baseTeardown) -func noopFunc(ctx context.Context, cfg *envconf.Config) (context.Context, error) { - return ctx, nil } diff --git a/hack/test/e2e/framework/funcsloader/go.mod b/hack/test/e2e/framework/funcsloader/go.mod index 93867cb66e..2c9d75181c 100644 --- a/hack/test/e2e/framework/funcsloader/go.mod +++ b/hack/test/e2e/framework/funcsloader/go.mod @@ -35,7 +35,6 @@ require ( github.com/go-openapi/swag/cmdutils v0.27.3 // indirect github.com/go-openapi/swag/conv v0.27.3 // indirect github.com/go-openapi/swag/fileutils v0.27.3 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect github.com/go-openapi/swag/jsonutils v0.27.3 // indirect github.com/go-openapi/swag/loading v0.27.3 // indirect github.com/go-openapi/swag/mangling v0.27.3 // indirect @@ -44,7 +43,6 @@ require ( github.com/go-openapi/swag/stringutils v0.27.3 // indirect github.com/go-openapi/swag/typeutils v0.27.3 // indirect github.com/go-openapi/swag/yamlutils v0.27.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect @@ -53,13 +51,12 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.24.1 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect github.com/spf13/pflag v1.0.10 // indirect + github.com/vladimirvivien/gexe v0.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect diff --git a/hack/test/e2e/framework/funcsloader/go.sum b/hack/test/e2e/framework/funcsloader/go.sum index aa9aa13efa..19a2f520b9 100644 --- a/hack/test/e2e/framework/funcsloader/go.sum +++ b/hack/test/e2e/framework/funcsloader/go.sum @@ -1,3 +1,5 @@ +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -12,110 +14,67 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s= github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY= github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= -github.com/go-openapi/swag v0.26.0 h1:GVDXCmfvhfu1BxiHo8/FA+BbKmhecHnG3varjON5/RI= -github.com/go-openapi/swag v0.26.0/go.mod h1:82g3193sZJRbocs7bNCqGfIgq8pkuwVwCfhKIRlEQF0= github.com/go-openapi/swag v0.27.3 h1:i6oVKkGZeFgETHMiBHGtj9gIQ1aLtWDdJnT/SRZeets= github.com/go-openapi/swag v0.27.3/go.mod h1:qEXs3GcyyQTDCFQ4ykqnLPDh8qT+zBcjbVWdxCAW0Us= -github.com/go-openapi/swag/cmdutils v0.26.0 h1:iowihOcvq7y4egO8cOq0dmfohz6wfeQ63U1EnuhO2TU= -github.com/go-openapi/swag/cmdutils v0.26.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= github.com/go-openapi/swag/cmdutils v0.27.3 h1:sjuL0TvW81i9R9GRMO/fy+c3mOW+7zxRYwy/7fobZt4= github.com/go-openapi/swag/cmdutils v0.27.3/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= github.com/go-openapi/swag/conv v0.27.3 h1:iqJFmGEjmX3AY0lSszABFqRVqOSt99XS0LzNIMJYuhU= github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ= -github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= -github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= github.com/go-openapi/swag/fileutils v0.27.3 h1:3UVoZ2RLaIs1lt+2jcKzL8RM3Yk0rmsDE9FLA/HGxFE= github.com/go-openapi/swag/fileutils v0.27.3/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= github.com/go-openapi/swag/jsonutils v0.27.3 h1:1DEz+O82frtSMBcos/7XIn1GnpNTbsD4Bru4Dc/uhRc= github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3 h1:h/eT9kmGCDdFLJF29lOhzLtF0FmP1AX2MhLJWVebsb8= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.27.3/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY= github.com/go-openapi/swag/loading v0.27.3 h1:L9nQkEgzU7QgFQL+pLEMfGUKxeM4pWwGwbET9Z3weW0= github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE= -github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ= -github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0= github.com/go-openapi/swag/mangling v0.27.3 h1:gRzzD1PAUoLTtGMgI3KpBmCSOlTuLTFWnviLxLcTnyg= github.com/go-openapi/swag/mangling v0.27.3/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= -github.com/go-openapi/swag/netutils v0.26.0 h1:CmZp+ZT7HrmFwrC3GdGsXBq2+42T1bjKBapcqVpIs3c= -github.com/go-openapi/swag/netutils v0.26.0/go.mod h1:5iK+Ok3ZohWWex1C50BFTPexi03UaPwjW4Oj8kgrpwo= github.com/go-openapi/swag/netutils v0.27.3 h1:IoBvfCoprsE6E87kAIm9basnISqDDqB79mJ8MN+f5PU= github.com/go-openapi/swag/netutils v0.27.3/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= github.com/go-openapi/swag/pools v0.27.3 h1:gXjImP3F6/56wRRcFgEPld084Y6u2gs21ikPBt8NKBk= github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= github.com/go-openapi/swag/stringutils v0.27.3 h1:Ru28hnbAvN5wycALQYy8IobHvASq+FUFMlp1QzLM0JI= github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= github.com/go-openapi/swag/typeutils v0.27.3 h1:l6SSrx5eR5/WVwrGNzN6bQ9WqL04mrxNBl9YgQ3rcJ4= github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= github.com/go-openapi/swag/yamlutils v0.27.3 h1:cRFCAoYtslYn9L9T0xWryHy1t7c1MACC+DMj3CLvwvs= github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= -github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0= +github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo= github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug= github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= -github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw= -github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y= @@ -128,33 +87,21 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= -github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= -github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= +github.com/onsi/ginkgo/v2 v2.27.4/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= +github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= -github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= -github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -163,144 +110,76 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/vladimirvivien/gexe v0.4.1 h1:W9gWkp8vSPjDoXDu04Yp4KljpVMaSt8IQuHswLDd5LY= -github.com/vladimirvivien/gexe v0.4.1/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= +github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A= +github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= -go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= -go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= -golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= -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= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4= -k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk= k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w= k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg= -k8s.io/apiextensions-apiserver v0.34.3 h1:p10fGlkDY09eWKOTeUSioxwLukJnm+KuDZdrW71y40g= -k8s.io/apiextensions-apiserver v0.34.3/go.mod h1:aujxvqGFRdb/cmXYfcRTeppN7S2XV/t7WMEc64zB5A0= -k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE= -k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiextensions-apiserver v0.36.3 h1:dPmOAPhwTtqb1bTxbFPsy18KHPhktQeO3WUPXunZIB0= +k8s.io/apiextensions-apiserver v0.36.3/go.mod h1:KTXFqgXiuw2pRoL+Wpmttqc+up9Xt/GohadPWeLLOa4= k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM= k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE= -k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A= -k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM= k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg= k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30= -k8s.io/component-base v0.34.3 h1:zsEgw6ELqK0XncCQomgO9DpUIzlrYuZYA0Cgo+JWpVk= -k8s.io/component-base v0.34.3/go.mod h1:5iIlD8wPfWE/xSHTRfbjuvUul2WZbI2nOUK65XL0E/c= k8s.io/component-base v0.36.3 h1:vc/UFvPCkW0irPz84LAodAL1j3f4xktPM6dDJIEheAY= k8s.io/component-base v0.36.3/go.mod h1:hZbNFG+gCMl9EbykDGEu73feKP9/Cq6JsV4pTo9GTO8= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= -k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad h1:oXImqH8mQNk7PmvzKhmN3ddJoY6OnyM225MXwGHPm0A= k8s.io/kube-openapi v0.0.0-20260721132016-d427ff9ee9ad/go.mod h1:0/mqHCVhlumdJ3BhCfnjSZQE037nAhNodh1/hK0T8/I= k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w= k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= -k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE= k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM= -sigs.k8s.io/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= -sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/controller-runtime v0.24.1 h1:miPEwrmirImAvgME1L9qebGHrOnGJoVmVdtOU9fRfo4= sigs.k8s.io/controller-runtime v0.24.1/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= -sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM= -sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k= sigs.k8s.io/e2e-framework v0.7.0 h1:AHkySTC6MvnnMbVSxaO4z1m2MhQKNFP+2Ihs5pRNLlM= sigs.k8s.io/e2e-framework v0.7.0/go.mod h1:1ZgXkUSjmnf18/JgHZNEATWjv48O5lJm9aI1QIsRdbw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= -sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q= sigs.k8s.io/structured-merge-diff/v6 v6.4.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= diff --git a/hack/test/e2e/framework/utils/argocd_fixtures.go b/hack/test/e2e/framework/utils/argocd_fixtures.go index 8c6e11e380..851e345bd4 100644 --- a/hack/test/e2e/framework/utils/argocd_fixtures.go +++ b/hack/test/e2e/framework/utils/argocd_fixtures.go @@ -210,3 +210,85 @@ func ArgoCDDeleteHandler() decoder.HandlerFunc { return argoCDClient.Delete(kind, obj.GetName()) } } + +// SetupArgoCDFixturesWithRepoURL returns a features.Func that sets up the Argo +// CD fixtures with the named ApplicationSet's source repoURL substituted for +// the demo GitOps repository URL configured in the test environment. This +// mirrors the repo substitution applied to Kargo fixtures. +func SetupArgoCDFixturesWithRepoURL(appSetName string) features.Func { + return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + return NewSetupArgoCDFixtures( + UpdateApplicationSetRepoURL(appSetName, requireKargoDemoRepo(ctx, t)), + )(ctx, t, cfg) + } +} + +// requireKargoDemoRepo returns the demo GitOps repository URL configured in the +// test environment, failing the test if it is missing or malformed. +func requireKargoDemoRepo(ctx context.Context, t *testing.T) string { + repoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + repo, ok := repoVal.(string) + if !ok { + t.Fatalf("kargo_demo_gitops_repo is not a string: %v", repoVal) + } + return repo +} + +// UpdateApplicationSetRepoURL returns a DecodeOption that rewrites the repoURL +// of the source(s) in the named ApplicationSet's Application template. This +// lets tests point Argo CD Applications at a fork of the demo GitOps +// repository, mirroring the repo substitution applied to Kargo fixtures via +// UpdateWarehouseGitRepoURL. +func UpdateApplicationSetRepoURL(name, repoURL string) decoder.DecodeOption { + return mutateApplicationSetSources(name, func(source map[string]any) { + source["repoURL"] = repoURL + }) +} + +// UpdateApplicationSetTargetRevision returns a DecodeOption that rewrites the +// targetRevision of the source(s) in the named ApplicationSet's Application +// template. This lets tests point Argo CD Applications at a branch created +// dynamically per test run. +func UpdateApplicationSetTargetRevision(name, targetRevision string) decoder.DecodeOption { + return mutateApplicationSetSources(name, func(source map[string]any) { + source["targetRevision"] = targetRevision + }) +} + +// mutateApplicationSetSources returns a DecodeOption that applies mutate to each +// source of the named ApplicationSet's Application template. Applications may +// use either a single "source" or a list of "sources". +func mutateApplicationSetSources(name string, mutate func(source map[string]any)) decoder.DecodeOption { + return MutateAsUnstructuredOptionFor("ApplicationSet", name, func(unstr runtime.Unstructured) error { + data := unstr.UnstructuredContent() + spec, ok := data["spec"].(map[string]any) + if !ok { + return errors.New("ApplicationSet spec is not a map") + } + template, ok := spec["template"].(map[string]any) + if !ok { + return errors.New("ApplicationSet spec.template is not a map") + } + templateSpec, ok := template["spec"].(map[string]any) + if !ok { + return errors.New("ApplicationSet spec.template.spec is not a map") + } + + if source, ok := templateSpec["source"].(map[string]any); ok { + mutate(source) + } + if sources, ok := templateSpec["sources"].([]any); ok { + for _, src := range sources { + if srcMap, ok := src.(map[string]any); ok { + mutate(srcMap) + } + } + } + + unstr.SetUnstructuredContent(data) + return nil + }) +} diff --git a/hack/test/e2e/framework/utils/features.go b/hack/test/e2e/framework/utils/features.go new file mode 100644 index 0000000000..6b8e8d562e --- /dev/null +++ b/hack/test/e2e/framework/utils/features.go @@ -0,0 +1,5 @@ +package utils + +import "sigs.k8s.io/e2e-framework/pkg/features" + +var TestFeatures []features.Feature diff --git a/hack/test/e2e/framework/utils/fixtures.go b/hack/test/e2e/framework/utils/fixtures.go deleted file mode 100644 index 0ef193f052..0000000000 --- a/hack/test/e2e/framework/utils/fixtures.go +++ /dev/null @@ -1,102 +0,0 @@ -package utils - -import ( - "context" - "os" - "testing" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/e2e-framework/klient/decoder" - "sigs.k8s.io/e2e-framework/klient/k8s/resources" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - "github.com/akuity/kargo/hack/test/e2e/envfuncs" -) - -const NamespaceKey envfuncs.ContextKey = "namespace" - -func SetupFixturesInNamespace(namespace string) features.Func { - return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - return SetupFixtures(context.WithValue(ctx, NamespaceKey, namespace), t, cfg) - } -} - -func SetupFixtures(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - testdata := os.DirFS("testdata") - pattern := "*" - namespace, ok := ctx.Value(NamespaceKey).(string) - t.Logf("namespace %v\n", namespace) - if !ok { - t.Logf("Using config namespace \n") - namespace = cfg.Namespace() - } - r, err := resources.New(cfg.Client().RESTConfig()) - if err != nil { - t.Fatal(err) - } - if err := decoder.DecodeEachFile(ctx, testdata, pattern, - decoder.CreateHandler(r), // try to CREATE objects after decoding - decoder.MutateNamespace(namespace), // inject a namespace into decoded objects, before calling CreateHandler - ); err != nil { - t.Fatal(err) - } - return ctx -} - -func TeardownFixturesInNamespace(namespace string) features.Func { - return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - return TeardownFixtures(context.WithValue(ctx, NamespaceKey, namespace), t, cfg) - } -} - -func TeardownFixtures(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - testdata := os.DirFS("testdata") - pattern := "*" - namespace, ok := ctx.Value(NamespaceKey).(string) - t.Logf("namespace %v\n", namespace) - if !ok { - t.Logf("Using config namespace \n") - namespace = cfg.Namespace() - } - r, err := resources.New(cfg.Client().RESTConfig()) - if err != nil { - t.Fatal(err) - } - if err := decoder.DecodeEachFile(ctx, testdata, pattern, - decoder.DeleteHandler(r), // try to DELETE objects after decoding - decoder.MutateNamespace(namespace), // inject a namespace into decoded objects, before calling CreateHandler - ); err != nil { - t.Fatal(err) - } - return ctx -} - -func CreateNamespace(namespace string) features.Func { - return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - client := cfg.Client() - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: namespace}, - } - t.Logf("CREATE namespace %v\n", ns) - if err := client.Resources().Create(ctx, ns); err != nil { - t.Fatal(err) - } - return ctx - } -} - -func DeleteNamespace(namespace string) features.Func { - return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - client := cfg.Client() - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{Name: namespace}, - } - t.Logf("DELETE namespace %v\n", ns) - if err := client.Resources().Delete(ctx, ns); err != nil { - t.Fatal(err) - } - return ctx - } -} diff --git a/hack/test/e2e/framework/utils/git_branches.go b/hack/test/e2e/framework/utils/git_branches.go new file mode 100644 index 0000000000..455748cdb5 --- /dev/null +++ b/hack/test/e2e/framework/utils/git_branches.go @@ -0,0 +1,37 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/google/go-github/v76/github" +) + +// CreateRemoteBranch creates branch in the GitHub repository, pointing it at the +// current head of fromBranch, using the go-github API. Branch ref management is +// not covered by the gitprovider abstraction, and the e2e suites target a GitHub +// fork, so this uses go-github directly. +func CreateRemoteBranch(ctx context.Context, repoURL, token, branch, fromBranch string) error { + owner, repo, err := gitHubOwnerRepo(repoURL) + if err != nil { + return err + } + + client := github.NewClient(nil).WithAuthToken(token) + + base, _, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+fromBranch) + if err != nil { + return fmt.Errorf("error getting ref for branch %q: %w", fromBranch, err) + } + if base.GetObject().GetSHA() == "" { + return fmt.Errorf("no sha found for branch %q", fromBranch) + } + + if _, _, err := client.Git.CreateRef(ctx, owner, repo, github.CreateRef{ + Ref: "refs/heads/" + branch, + SHA: base.GetObject().GetSHA(), + }); err != nil { + return fmt.Errorf("error creating branch %q: %w", branch, err) + } + return nil +} diff --git a/hack/test/e2e/framework/utils/git_fixtures.go b/hack/test/e2e/framework/utils/git_fixtures.go new file mode 100644 index 0000000000..5473835a97 --- /dev/null +++ b/hack/test/e2e/framework/utils/git_fixtures.go @@ -0,0 +1,170 @@ +// nolint:gosec +package utils + +import ( + "context" + "errors" + "fmt" + "strings" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/e2e-framework/klient/decoder" + "sigs.k8s.io/e2e-framework/klient/k8s" + + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/pkg/gitprovider" + + // Git provider registrations + _ "github.com/akuity/kargo/pkg/gitprovider/azure" + _ "github.com/akuity/kargo/pkg/gitprovider/bitbucket/cloud" + _ "github.com/akuity/kargo/pkg/gitprovider/gitea" + _ "github.com/akuity/kargo/pkg/gitprovider/github" + _ "github.com/akuity/kargo/pkg/gitprovider/gitlab" +) + +// GitCreds holds the git credentials substituted into git-driven suite +// fixtures. +type GitCreds struct { + RepoURL string + Username string + Password string +} + +// RequireGitCreds returns credentials for the demo GitOps repository fork, +// sourced from the test environment (kargo_demo_gitops_repo + git_pat). The +// username is derived from the repository owner; GitHub authenticates via the +// PAT regardless of the username. It fails the test if either value is missing. +func RequireGitCreds(ctx context.Context, t *testing.T) GitCreds { + repoURL := requireKargoDemoRepo(ctx, t) + patVal, err := envfuncs.GetEnv(ctx, []string{"context", "git_pat"}) + if err != nil { + t.Fatalf("cannot get git_pat %v", err) + } + pat, ok := patVal.(string) + if !ok { + t.Fatalf("git_pat is not a string: %v", patVal) + } + return GitCreds{ + RepoURL: repoURL, + Username: gitRepoOwner(repoURL), + Password: pat, + } +} + +// gitRepoOwner extracts the owner segment from a Git HTTPS URL, e.g. +// https://github.com/octocat/repo.git -> "octocat". +func gitRepoOwner(repoURL string) string { + owner, _, _ := gitHubOwnerRepo(repoURL) + return owner +} + +// UpdateGitCredentialsSecret returns a DecodeOption that rewrites the repoURL, +// username and password of the named git credentials Secret. Core types decode +// into their typed representation, so this operates on a *corev1.Secret. +func UpdateGitCredentialsSecret(name, repoURL, username, password string) decoder.DecodeOption { + return MutateOptionFor("Secret", name, func(obj k8s.Object) error { + secret, ok := obj.(*corev1.Secret) + if !ok { + return fmt.Errorf("object %q is not a *corev1.Secret", name) + } + if secret.StringData == nil { + secret.StringData = map[string]string{} + } + secret.StringData["repoURL"] = repoURL + secret.StringData["username"] = username + secret.StringData["password"] = password + return nil + }) +} + +// UpdateStagePromotionVar returns a DecodeOption that rewrites the value of the +// promotion variable named key in the named Stage's promotionTemplate. An empty +// stageName matches every Stage. +func UpdateStagePromotionVar(stageName, key, val string) decoder.DecodeOption { + return MutateAsUnstructuredOptionFor("Stage", stageName, func(unstr runtime.Unstructured) error { + data := unstr.UnstructuredContent() + spec, ok := data["spec"].(map[string]any) + if !ok { + return errors.New("stage spec is not a map") + } + promoTmpl, ok := spec["promotionTemplate"].(map[string]any) + if !ok { + return errors.New("stage spec.promotionTemplate is not a map") + } + promoSpec, ok := promoTmpl["spec"].(map[string]any) + if !ok { + return errors.New("stage spec.promotionTemplate.spec is not a map") + } + vars, ok := promoSpec["vars"].([]any) + if !ok { + // No vars to update. + return nil + } + for _, v := range vars { + if vm, ok := v.(map[string]any); ok && vm["name"] == key { + vm["value"] = val + } + } + + unstr.SetUnstructuredContent(data) + return nil + }) +} + +// MergePullRequest merges the numbered pull request in the given GitHub +// repository using token for authentication. It retries while the provider +// reports the pull request as not yet mergeable (mergeability still computing or +// a transient head change), up to timeout. It is intended to unblock +// git-wait-for-pr promotion steps, which otherwise never complete in an +// unattended run. +func MergePullRequest( + ctx context.Context, + repoURL, token string, + prNumber int, + timeout time.Duration, +) error { + gitProv, err := gitprovider.New(repoURL, &gitprovider.Options{Token: token}) + if err != nil { + return fmt.Errorf("error creating git provider service: %w", err) + } + + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + ticker := time.NewTicker(5 * time.Second) + defer ticker.Stop() + var lastErr error + for { + _, merged, err := gitProv.MergePullRequest( + timedCtx, + int64(prNumber), + &gitprovider.MergePullRequestOpts{MergeMethod: "merge"}, + ) + if err != nil { + return fmt.Errorf("error merging pull request %d: %w", prNumber, err) + } + if merged { + return nil + } + lastErr = fmt.Errorf("pull request %d is not ready to merge yet", prNumber) + select { + case <-timedCtx.Done(): + return fmt.Errorf("timed out merging pull request %d: %w: %w", prNumber, timedCtx.Err(), lastErr) + case <-ticker.C: + } + } +} + +// gitHubOwnerRepo parses the owner and repository name from a GitHub HTTPS URL, +// e.g. https://github.com/octocat/repo.git -> ("octocat", "repo"). +func gitHubOwnerRepo(repoURL string) (string, string, error) { + trimmed := strings.TrimSuffix(strings.TrimSuffix(repoURL, ".git"), "/") + parts := strings.Split(trimmed, "/") + if len(parts) < 2 || parts[len(parts)-2] == "" || parts[len(parts)-1] == "" { + return "", "", fmt.Errorf("cannot parse owner/repo from %q", repoURL) + } + return parts[len(parts)-2], parts[len(parts)-1], nil +} diff --git a/hack/test/e2e/framework/utils/kargo_fixtures.go b/hack/test/e2e/framework/utils/kargo_fixtures.go index 9ae2a96a2b..3ef2f1d6cc 100644 --- a/hack/test/e2e/framework/utils/kargo_fixtures.go +++ b/hack/test/e2e/framework/utils/kargo_fixtures.go @@ -4,7 +4,7 @@ import ( "context" "errors" "fmt" - "os" + "io/fs" "path/filepath" "slices" "strings" @@ -26,6 +26,8 @@ import ( const groupKargo = "kargo" const KargoCLIKey envfuncs.ContextKey = "kargo_cli" const KargoCLIWatchKey envfuncs.ContextKey = "kargo_watch" +const TestDataPath = "testdata" +const TestDataKey envfuncs.ContextKey = "test_data" func SetupKargoClients(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { ctx = SetupKargoApiClient(ctx, t, cfg) @@ -113,15 +115,26 @@ func TeardownKargoFixtures(ctx context.Context, t *testing.T, cfg *envconf.Confi return TeardownKargoFixturesWithOptions(ctx, t, cfg) } +func TestData(testData fs.FS) features.Func { + return func(ctx context.Context, _ *testing.T, _ *envconf.Config) context.Context { + return context.WithValue(ctx, TestDataKey, testData) + } +} + func scanFixtures( ctx context.Context, group string, sortFun func([]string) []string, handlerFun decoder.HandlerFunc, - options ...decoder.DecodeOption) error { + options ...decoder.DecodeOption, +) error { + testData, ok := ctx.Value(TestDataKey).(fs.FS) + if !ok { + return fmt.Errorf("unable to get testdata from context") + } - fixturesDir := filepath.Join("testdata", group) - files, err := filepath.Glob(filepath.Join(fixturesDir, "*.yaml")) + fixturesDir := filepath.Join(TestDataPath, group) + files, err := fs.Glob(testData, filepath.Join(fixturesDir, "*.yaml")) if err != nil { return err } @@ -129,7 +142,7 @@ func scanFixtures( files = sortFun(files) for _, file := range files { - err := scanFile(ctx, file, handlerFun, options...) + err := scanFile(ctx, testData, file, handlerFun, options...) if err != nil { return err } @@ -140,11 +153,12 @@ func scanFixtures( func scanFile( ctx context.Context, + testData fs.FS, fileName string, handlerFun decoder.HandlerFunc, options ...decoder.DecodeOption, ) error { - f, err := os.Open(fileName) + f, err := testData.Open(fileName) if err != nil { return err } @@ -153,7 +167,7 @@ func scanFile( if err != nil { return err } - return f.Close() + return nil } func sortDesc(sorted []string) []string { @@ -179,8 +193,9 @@ func KargoCreateHandler() decoder.HandlerFunc { return fmt.Errorf("kargo_cli is required in context") } + fmt.Printf("Create kargo resource %v : %v\n", obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName()) + manifest, err := yaml.Marshal(obj) - fmt.Printf("Creating resource: %v\n", obj.GetObjectKind()) if err != nil { return fmt.Errorf("error encoding kargo resource manifest: %w", err) } @@ -194,7 +209,8 @@ func KargoCreateHandler() decoder.HandlerFunc { } if err != nil { - return fmt.Errorf("error creating kargo resource: %w", err) + return fmt.Errorf("error creating kargo resource %v-%v: %w", + obj.GetObjectKind().GroupVersionKind().Kind, obj.GetName(), err) } createErrs := make([]error, 0, len(res.Results)) for _, r := range res.Results { diff --git a/hack/test/e2e/framework/utils/kargo_functions.go b/hack/test/e2e/framework/utils/kargo_functions.go index bd862bcd8d..f49684253b 100644 --- a/hack/test/e2e/framework/utils/kargo_functions.go +++ b/hack/test/e2e/framework/utils/kargo_functions.go @@ -3,7 +3,6 @@ package utils import ( "errors" - "fmt" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/e2e-framework/klient/decoder" @@ -13,7 +12,6 @@ import ( func UpdatePromotionTasksVar(name, key, val string) decoder.DecodeOption { return MutateAsUnstructuredOptionFor("PromotionTask", name, func(unstr runtime.Unstructured) error { data := unstr.UnstructuredContent() - fmt.Printf("Parsed data %v\n", data) for _, tplVar := range data["spec"].(map[string]any)["vars"].([]any) { tplVarMap := tplVar.(map[string]any) if tplVarMap["name"] == key { @@ -21,8 +19,6 @@ func UpdatePromotionTasksVar(name, key, val string) decoder.DecodeOption { } } - fmt.Printf("Updated data %v\n", data) - unstr.SetUnstructuredContent(data) return nil }) @@ -31,7 +27,6 @@ func UpdatePromotionTasksVar(name, key, val string) decoder.DecodeOption { func UpdateWarehouseGitRepoURL(name, repoURL string) decoder.DecodeOption { return MutateAsUnstructuredOptionFor("Warehouse", name, func(unstr runtime.Unstructured) error { data := unstr.UnstructuredContent() - fmt.Printf("Parsed data %v\n", data) for _, sub := range data["spec"].(map[string]any)["subscriptions"].([]any) { subMap := sub.(map[string]any) @@ -41,8 +36,6 @@ func UpdateWarehouseGitRepoURL(name, repoURL string) decoder.DecodeOption { } } - fmt.Printf("Updated data %v\n", data) - unstr.SetUnstructuredContent(data) return nil }) diff --git a/hack/test/e2e/framework/utils/kargo_promotions.go b/hack/test/e2e/framework/utils/kargo_promotions.go index 483281a8b9..b540012a3b 100644 --- a/hack/test/e2e/framework/utils/kargo_promotions.go +++ b/hack/test/e2e/framework/utils/kargo_promotions.go @@ -4,7 +4,7 @@ package utils import ( "context" "errors" - "fmt" + "net/http" "strings" "testing" "time" @@ -14,6 +14,9 @@ import ( "github.com/akuity/kargo/pkg/x/client/generated" ) +// PromoteAndWaitForPhase starts a promotion of freightName to stage and waits +// until the promotion reaches phase, which may be a running or a terminal +// phase. It asserts the observed phase equals phase. func PromoteAndWaitForPhase( ctx context.Context, t *testing.T, @@ -21,34 +24,59 @@ func PromoteAndWaitForPhase( phase kargoapi.PromotionPhase, timeout time.Duration, ) (*kargoapi.Promotion, error) { - promotion, err := PromoteAndWaitForCompletion(ctx, t, project, stage, freightName, timeout) + name := StartPromotion(ctx, t, project, stage, freightName, timeout) + return WaitForPromotionPhase(ctx, t, project, name, phase, timeout) +} + +// PromoteWithPRMerge promotes freightName to a stage whose promotion opens a +// pull request and blocks on git-wait-for-pr. It waits for the promotion to be +// Running, reads the pull request number recorded by the git-open-pr step +// (identified by prStepAlias), merges that pull request via the GitHub API +// using token, then waits for the promotion to succeed. +func PromoteWithPRMerge( + ctx context.Context, + t *testing.T, + project, stage, freightName string, + repoURL, token, prStepAlias string, + timeout time.Duration, +) (*kargoapi.Promotion, error) { + running, err := PromoteAndWaitForPhase( + ctx, t, + project, stage, freightName, + kargoapi.PromotionPhaseRunning, + timeout, + ) if err != nil { return nil, err } - if promotion.Status.Phase != phase { - t.Fatalf( - "Promotion '%v' did not finish with phase '%v', actual phase: '%v'", - promotion.Name, phase, promotion.Status.Phase) + + prNumber := WaitForPullRequestID(ctx, t, project, running.Name, prStepAlias, timeout) + + if err := MergePullRequest(ctx, repoURL, token, prNumber, timeout); err != nil { + t.Fatalf("error merging pull request %d: %v", prNumber, err) } - return promotion, err -} -func RefreshStage( - ctx context.Context, - _ *testing.T, - project, stage string, -) error { - kargoClient := ctx.Value(KargoCLIKey).(generated.APIClient) - _, err := kargoClient.CoreAPI.RefreshStage(ctx, project, stage).Execute() - return err + return WaitForPromotionPhase( + ctx, t, + project, running.Name, + kargoapi.PromotionPhaseSucceeded, + timeout, + ) } -func PromoteAndWaitForCompletion( +// StartPromotion issues a promote request for freightName to stage and returns +// the name of the created Promotion. +// +// A Stage transiently rejects a promotion with 400 Bad Request while the +// freight is still being qualified in an upstream stage, so the request is +// retried on 400 until it is accepted or timeout elapses. Any other error is +// fatal immediately. +func StartPromotion( ctx context.Context, t *testing.T, project, stage, freightName string, timeout time.Duration, -) (*kargoapi.Promotion, error) { +) string { kargoClient := ctx.Value(KargoCLIKey).(generated.APIClient) _, httpRes, err := kargoClient.CoreAPI.GetStage(ctx, project, stage).Execute() @@ -59,37 +87,98 @@ func PromoteAndWaitForCompletion( t.Fatalf("error getting stage: %v", err) } - promoteRes, httpRes, promoteErr := kargoClient.CoreAPI. + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + + for { + promoteRes, httpRes, promoteErr := kargoClient.CoreAPI. + PromoteToStage(timedCtx, project, stage). + Body(generated.PromoteToStageRequest{ + Freight: &freightName, + }). + Execute() + statusCode := 0 + if httpRes != nil { + statusCode = httpRes.StatusCode + _ = httpRes.Body.Close() + } + + if promoteErr == nil { + if promoteRes.Metadata.Name == nil { + t.Log("Promotion", promoteRes) + t.Fatalf("Error promoting: promotion name is missing") + } + return *promoteRes.Metadata.Name + } + + // Only the transient "stage not ready for this freight yet" case is + // retried; every other failure is reported immediately. + if statusCode != http.StatusBadRequest { + t.Fatalf("Error promoting: %v (response: %v, http: %v)", promoteErr, promoteRes, httpRes) + } + + t.Logf("Stage %q not ready to accept freight yet (400), retrying promotion", stage) + select { + case <-timedCtx.Done(): + t.Fatalf("Error promoting after retrying for %v: %v", timeout, promoteErr) + case <-ticker.C: + } + } +} + +// TryPromoteToStage issues a single promote request for freightName to stage +// without retrying, returning the HTTP status code and error. It is used to +// assert whether a promotion is currently permitted (e.g. before a required +// soak time has elapsed), where an unavailable freight yields 400 Bad Request. +func TryPromoteToStage( + ctx context.Context, + project, stage, freightName string, +) (int, error) { + kargoClient := ctx.Value(KargoCLIKey).(generated.APIClient) + _, httpRes, promoteErr := kargoClient.CoreAPI. PromoteToStage(ctx, project, stage). Body(generated.PromoteToStageRequest{ Freight: &freightName, }). Execute() + statusCode := 0 if httpRes != nil { + statusCode = httpRes.StatusCode _ = httpRes.Body.Close() } - if promoteErr != nil { - t.Fatalf("Error promoting %v, %v", promoteErr, promoteRes) - } - - promoName := promoteRes.Metadata.Name - if promoName == nil { - t.Log("Promotion", promoteRes) - t.Fatalf("Error promoting: promotion name is missing") - } - promotion, err := WaitForPromotion(ctx, t, project, *promoName, timeout) + return statusCode, promoteErr +} +// WaitForPromotionPhase watches the named promotion until it reaches phase or +// any terminal phase, whichever comes first, then asserts the observed phase +// equals phase. Passing a terminal phase makes it wait for completion. +func WaitForPromotionPhase( + ctx context.Context, + t *testing.T, + project, name string, + phase kargoapi.PromotionPhase, + timeout time.Duration, +) (*kargoapi.Promotion, error) { + promotion, err := watchPromotionForPhase(ctx, project, name, phase, timeout) if err != nil { - t.Fatalf("Error getting promotion %v", err) + t.Fatalf("Error waiting for promotion %q: %v", name, err) + } + if promotion.Status.Phase != phase { + t.Fatalf( + "Promotion '%v' did not reach phase '%v', actual phase: '%v'. Message: '%v'", + promotion.Name, phase, promotion.Status.Phase, promotion.Status.Message) } return promotion, nil - } -func WaitForPromotion( +// watchPromotionForPhase returns the promotion once its phase equals phase or +// is terminal. +func watchPromotionForPhase( ctx context.Context, - _ *testing.T, project, name string, + phase kargoapi.PromotionPhase, timeout time.Duration, ) (*kargoapi.Promotion, error) { timedCtx, cancel := context.WithTimeout(ctx, timeout) @@ -100,11 +189,10 @@ func WaitForPromotion( select { case event := <-watchChan: if event.Object != nil { - phase := event.Object.Status.Phase - if phase == "" || phase == kargoapi.PromotionPhaseRunning || phase == kargoapi.PromotionPhasePending { - continue + current := event.Object.Status.Phase + if current == phase || current.IsTerminal() { + return event.Object, nil } - return event.Object, nil } case err := <-errorChan: if strings.Contains(err.Error(), "unexpected status 404") { @@ -119,6 +207,93 @@ func WaitForPromotion( } } +// WaitForPullRequestID watches the named promotion until the git-open-pr step +// with the given alias records a pull request id (its pr.id output), returning +// it. It fails the test if the promotion reaches a terminal phase first or the +// timeout elapses. +func WaitForPullRequestID( + ctx context.Context, + t *testing.T, + project, name, stepAlias string, + timeout time.Duration, +) int { + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + watchClient := ctx.Value(KargoCLIWatchKey).(watch.Client) + watchChan, errorChan := watchClient.WatchPromotion(timedCtx, project, name) + for { + select { + case event := <-watchChan: + if event.Object != nil { + if id, ok := pullRequestIDFromState(event.Object, stepAlias); ok { + return id + } + if event.Object.Status.Phase.IsTerminal() { + t.Fatalf( + "promotion %q reached terminal phase %q before recording a pull request id. Full status: %v", + name, event.Object.Status.Phase, event.Object.Status) + } + } + case err := <-errorChan: + if strings.Contains(err.Error(), "unexpected status 404") { + watchChan, errorChan = watchClient.WatchPromotion(timedCtx, project, name) + } else { + t.Fatalf("error watching promotion %q: %v", name, err) + } + case <-timedCtx.Done(): + t.Fatalf("timed out waiting for pull request id from promotion %q", name) + } + } +} + +// PromotionStepOutput returns the string value stored by the step with the +// given alias under key in the promotion's shared state (i.e. the value a step +// referenced as outputs[stepAlias].key). +func PromotionStepOutput(promotion *kargoapi.Promotion, stepAlias, key string) (string, bool) { + stepOutput, ok := promotion.Status.GetState()[stepAlias].(map[string]any) + if !ok { + return "", false + } + value, ok := stepOutput[key].(string) + return value, ok +} + +// pullRequestIDFromState extracts the pull request id recorded by the +// git-open-pr step, stored in the promotion state under stepAlias -> pr -> id. +func pullRequestIDFromState(promotion *kargoapi.Promotion, stepAlias string) (int, bool) { + stepOutput, ok := promotion.Status.GetState()[stepAlias].(map[string]any) + if !ok { + return 0, false + } + pr, ok := stepOutput["pr"].(map[string]any) + if !ok { + return 0, false + } + switch id := pr["id"].(type) { + case float64: + return int(id), true + case int64: + return int(id), true + case int: + return id, true + default: + return 0, false + } +} + +func RefreshStage( + ctx context.Context, + _ *testing.T, + project, stage string, +) error { + kargoClient := ctx.Value(KargoCLIKey).(generated.APIClient) + httpRes, err := kargoClient.CoreAPI.RefreshStage(ctx, project, stage).Execute() + if httpRes != nil { + _ = httpRes.Body.Close() + } + return err +} + func WaitForLatestFreight(ctx context.Context, project, origin string, timeout time.Duration) (string, error) { watchClient := ctx.Value(KargoCLIWatchKey).(watch.Client) timedCtx, cancel := context.WithTimeout(ctx, timeout) @@ -187,6 +362,61 @@ func WaitForFreightToBeVerified( return freight } +// WaitForStageVerified watches the named Stage until its most recent Freight +// selection has been verified successfully, returning the Stage. It fails the +// test if verification reaches a terminal, non-successful phase or the timeout +// elapses. +func WaitForStageVerified( + ctx context.Context, + t *testing.T, + project, stage string, + timeout time.Duration, +) *kargoapi.Stage { + timedCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + watchClient := ctx.Value(KargoCLIWatchKey).(watch.Client) + watchChan, errorChan := watchClient.WatchStage(timedCtx, project, stage) + for { + select { + case event := <-watchChan: + if event.Object == nil { + continue + } + info := currentStageVerification(event.Object) + if info == nil { + continue + } + switch info.Phase { + case kargoapi.VerificationPhaseSuccessful: + return event.Object + case kargoapi.VerificationPhaseFailed, + kargoapi.VerificationPhaseError, + kargoapi.VerificationPhaseAborted, + kargoapi.VerificationPhaseInconclusive: + t.Fatalf("stage %q verification finished with phase %q: %s", stage, info.Phase, info.Message) + } + case err := <-errorChan: + if strings.Contains(err.Error(), "unexpected status 404") { + watchChan, errorChan = watchClient.WatchStage(timedCtx, project, stage) + } else { + t.Fatalf("error watching stage %q: %v", stage, err) + } + case <-timedCtx.Done(): + t.Fatalf("timed out waiting for stage %q to be verified", stage) + } + } +} + +// currentStageVerification returns the verification info for the Stage's most +// recent Freight selection, or nil if none has been recorded yet. +func currentStageVerification(stage *kargoapi.Stage) *kargoapi.VerificationInfo { + current := stage.Status.FreightHistory.Current() + if current == nil { + return nil + } + return current.VerificationHistory.Current() +} + func GetFreight(ctx context.Context, project, freightID string) (*generated.Freight, error) { kargoClient := ctx.Value(KargoCLIKey).(generated.APIClient) @@ -197,37 +427,5 @@ func GetFreight(ctx context.Context, project, freightID string) (*generated.Frei if err != nil { return nil, err } - fmt.Printf("FREIGHT: %v", freightOK) return freightOK, nil } - -// func getAnyFreight(kargoClient generated.APIClient, project, origin string) (*kargoapi.Freight, error) { - -// params := core.NewQueryFreightsRestParams().WithProject(project).WithOrigins([]string{origin}) - -// freightRes, err := kargoClient.CoreAPI.QueryFreightsRest(params, nil) -// if err != nil { -// return nil, fmt.Errorf("Error querying freight %v", err) -// } - -// // FIXME: change that once we make freight response typed -// var freightJSON []byte -// if freightJSON, err = json.Marshal(freightRes); err != nil { -// return nil, fmt.Errorf("marshal freight: %w", err) -// } -// // The response is {"groups": {"": {"items": [...]}}} -// type freightList struct { -// Items []*kargoapi.Freight `json:"items"` -// } -// var result struct { -// Groups map[string]*freightList `json:"groups"` -// } -// if err = json.Unmarshal(freightJSON, &result); err != nil { -// return nil, fmt.Errorf("unmarshal freight: %v", err) -// } -// freights := result.Groups[""].Items -// if len(freights) < 1 { -// return nil, fmt.Errorf("no freights found") -// } -// return freights[0], nil -// } diff --git a/hack/test/e2e/framework/utils/requirements.go b/hack/test/e2e/framework/utils/requirements.go index aaf8cd8abb..46d61c018a 100644 --- a/hack/test/e2e/framework/utils/requirements.go +++ b/hack/test/e2e/framework/utils/requirements.go @@ -34,3 +34,14 @@ func RequireContextValue(key envfuncs.ContextKey) features.Func { func RequireKargoCli(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { return RequireContextValue("kargo_cli")(ctx, t, cfg) } + +func SkipIfNoEnvValue(path []string) features.Func { + return func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + ctx = RequireContextValue(envfuncs.EnvKey)(ctx, t, cfg) + _, err := envfuncs.GetEnv(ctx, path) + if err != nil { + t.Skipf("cannot get value for path %v from context %v. Skipping", path, ctx) + } + return ctx + } +} diff --git a/hack/test/e2e/go.mod b/hack/test/e2e/go.mod index cbed16bfb3..4eb8520c05 100644 --- a/hack/test/e2e/go.mod +++ b/hack/test/e2e/go.mod @@ -20,6 +20,7 @@ require ( github.com/akuity/kargo/api v0.0.0 github.com/akuity/kargo/hack/test/e2e/envfuncs v0.0.0-00010101000000-000000000000 github.com/akuity/kargo/pkg/x/client/generated v0.0.0 + github.com/google/go-github/v76 v76.0.0 k8s.io/api v0.36.3 k8s.io/apimachinery v0.36.3 sigs.k8s.io/e2e-framework v0.7.0 @@ -27,14 +28,17 @@ require ( ) require ( + code.gitea.io/sdk/gitea v0.25.1 // indirect connectrpc.com/connect v1.20.0 // indirect dario.cat/mergo v1.0.2 // indirect envs v0.0.0-00010101000000-000000000000 // indirect + github.com/42wim/httpsig v1.2.4 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/adrg/xdg v0.5.3 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect @@ -45,6 +49,7 @@ require ( github.com/cloudwego/base64x v0.1.7 // indirect github.com/coreos/go-oidc/v3 v3.20.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/davidmz/go-pageant v1.0.2 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/expr-lang/expr v1.17.8 // indirect @@ -54,6 +59,7 @@ require ( github.com/gin-contrib/sse v1.1.1 // indirect github.com/gin-gonic/gin v1.12.0 // indirect github.com/go-errors/errors v1.5.1 // indirect + github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/zapr v1.3.0 // indirect @@ -80,9 +86,12 @@ require ( github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-containerregistry v0.21.7 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -92,6 +101,7 @@ require ( github.com/leodido/go-urn v1.5.0 // indirect github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect github.com/mattn/go-isatty v0.0.24 // indirect + github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/spdystream v0.5.1 // indirect @@ -100,6 +110,7 @@ require ( github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oapi-codegen/runtime v1.6.0 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect @@ -122,6 +133,7 @@ require ( github.com/vladimirvivien/gexe v0.5.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xlab/treeprint v1.2.0 // indirect + gitlab.com/gitlab-org/api/client-go v1.46.0 // indirect go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect diff --git a/hack/test/e2e/go.sum b/hack/test/e2e/go.sum index b517548601..36cd8e0431 100644 --- a/hack/test/e2e/go.sum +++ b/hack/test/e2e/go.sum @@ -1,7 +1,11 @@ +code.gitea.io/sdk/gitea v0.25.1 h1:yywxWwoV+SdjHtbC6unBiXojWdZOtoHuGhEazEXeWuE= +code.gitea.io/sdk/gitea v0.25.1/go.mod h1:uDFWYBU8dgZsgOHwe6C/6olxvf8FHguNB3wW1i83fgg= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU= +github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -10,14 +14,18 @@ github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAw github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= @@ -39,6 +47,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= +github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= @@ -47,6 +57,8 @@ github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjT github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -63,6 +75,8 @@ github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= +github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= @@ -125,27 +139,42 @@ github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/go-github/v76 v76.0.0 h1:MCa9VQn+VG5GG7Y7BAkBvSRUN3o+QpaEOuZwFPJmdFA= +github.com/google/go-github/v76 v76.0.0/go.mod h1:38+d/8pYDO4fBLYfBhXF5EKO0wA3UkXBjfmQapFsNCQ= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/graph-gophers/graphql-go v1.9.0 h1:yu0ucKHLc5qGpRwLYKIWtr9bOoxovkWasuBrPQwlHls= +github.com/graph-gophers/graphql-go v1.9.0/go.mod h1:23olKZ7duEvHlF/2ELEoSZaY1aNPfShjP782SOoNTyM= 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-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-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= @@ -162,8 +191,12 @@ github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY= +github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0 h1:mmJCWLe63QvybxhW1iBmQWEaCKdc4SKgALfTNZ+OphU= +github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.0/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -182,6 +215,10 @@ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/ github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/runtime v1.6.0 h1:7Xx+GlueD6nRuyKoCPzL434Jfi3BetbiJOrzCHp/VPU= +github.com/oapi-codegen/runtime v1.6.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU= github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec= github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo/v2 v2.27.4 h1:fcEcQW/A++6aZAZQNUmNjvA9PSOzefMJBerHJ4t8v8Y= @@ -232,6 +269,7 @@ github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiT github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -255,6 +293,8 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +gitlab.com/gitlab-org/api/client-go v1.46.0 h1:YxBWFZIFYKcGESCb9fpkwzouo+apyB9pr/XTWzNoL24= +gitlab.com/gitlab-org/api/client-go v1.46.0/go.mod h1:FtgyU6g2HS5+fMhw6nLK96GBEEBx5MzntOiJWfIaiN8= go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8= go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= @@ -276,25 +316,37 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho= golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= diff --git a/hack/test/e2e/suites/argocd_helm_chart/argocd_helm_chart_test.go b/hack/test/e2e/suites/argocd_helm_chart/argocd_helm_chart_test.go new file mode 100644 index 0000000000..ce1cbee26d --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart/argocd_helm_chart_test.go @@ -0,0 +1,25 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_helm_chart + +// This test implements the Argo CD driven, Helm chart-only example from +// https://github.com/akuity/kargo-examples (01-argocd-driven/02-helm-driven/01-chart-only). +// Stage-specific Argo CD Applications point at a specific version of the chart, and Kargo advances new chart versions +// from stage to stage. + +import ( + "testing" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdHelmChart(t *testing.T) { + // Actual test code lives in test_code.go + // This is a trick to allow shared run between multiple packages + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_helm_chart/feature.go b/hack/test/e2e/suites/argocd_helm_chart/feature.go new file mode 100644 index 0000000000..eec48cc309 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart/feature.go @@ -0,0 +1,87 @@ +//nolint:forcetypeassert +package argocd_helm_chart + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-helm-chart") + + project := "kargo-argocd-helm-chart" + origin := "kargo-demo" + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + feature.Setup(utils.SetupArgocdClient) + feature.Setup(utils.SetupArgoCDFixtures) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. + // This example subscribes to a public Helm chart repository, so no repo + // URL substitution is required. + feature.Setup(utils.RequireKargoCli) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_helm_chart/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_helm_chart/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..1a90daf03c --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart/testdata/argocd/argocd.yaml @@ -0,0 +1,33 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-helm-chart + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-helm-chart-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-helm-chart:{{stage}} + spec: + project: default + source: + repoURL: https://grafana-community.github.io/helm-charts + chart: grafana + # Kargo will update targetRevision to move new charts into each stage + targetRevision: placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-helm-chart-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision diff --git a/hack/test/e2e/suites/argocd_helm_chart/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_helm_chart/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..5bb8a60279 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart/testdata/kargo/kargo.yaml @@ -0,0 +1,92 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-helm-chart +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-helm-chart +spec: + subscriptions: + - chart: + repoURL: https://grafana-community.github.io/helm-charts + name: grafana + semverConstraint: ^12.0.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-helm-chart +spec: + vars: + - name: chartRepo + value: https://grafana-community.github.io/helm-charts + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.chartRepo }} + chart: grafana + desiredRevision: ${{ chartFrom(vars.chartRepo, "grafana").Version }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-helm-chart +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-helm-chart +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-helm-chart +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_helm_chart_n_image/argocd_helm_chart_n_image_test.go b/hack/test/e2e/suites/argocd_helm_chart_n_image/argocd_helm_chart_n_image_test.go new file mode 100644 index 0000000000..2aa6f04626 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart_n_image/argocd_helm_chart_n_image_test.go @@ -0,0 +1,26 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_helm_chart_n_image + +// This test implements the Argo CD driven, Helm chart-and-image example from +// https://github.com/akuity/kargo-examples (01-argocd-driven/02-helm-driven/03-chart-n-image). +// Stage-specific Argo CD Applications point at a specific version of the +// chart in the chart repository and set the image tag from a +// public image repository, and Kargo advances new chart versions and image +// tags from stage to stage. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdHelmChartNImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_helm_chart_n_image/feature.go b/hack/test/e2e/suites/argocd_helm_chart_n_image/feature.go new file mode 100644 index 0000000000..668696e16b --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart_n_image/feature.go @@ -0,0 +1,88 @@ +//nolint:forcetypeassert +package argocd_helm_chart_n_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-helm-chart-n-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-helm-chart-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + feature.Setup(utils.SetupArgoCDFixtures) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. + // This example subscribes to a public Helm chart repository, so no repo + // URL substitution is required. + feature.Setup(utils.RequireKargoCli) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..add66846ff --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/argocd/argocd.yaml @@ -0,0 +1,39 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-helm-chart-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-helm-chart-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-helm-chart-image:{{stage}} + spec: + project: default + source: + repoURL: https://grafana-community.github.io/helm-charts + chart: grafana + # Kargo will update targetRevision to move new charts into each stage + targetRevision: placeholder + helm: + parameters: + # Kargo will update this value to move new images into each stage + - name: image.tag + value: placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-helm-chart-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision + - .spec.source.helm.parameters diff --git a/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..eaafaf2955 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_chart_n_image/testdata/kargo/kargo.yaml @@ -0,0 +1,101 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-helm-chart-image +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-helm-chart-image +spec: + subscriptions: + - chart: + repoURL: https://grafana-community.github.io/helm-charts + name: grafana # Watch for new versions of this chart + semverConstraint: ^12.0.0 + - image: + repoURL: docker.io/grafana/grafana # Watch for new versions of this image + semverConstraint: ^13.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-helm-chart-image +spec: + vars: + - name: chartRepo + value: https://grafana-community.github.io/helm-charts + - name: imageRepo + value: docker.io/grafana/grafana + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.chartRepo }} + chart: grafana + desiredRevision: ${{ chartFrom(vars.chartRepo, "grafana").Version }} + updateTargetRevision: true + helm: + images: + - key: image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-helm-chart-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-helm-chart-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-helm-chart-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_helm_commit_n_image/README.md b/hack/test/e2e/suites/argocd_helm_commit_n_image/README.md new file mode 100644 index 0000000000..73e4f60552 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_commit_n_image/README.md @@ -0,0 +1,19 @@ +# argocd_helm_commit_n_image + +Argo CD driven, Helm. Kargo advances new commits (from a Git branch) together with new image versions into each stage. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/argocd_helm_commit_n_image/argocd_helm_commit_n_image_test.go b/hack/test/e2e/suites/argocd_helm_commit_n_image/argocd_helm_commit_n_image_test.go new file mode 100644 index 0000000000..cb8f1d2291 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_commit_n_image/argocd_helm_commit_n_image_test.go @@ -0,0 +1,22 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_helm_commit_n_image + +// This test implements an example of promoting argocd applications similar to https://github.com/akuity/kargo-examples +// The difference is that this example does not have an AnalysisTemplate verification. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdHelmCommitNImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_helm_commit_n_image/feature.go b/hack/test/e2e/suites/argocd_helm_commit_n_image/feature.go new file mode 100644 index 0000000000..500affb402 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_commit_n_image/feature.go @@ -0,0 +1,99 @@ +//nolint:forcetypeassert +package argocd_helm_commit_n_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-helm-commit-n-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-helm-commit-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), + utils.UpdateWarehouseGitRepoURL("kargo-demo", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightId) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..b6149b5e5e --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/argocd/argocd.yaml @@ -0,0 +1,40 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-helm-commit-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-helm-commit-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-helm-commit-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will update targetRevision to move new commits from the + # new-helm branch into each stage + targetRevision: placeholder + path: charts/kargo-demo + helm: + parameters: + # Kargo will update this value to move new images into each stage + - name: image.name + value: public.ecr.aws/nginx/nginx:placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-helm-commit-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision + - .spec.source.helm.parameters diff --git a/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..62136b7e87 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_commit_n_image/testdata/kargo/kargo.yaml @@ -0,0 +1,100 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-helm-commit-image +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-helm-commit-image +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + # Watch this branch instead of main. This is the "trunk" for this example. + branch: new-helm + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-helm-commit-image +spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ commitFrom(vars.gitRepo).ID }} + updateTargetRevision: true + helm: + images: + - key: image.name + value: ${{ vars.imageRepo }}:${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_helm_image_chart_repo/argocd_helm_image_chart_repo_test.go b/hack/test/e2e/suites/argocd_helm_image_chart_repo/argocd_helm_image_chart_repo_test.go new file mode 100644 index 0000000000..21308d2b2b --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_chart_repo/argocd_helm_image_chart_repo_test.go @@ -0,0 +1,24 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_helm_image_chart_repo + +// This test implements the Argo CD driven, Helm image-only (with chart repo) +// example from https://github.com/akuity/kargo-examples +// (01-argocd-driven/02-helm-driven/02-image-only/01-with-chart-repo). +// Stage-specific Argo CD Applications point at a specific version of the +// chart in the chart repository and mix in specific versions of the image, which Kargo watches. + +import ( + "testing" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdHelmImageChartRepo(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_helm_image_chart_repo/feature.go b/hack/test/e2e/suites/argocd_helm_image_chart_repo/feature.go new file mode 100644 index 0000000000..59134ad341 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_chart_repo/feature.go @@ -0,0 +1,88 @@ +//nolint:forcetypeassert +package argocd_helm_image_chart_repo + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-helm-image-chart-repo") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-helm-image-chartrepo" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + feature.Setup(utils.SetupArgoCDFixtures) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. + // This example subscribes to a public image registry and a public Helm + // chart repository, so no repo URL substitution is required. + feature.Setup(utils.RequireKargoCli) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..8043d8e710 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/argocd/argocd.yaml @@ -0,0 +1,37 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-helm-image-chartrepo + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-helm-image-chartrepo-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-helm-image-chartrepo:{{stage}} + spec: + project: default + source: + repoURL: https://grafana-community.github.io/helm-charts + chart: grafana + targetRevision: 12.11.2 + helm: + parameters: + # Kargo will update this value to move new images into each stage + - name: image.tag + value: placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-helm-image-chartrepo-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.helm.parameters diff --git a/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..4a974d3e23 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_chart_repo/testdata/kargo/kargo.yaml @@ -0,0 +1,93 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-helm-image-chartrepo +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-helm-image-chartrepo +spec: + subscriptions: + - image: + repoURL: docker.io/grafana/grafana # Watch for new versions of this image + semverConstraint: ^13.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-helm-image-chartrepo +spec: + vars: + - name: imageRepo + value: docker.io/grafana/grafana + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: https://grafana-community.github.io/helm-charts + chart: grafana + helm: + images: + - key: image.tag + value: ${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-helm-image-chartrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-helm-image-chartrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-helm-image-chartrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_helm_image_git_repo/README.md b/hack/test/e2e/suites/argocd_helm_image_git_repo/README.md new file mode 100644 index 0000000000..90d66f6368 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_git_repo/README.md @@ -0,0 +1,19 @@ +# argocd_helm_image_git_repo + +Argo CD driven, Helm image-only with a Git-repo source. New image versions are mixed into each stage's `Application` via Helm parameters. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/argocd_helm_image_git_repo/argocd_helm_image_git_repo_test.go b/hack/test/e2e/suites/argocd_helm_image_git_repo/argocd_helm_image_git_repo_test.go new file mode 100644 index 0000000000..c5aa146d01 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_git_repo/argocd_helm_image_git_repo_test.go @@ -0,0 +1,22 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_helm_image_git_repo + +// This test implements an example of promoting argocd applications similar to https://github.com/akuity/kargo-examples +// The difference is that this example does not have an AnalysisTemplate verification. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdHelmImageGitRepo(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_helm_image_git_repo/feature.go b/hack/test/e2e/suites/argocd_helm_image_git_repo/feature.go new file mode 100644 index 0000000000..5abce720e9 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_git_repo/feature.go @@ -0,0 +1,98 @@ +//nolint:forcetypeassert +package argocd_helm_image_git_repo + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-helm-image-git-repo") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-helm-image-gitrepo" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightId) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..749ef7f439 --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/argocd/argocd.yaml @@ -0,0 +1,37 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-helm-image-gitrepo + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-helm-image-gitrepo-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-helm-image-gitrepo:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + targetRevision: new-helm + path: charts/kargo-demo + helm: + parameters: + # Kargo will update this value to move new images into each stage + - name: image.name + value: public.ecr.aws/nginx/nginx:placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-helm-image-gitrepo-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.helm.parameters diff --git a/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..0d63f4cbbb --- /dev/null +++ b/hack/test/e2e/suites/argocd_helm_image_git_repo/testdata/kargo/kargo.yaml @@ -0,0 +1,94 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-helm-image-gitrepo +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-helm-image-gitrepo +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-helm-image-gitrepo +spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + helm: + images: + - key: image.name + value: ${{ vars.imageRepo }}:${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-helm-image-gitrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-helm-image-gitrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-helm-image-gitrepo +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_kustomize_commit_n_image/README.md b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/README.md new file mode 100644 index 0000000000..1273c87b7e --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/README.md @@ -0,0 +1,19 @@ +# argocd_kustomize_commit_n_image + +Argo CD driven, Kustomize. Kargo advances new commits together with new image versions into each stage. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/argocd_kustomize_commit_n_image/argocd_kustomize_commit_n_image_test.go b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/argocd_kustomize_commit_n_image_test.go new file mode 100644 index 0000000000..0fc52b2b0e --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/argocd_kustomize_commit_n_image_test.go @@ -0,0 +1,22 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_kustomize_commit_n_image + +// This test implements an example of promoting argocd applications similar to https://github.com/akuity/kargo-examples +// The difference is that this example does not have an AnalysisTemplate verification. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdKustomizeCommitNImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_kustomize_commit_n_image/feature.go b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/feature.go new file mode 100644 index 0000000000..55521c7fde --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/feature.go @@ -0,0 +1,100 @@ +//nolint:forcetypeassert +package argocd_kustomize_commit_n_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-kustomize-commit-n-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-kustomize-commit-image" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), + utils.UpdateWarehouseGitRepoURL("kargo-demo", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + origin := "kargo-demo" + + t.Logf("Require freight \n") + + anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightId) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..50e10f64fe --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/argocd/argocd.yaml @@ -0,0 +1,39 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-kustomize-commit-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-kustomize-commit-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-kustomize-commit-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will update targetRevision to move new commits from the + # kustomize branch into each stage + targetRevision: placeholder + path: stages/{{stage}} + kustomize: + images: + # Kargo will update this value to move new images into each stage + - public.ecr.aws/nginx/nginx=public.ecr.aws/nginx/nginx:placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-kustomize-commit-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision + - .spec.source.kustomize.images diff --git a/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..af02fca9cb --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_commit_n_image/testdata/kargo/kargo.yaml @@ -0,0 +1,100 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-kustomize-commit-image +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-kustomize-commit-image +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + # Watch this branch instead of main. This is the "trunk" for this example. + branch: kustomize + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-kustomize-commit-image +spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ commitFrom(vars.gitRepo).ID }} + updateTargetRevision: true + kustomize: + images: + - repoURL: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_kustomize_image/README.md b/hack/test/e2e/suites/argocd_kustomize_image/README.md new file mode 100644 index 0000000000..b5a6cde330 --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_image/README.md @@ -0,0 +1,19 @@ +# argocd_kustomize_image + +Argo CD driven, Kustomize image-only. New image versions are mixed into each stage's `Application` via Kustomize. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/argocd_kustomize_image/argocd_kustomize_image_test.go b/hack/test/e2e/suites/argocd_kustomize_image/argocd_kustomize_image_test.go new file mode 100644 index 0000000000..e5cbee9f4e --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_image/argocd_kustomize_image_test.go @@ -0,0 +1,22 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_kustomize_image + +// This test implements an example of promoting argocd applications similar to https://github.com/akuity/kargo-examples +// The difference is that this example does not have an AnalysisTemplate verification. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdKustomizeImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_kustomize_image/feature.go b/hack/test/e2e/suites/argocd_kustomize_image/feature.go new file mode 100644 index 0000000000..e78e824b0b --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_image/feature.go @@ -0,0 +1,100 @@ +//nolint:forcetypeassert +package argocd_kustomize_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-kustomize-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-kustomize-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + // The Warehouse subscribes to an image, so only the PromotionTask's + // gitRepo var needs to point at the fork. + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightId) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) + }) + + for _, stage := range []string{"test", "uat", "prod"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_kustomize_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_kustomize_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..4b210ce594 --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_image/testdata/argocd/argocd.yaml @@ -0,0 +1,36 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-kustomize-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-argocd-kustomize-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-argocd-kustomize-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + targetRevision: kustomize + path: stages/{{stage}} + kustomize: + images: + # Kargo will update this value to move new images into each stage + - public.ecr.aws/nginx/nginx=public.ecr.aws/nginx/nginx:placeholder + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-kustomize-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.kustomize.images diff --git a/hack/test/e2e/suites/argocd_kustomize_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_kustomize_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..d2636ba3d4 --- /dev/null +++ b/hack/test/e2e/suites/argocd_kustomize_image/testdata/kargo/kargo.yaml @@ -0,0 +1,94 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-kustomize-image +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-kustomize-image +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-argocd-kustomize-image +spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + kustomize: + images: + - repoURL: ${{ vars.imageRepo }} + tag: ${{ imageFrom(vars.imageRepo).Tag }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-argocd-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-argocd-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/argocd_update/README.md b/hack/test/e2e/suites/argocd_update/README.md new file mode 100644 index 0000000000..b4cc0ad172 --- /dev/null +++ b/hack/test/e2e/suites/argocd_update/README.md @@ -0,0 +1,19 @@ +# argocd_update + +Argo CD driven, commit-only promotion. Kargo advances new commits from stage to stage by updating each stage's Argo CD `Application` `targetRevision`. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/argocd_update/argocd_update_test.go b/hack/test/e2e/suites/argocd_update/argocd_update_test.go index 26f36a3c3d..be86a97888 100644 --- a/hack/test/e2e/suites/argocd_update/argocd_update_test.go +++ b/hack/test/e2e/suites/argocd_update/argocd_update_test.go @@ -1,21 +1,12 @@ //go:build e2e //nolint:forcetypeassert -package argocd_update_test +package argocd_update // This test implements an example of promoting argocd applications similar to https://github.com/akuity/kargo-examples // The difference is that this example does not have an AnalysisTemplate verification. import ( - "context" "testing" - "time" - - // "github.com/akuity/kargo/pkg/x/client/generated/core" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - kargoapi "github.com/akuity/kargo/api/v1alpha1" - "github.com/akuity/kargo/hack/test/e2e/envfuncs" "github.com/akuity/kargo/hack/test/e2e/framework/utils" ) @@ -26,118 +17,5 @@ func TestMain(m *testing.M) { } func TestArgocdUpdate(t *testing.T) { - feature := features.New("argocd-update") - - project := "kargo-argocd-update" - - feature.Setup(utils.SetupArgocdClient) - feature.Setup(utils.SetupArgoCDFixtures) - feature.Teardown(utils.TeardownArgoCDFixtures) - - feature.Setup(utils.SetupKargoClients) - - // Setup and teardown fixtures from testdata folder - feature.Setup(utils.RequireKargoCli) - feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { - kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) - if err != nil { - t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) - } - kargoDemoRepo := kargoDemoRepoVal.(string) - - return utils.NewSetupKargoFixtures( - utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), - utils.UpdateWarehouseGitRepoURL("kargo-demo", kargoDemoRepo), - )(ctx, t, cfg) - }) - feature.Teardown(utils.TeardownKargoFixtures) - - feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - origin := "kargo-demo" - - t.Logf("Require freight \n") - - anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) - if err != nil { - t.Fatal(err) - } - - t.Logf("Freight: %v", anyFreightId) - return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) - }) - - feature.Assess("promote test", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) - stage := "test" - - t.Logf("Promoting test to %v \n", freightID) - - if err := utils.RefreshStage(ctx, t, project, stage); err != nil { - t.Fatal(err) - } - - _, err := utils.PromoteAndWaitForPhase( - ctx, t, - project, stage, freightID, - kargoapi.PromotionPhaseSucceeded, - 10*time.Minute) - if err != nil { - t.Fatal(err) - } - - _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) - - return ctx - }) - - feature.Assess("promote uat", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) - stage := "uat" - - t.Logf("Promoting uat \n") - - if err := utils.RefreshStage(ctx, t, project, stage); err != nil { - t.Fatal(err) - } - - _, err := utils.PromoteAndWaitForPhase( - ctx, t, - project, stage, freightID, - kargoapi.PromotionPhaseSucceeded, - 10*time.Minute) - if err != nil { - t.Fatal(err) - } - - _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) - - return ctx - }) - - feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) - - stage := "prod" - - t.Logf("Promoting prod \n") - - if err := utils.RefreshStage(ctx, t, project, stage); err != nil { - t.Fatal(err) - } - - _, err := utils.PromoteAndWaitForPhase( - ctx, t, - project, stage, freightID, - kargoapi.PromotionPhaseSucceeded, - 10*time.Minute) - if err != nil { - t.Fatal(err) - } - - _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) - - return ctx - }) - - utils.TestEnv.Test(t, feature.Feature()) + utils.TestEnv.Test(t, feature()) } diff --git a/hack/test/e2e/suites/argocd_update/feature.go b/hack/test/e2e/suites/argocd_update/feature.go new file mode 100644 index 0000000000..dcca080b5b --- /dev/null +++ b/hack/test/e2e/suites/argocd_update/feature.go @@ -0,0 +1,146 @@ +//nolint:forcetypeassert +package argocd_update + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-update") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-argocd-update" + + feature.Setup(utils.SetupArgocdClient) + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "gitRepo", kargoDemoRepo), + utils.UpdateWarehouseGitRepoURL("kargo-demo", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + origin := "kargo-demo" + + t.Logf("Require freight \n") + + anyFreightId, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightId) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightId) + }) + + feature.Assess("promote test", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + stage := "test" + + t.Logf("Promoting test to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + feature.Assess("promote uat", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + stage := "uat" + + t.Logf("Promoting uat \n") + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + stage := "prod" + + t.Logf("Promoting prod \n") + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_update/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_update/testdata/argocd/argocd.yaml index 12bed0690c..f3d7757bdd 100644 --- a/hack/test/e2e/suites/argocd_update/testdata/argocd/argocd.yaml +++ b/hack/test/e2e/suites/argocd_update/testdata/argocd/argocd.yaml @@ -18,7 +18,7 @@ spec: spec: project: default source: - repoURL: https://github.com/hairyhum/kargo-demo-gitops.git + repoURL: https://github.com//kargo-demo-gitops.git targetRevision: placeholder path: stages/{{stage}} kustomize: diff --git a/hack/test/e2e/suites/argocd_wait/README.md b/hack/test/e2e/suites/argocd_wait/README.md new file mode 100644 index 0000000000..cff8f29993 --- /dev/null +++ b/hack/test/e2e/suites/argocd_wait/README.md @@ -0,0 +1,34 @@ +# argocd_wait + +Exercises the `argocd-wait` promotion step. A branch is created uniquely per test +run in setup (a copy of the `kustomize` branch) and deleted in teardown; the Argo +CD `Application` tracks that branch with **auto-sync** enabled. The promotion +checks out the existing branch, updates the image, commits and pushes, then uses +`argocd-wait` to block until Argo CD reconciles the change (Healthy + Synced). +Argo CD's own auto-sync triggers the sync -- not an `argocd-update` step -- which +is the scenario `argocd-wait` exists for. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the git credentials Secret, the promotion's `gitRepo` var, and the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes the per-run branch that Argo CD tracks. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` + +## Note + +Each run creates a branch named `argocd-wait/e2e/` on the fork in +setup, pointing it at the head of the `kustomize` branch via the go-github API +(no clone, no `git` binary). The branch is left behind, like the other git-driven +suites' branches. diff --git a/hack/test/e2e/suites/argocd_wait/argocd_wait_test.go b/hack/test/e2e/suites/argocd_wait/argocd_wait_test.go new file mode 100644 index 0000000000..1d21a6bd13 --- /dev/null +++ b/hack/test/e2e/suites/argocd_wait/argocd_wait_test.go @@ -0,0 +1,27 @@ +//go:build e2e +//nolint:forcetypeassert +package argocd_wait + +// This test exercises the argocd-wait promotion step. A branch is created in +// test setup (as a copy of the kustomize branch); the Argo CD Application tracks +// that branch with auto-sync enabled. The Kargo +// promotion checks out the existing branch, updates the image, commits and +// pushes, then uses argocd-wait to block until Argo CD reconciles the change +// (Healthy + Synced). Kargo never triggers the sync -- Argo CD's own auto-sync +// does -- which is exactly the scenario argocd-wait exists for. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestArgocdWait(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/argocd_wait/feature.go b/hack/test/e2e/suites/argocd_wait/feature.go new file mode 100644 index 0000000000..2152a8cc5c --- /dev/null +++ b/hack/test/e2e/suites/argocd_wait/feature.go @@ -0,0 +1,125 @@ +//nolint:forcetypeassert +package argocd_wait + +import ( + "context" + "embed" + "fmt" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("argocd-wait") + + project := "kargo-argocd-wait" + origin := "kargo-demo" + stage := "test" + + // Branch created uniquely per test run. Both the Argo CD Application's + // targetRevision and the promotion's targetBranch var point at it. + branch := fmt.Sprintf("argocd-wait/e2e/%d", time.Now().UnixNano()) + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + // Create the branch (a copy of the kustomize branch) BEFORE Argo CD is set + // up, so the Application has an existing branch to track. + feature.Setup(func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + t.Logf("Git creds %v", creds) + + if err := utils.CreateRemoteBranch(ctx, creds.RepoURL, creds.Password, branch, "kustomize"); err != nil { + t.Fatalf("failed to create branch %q: %v", branch, err) + } + t.Logf("created branch %q", branch) + return ctx + }) + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet at the fork and the per-run branch. + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + repoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + repo := repoVal.(string) + + return utils.NewSetupArgoCDFixtures( + utils.UpdateApplicationSetRepoURL(project, repo), + utils.UpdateApplicationSetTargetRevision(project, branch), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Substitute the git credentials Secret, the promotion's gitRepo var and the + // per-run targetBranch. The Warehouse subscribes to an image, so no Warehouse + // git repo URL substitution is applied. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + utils.UpdateStagePromotionVar("", "targetBranch", branch), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + feature.Assess("promotion waits for argocd to reconcile", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v on branch %q \n", stage, freightID, branch) + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + // The argocd-wait step keeps the promotion Running until Argo CD reports + // the Application Healthy and Synced, so reaching Succeeded means the + // pushed change was reconciled. + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/argocd_wait/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/argocd_wait/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..dcef8716c2 --- /dev/null +++ b/hack/test/e2e/suites/argocd_wait/testdata/argocd/argocd.yaml @@ -0,0 +1,34 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-argocd-wait + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + template: + metadata: + name: kargo-argocd-wait-{{stage}} + # No kargo.akuity.io/authorized-stage annotation: argocd-wait does not + # require it (unlike argocd-update), since it only reads Application state. + spec: + project: default + source: + # repoURL and targetRevision are substituted at runtime: the repoURL + # from the test env, the targetRevision with the branch created per run. + repoURL: https://github.com//kargo-demo-gitops.git + targetRevision: placeholder + path: stages/{{stage}} + destination: + server: https://kubernetes.default.svc + namespace: kargo-argocd-wait-{{stage}} + # Auto-sync so Argo CD reconciles changes pushed to the branch on its own; + # the promotion's argocd-wait step waits for that reconciliation. + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/hack/test/e2e/suites/argocd_wait/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/argocd_wait/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..c553ba3476 --- /dev/null +++ b/hack/test/e2e/suites/argocd_wait/testdata/kargo/kargo.yaml @@ -0,0 +1,106 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-argocd-wait +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-argocd-wait + labels: + kargo.akuity.io/cred-type: git +stringData: + # repoURL, username and password are substituted at runtime from the test env. + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-argocd-wait +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-argocd-wait +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + # The targetBranch already exists (created in test setup and tracked by Argo + # CD), so it is simply checked out -- no branch creation here. + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - branch: ${{ vars.targetBranch }} + path: ${{ vars.outPath }} + # Set the image to the freight's tag in place; Argo CD renders stages/, + # which references base, so this changes the rendered image. + - uses: kustomize-set-image + as: update-image + config: + path: ${{ vars.outPath }}/base + images: + - image: public.ecr.aws/nginx/nginx + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: ${{ task.outputs['update-image'].commitMessage }} + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-argocd-wait +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + # Substituted at runtime with the branch created per test run. + - name: targetBranch + value: placeholder + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + # Push the change to the branch Argo CD tracks and auto-syncs. + - uses: git-push + config: + path: ${{ vars.outPath }} + # Wait for Argo CD to reconcile the pushed change (Healthy + Synced). The + # sync is triggered by Argo CD's own auto-sync, not by an argocd-update + # step, which is exactly what argocd-wait is for. + - uses: argocd-wait + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} diff --git a/hack/test/e2e/suites/git_commit_only/README.md b/hack/test/e2e/suites/git_commit_only/README.md new file mode 100644 index 0000000000..0707c16be1 --- /dev/null +++ b/hack/test/e2e/suites/git_commit_only/README.md @@ -0,0 +1,21 @@ +# git_commit_only + +Git driven, commit-only. Kargo advances commits by copying manifests to a stage-specific branch; `prod` is promoted via a pull request. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes stage-specific branches and, for the `prod` stage, opens and merges a pull request. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` diff --git a/hack/test/e2e/suites/git_commit_only/feature.go b/hack/test/e2e/suites/git_commit_only/feature.go new file mode 100644 index 0000000000..c0a02fb786 --- /dev/null +++ b/hack/test/e2e/suites/git_commit_only/feature.go @@ -0,0 +1,124 @@ +//nolint:forcetypeassert +package git_commit_only + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("git-commit-only") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-git-commit-only" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the git + // credentials Secret, the per-Stage gitRepo var and the Warehouse git + // subscription with the fork and PAT from the test env. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + utils.UpdateWarehouseGitRepoURL("kargo-demo", creds.RepoURL), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + // test and uat push directly to their stage branches; prod is handled + // separately below because it is gated on a pull request merge. + for _, stage := range []string{"test", "uat"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + creds := utils.RequireGitCreds(ctx, t) + stage := "prod" + + t.Logf("Promoting prod (merging its pull request) to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteWithPRMerge( + ctx, t, + project, stage, freightID, + creds.RepoURL, creds.Password, "open-pr", + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/git_commit_only/git_commit_only_test.go b/hack/test/e2e/suites/git_commit_only/git_commit_only_test.go new file mode 100644 index 0000000000..4a30bcf042 --- /dev/null +++ b/hack/test/e2e/suites/git_commit_only/git_commit_only_test.go @@ -0,0 +1,29 @@ +//go:build e2e +//nolint:forcetypeassert +package git_commit_only + +// This test implements the Git driven, commit-only example from +// https://github.com/akuity/kargo-examples (02-git-driven/01-commit-only). +// Kargo watches the kustomize branch for new commits and advances them from +// stage to stage by copying select contents to the head of a stage-specific +// branch, then pointing the Argo CD Application at the pushed commit. +// +// The prod stage opens a pull request and waits for it to be merged +// (git-open-pr / git-wait-for-pr); the test merges that PR with the configured +// PAT so the promotion can complete. AnalysisTemplate verification is stripped +// (see testdata/review/verification.yaml). + +import ( + "testing" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestGitCommitOnly(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/git_commit_only/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/git_commit_only/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..e5ba2cd7c2 --- /dev/null +++ b/hack/test/e2e/suites/git_commit_only/testdata/argocd/argocd.yaml @@ -0,0 +1,37 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-git-commit-only + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-git-commit-only-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-git-commit-only:{{stage}} + spec: + project: default + source: + # repoURL is substituted at runtime from the test env. + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo pushes each stage's manifests to this branch. + targetRevision: placeholder + path: stages/{{stage}} + kustomize: + images: + - public.ecr.aws/nginx/nginx=public.ecr.aws/nginx/nginx:1.24.0 + destination: + server: https://kubernetes.default.svc + namespace: kargo-git-commit-only-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision diff --git a/hack/test/e2e/suites/git_commit_only/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/git_commit_only/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..dd736ea40f --- /dev/null +++ b/hack/test/e2e/suites/git_commit_only/testdata/kargo/kargo.yaml @@ -0,0 +1,196 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-git-commit-only +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-git-commit-only + labels: + kargo.akuity.io/cred-type: git +stringData: + # repoURL, username and password are substituted at runtime from the test env. + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-git-commit-only +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - commit: ${{ commitFrom(vars.gitRepo).ID }} + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ${{ vars.outPath }} + - uses: git-clear + config: + path: ${{ vars.outPath }} + - uses: copy + config: + inPath: ./src/base + outPath: ${{ vars.outPath }}/base + - uses: copy + config: + inPath: ./src/stages/${{ ctx.stage }} + outPath: ${{ vars.outPath }}/stages/${{ ctx.stage }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: updated manifests copied from main + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-git-commit-only +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + # Watch this branch instead of main. This is the "trunk" for this example. + branch: kustomize +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-git-commit-only +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: commit-only/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-git-commit-only +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: commit-only/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-git-commit-only +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: commit-only/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + - uses: git-push + as: push + config: + path: ${{ vars.outPath }} + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitRepo }} + createTargetBranch: true + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + - uses: git-wait-for-pr + as: wait-for-pr + config: + repoURL: ${{ vars.gitRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs['wait-for-pr'].commit }} + updateTargetRevision: true diff --git a/hack/test/e2e/suites/git_helm_commit_n_image/README.md b/hack/test/e2e/suites/git_helm_commit_n_image/README.md new file mode 100644 index 0000000000..81a35b9239 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_commit_n_image/README.md @@ -0,0 +1,21 @@ +# git_helm_commit_n_image + +Git driven, Helm commit-and-image. Kargo renders the chart to a stage-specific branch; `prod` is promoted via a pull request. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes stage-specific branches and, for the `prod` stage, opens and merges a pull request. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` diff --git a/hack/test/e2e/suites/git_helm_commit_n_image/feature.go b/hack/test/e2e/suites/git_helm_commit_n_image/feature.go new file mode 100644 index 0000000000..2cb25054eb --- /dev/null +++ b/hack/test/e2e/suites/git_helm_commit_n_image/feature.go @@ -0,0 +1,124 @@ +//nolint:forcetypeassert +package git_helm_commit_n_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("git-helm-commit-n-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-git-helm-commit-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the git + // credentials Secret, the per-Stage gitRepo var and the Warehouse git + // subscription with the fork and PAT from the test env. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + utils.UpdateWarehouseGitRepoURL("kargo-demo", creds.RepoURL), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + // test and uat push directly to their stage branches; prod is handled + // separately below because it is gated on a pull request merge. + for _, stage := range []string{"test", "uat"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + creds := utils.RequireGitCreds(ctx, t) + stage := "prod" + + t.Logf("Promoting prod (merging its pull request) to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteWithPRMerge( + ctx, t, + project, stage, freightID, + creds.RepoURL, creds.Password, "open-pr", + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/git_helm_commit_n_image/git_helm_commit_n_image_test.go b/hack/test/e2e/suites/git_helm_commit_n_image/git_helm_commit_n_image_test.go new file mode 100644 index 0000000000..049326151e --- /dev/null +++ b/hack/test/e2e/suites/git_helm_commit_n_image/git_helm_commit_n_image_test.go @@ -0,0 +1,32 @@ +//go:build e2e +//nolint:forcetypeassert +package git_helm_commit_n_image + +// This test implements the Git driven, Helm driven, commit-n-image example from +// https://github.com/akuity/kargo-examples +// (02-git-driven/02-helm-driven/02-commit-n-image). Kargo watches the new-helm +// branch for new commits and the nginx image for new versions, renders the Helm +// chart and advances the result from stage to stage by pushing it to a +// stage-specific branch, then pointing the Argo CD Application at the pushed +// commit. +// +// The prod stage opens a pull request and waits for it to be merged +// (git-open-pr / git-wait-for-pr); the test merges that PR with the configured +// PAT so the promotion can complete. AnalysisTemplate verification is stripped +// (see testdata/review/verification.yaml). + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestGitHelmCommitNImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/git_helm_commit_n_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/git_helm_commit_n_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..889c0439e6 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_commit_n_image/testdata/argocd/argocd.yaml @@ -0,0 +1,34 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-git-helm-commit-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-git-helm-commit-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-git-helm-commit-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will update this branch to move new commits from the helm branch + # and/or new images into this stage + targetRevision: placeholder + path: ./kargo-demo/templates + destination: + server: https://kubernetes.default.svc + namespace: kargo-git-helm-commit-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision \ No newline at end of file diff --git a/hack/test/e2e/suites/git_helm_commit_n_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/git_helm_commit_n_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..f4f8ea8e36 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_commit_n_image/testdata/kargo/kargo.yaml @@ -0,0 +1,210 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-git-helm-commit-image +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-git-helm-commit-image + labels: + kargo.akuity.io/cred-type: git +stringData: + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-git-helm-commit-image +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + # Watch this branch instead of main. This is the "trunk" for this example. + branch: new-helm + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-git-helm-commit-image +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - commit: ${{ commitFrom(vars.gitRepo).ID }} + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ${{ vars.outPath }} + - uses: git-clear + config: + path: ${{ vars.outPath }} + - uses: yaml-update + as: update-image + config: + path: ./src/charts/kargo-demo/values.yaml + updates: + - key: image.name + value: ${{ vars.imageRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + - uses: helm-template + config: + path: ./src/charts/kargo-demo + releaseName: kargo-demo + valuesFiles: + - ./src/charts/kargo-demo/stages/${{ ctx.stage }}/values.yaml + outPath: ${{ vars.outPath }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: ${{ task.outputs['update-image'].commitMessage }} + + message: ${{ task.outputs['update-image'].commitMessage }} + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-git-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + targetBranch: ${{ vars.targetBranch }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-git-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + targetBranch: ${{ vars.targetBranch }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-git-helm-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + - uses: git-push + as: push + config: + path: ${{ vars.outPath }} + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitRepo }} + createTargetBranch: true + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + - uses: git-wait-for-pr + as: wait-for-pr + config: + repoURL: ${{ vars.gitRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs['wait-for-pr'].commit }} + updateTargetRevision: true diff --git a/hack/test/e2e/suites/git_helm_image/README.md b/hack/test/e2e/suites/git_helm_image/README.md new file mode 100644 index 0000000000..caf28bfbaa --- /dev/null +++ b/hack/test/e2e/suites/git_helm_image/README.md @@ -0,0 +1,21 @@ +# git_helm_image + +Git driven, Helm image-only. Kargo renders the chart to a stage-specific branch; `prod` is promoted via a pull request. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes stage-specific branches and, for the `prod` stage, opens and merges a pull request. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` diff --git a/hack/test/e2e/suites/git_helm_image/feature.go b/hack/test/e2e/suites/git_helm_image/feature.go new file mode 100644 index 0000000000..24d1b438ce --- /dev/null +++ b/hack/test/e2e/suites/git_helm_image/feature.go @@ -0,0 +1,124 @@ +//nolint:forcetypeassert +package git_helm_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("git-helm-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-git-helm-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the git + // credentials Secret and the per-Stage gitRepo var with the fork and PAT + // from the test env. The Warehouse subscribes to an image, not git, so no + // git repo URL substitution is applied to it. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + // test and uat push directly to their stage branches; prod is handled + // separately below because it is gated on a pull request merge. + for _, stage := range []string{"test", "uat"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + creds := utils.RequireGitCreds(ctx, t) + stage := "prod" + + t.Logf("Promoting prod (merging its pull request) to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteWithPRMerge( + ctx, t, + project, stage, freightID, + creds.RepoURL, creds.Password, "open-pr", + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/git_helm_image/git_helm_image_test.go b/hack/test/e2e/suites/git_helm_image/git_helm_image_test.go new file mode 100644 index 0000000000..947c79c1a9 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_image/git_helm_image_test.go @@ -0,0 +1,31 @@ +//go:build e2e +//nolint:forcetypeassert +package git_helm_image + +// This test implements the Git driven, Helm-based image-only example from +// https://github.com/akuity/kargo-examples +// (02-git-driven/02-helm-driven/01-image-only). Kargo watches a container +// image repository for new versions, renders the kargo-demo Helm chart with the +// updated image tag, and advances the result from stage to stage by committing +// the rendered manifests to a stage-specific branch, then pointing the Argo CD +// Application at the pushed commit. +// +// The prod stage opens a pull request and waits for it to be merged +// (git-open-pr / git-wait-for-pr); the test merges that PR with the configured +// PAT so the promotion can complete. AnalysisTemplate verification is stripped +// (see testdata/review/verification.yaml). + +import ( + "testing" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestGitHelmImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/git_helm_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/git_helm_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..7c93d20357 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_image/testdata/argocd/argocd.yaml @@ -0,0 +1,34 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-git-helm-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-git-helm-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-git-helm-image:{{stage}} + spec: + project: default + source: + # repoURL is substituted at runtime from the test env. + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will update this branch to move new images into this stage + targetRevision: placeholder + path: ./kargo-demo/templates + destination: + server: https://kubernetes.default.svc + namespace: kargo-git-helm-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision \ No newline at end of file diff --git a/hack/test/e2e/suites/git_helm_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/git_helm_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..19cd8bda68 --- /dev/null +++ b/hack/test/e2e/suites/git_helm_image/testdata/kargo/kargo.yaml @@ -0,0 +1,203 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-git-helm-image +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-git-helm-image + labels: + kargo.akuity.io/cred-type: git +stringData: + # repoURL, username and password are substituted at runtime from the test env. + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-git-helm-image +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-git-helm-image +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - branch: new-helm + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ${{ vars.outPath }} + - uses: git-clear + config: + path: ${{ vars.outPath }} + - uses: yaml-update + as: update-image + config: + path: ./src/charts/kargo-demo/values.yaml + updates: + - key: image.name + value: ${{ vars.imageRepo }}:${{ imageFrom(vars.imageRepo).Tag }} + - uses: helm-template + config: + path: ./src/charts/kargo-demo + releaseName: kargo-demo + valuesFiles: + - ./src/charts/kargo-demo/stages/${{ ctx.stage }}/values.yaml + outPath: ${{ vars.outPath }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: ${{ task.outputs['update-image'].commitMessage }} + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-git-helm-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-git-helm-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-git-helm-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: helm-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + - uses: git-push + as: push + config: + path: ${{ vars.outPath }} + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitRepo }} + createTargetBranch: true + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + - uses: git-wait-for-pr + as: wait-for-pr + config: + repoURL: ${{ vars.gitRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs['wait-for-pr'].commit }} + updateTargetRevision: true diff --git a/hack/test/e2e/suites/git_kustomize_commit_n_image/README.md b/hack/test/e2e/suites/git_kustomize_commit_n_image/README.md new file mode 100644 index 0000000000..fb955e559b --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_commit_n_image/README.md @@ -0,0 +1,21 @@ +# git_kustomize_commit_n_image + +Git driven, Kustomize commit-and-image. `prod` is promoted via a pull request. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes stage-specific branches and, for the `prod` stage, opens and merges a pull request. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` diff --git a/hack/test/e2e/suites/git_kustomize_commit_n_image/feature.go b/hack/test/e2e/suites/git_kustomize_commit_n_image/feature.go new file mode 100644 index 0000000000..4e0f248866 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_commit_n_image/feature.go @@ -0,0 +1,124 @@ +//nolint:forcetypeassert +package git_kustomize_commit_n_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("git-kustomize-commit-n-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-git-kustomize-commit-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the git + // credentials Secret, the per-Stage gitRepo var and the Warehouse git + // subscription with the fork and PAT from the test env. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + utils.UpdateWarehouseGitRepoURL("kargo-demo", creds.RepoURL), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + // test and uat push directly to their stage branches; prod is handled + // separately below because it is gated on a pull request merge. + for _, stage := range []string{"test", "uat"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + creds := utils.RequireGitCreds(ctx, t) + stage := "prod" + + t.Logf("Promoting prod (merging its pull request) to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteWithPRMerge( + ctx, t, + project, stage, freightID, + creds.RepoURL, creds.Password, "open-pr", + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/git_kustomize_commit_n_image/git_kustomize_commit_n_image_test.go b/hack/test/e2e/suites/git_kustomize_commit_n_image/git_kustomize_commit_n_image_test.go new file mode 100644 index 0000000000..8fb9989fbe --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_commit_n_image/git_kustomize_commit_n_image_test.go @@ -0,0 +1,32 @@ +//go:build e2e +//nolint:forcetypeassert +package git_kustomize_commit_n_image + +// This test implements the Git driven, kustomize commit-n-image example from +// https://github.com/akuity/kargo-examples +// (02-git-driven/03-kustomize-driven/02-commit-n-image). Kargo watches the +// kustomize branch for new commits and a container image for new versions, +// advancing them from stage to stage by rendering manifests with the updated +// image and pushing the result to the head of a stage-specific branch, then +// pointing the Argo CD Application at the pushed commit. +// +// The prod stage opens a pull request and waits for it to be merged +// (git-open-pr / git-wait-for-pr); the test merges that PR with the configured +// PAT so the promotion can complete. AnalysisTemplate verification is stripped +// (see testdata/review/verification.yaml). + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestGitKustomizeCommitNImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..14022b7968 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/argocd/argocd.yaml @@ -0,0 +1,34 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-git-kustomize-commit-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-git-kustomize-commit-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-git-kustomize-commit-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will update this branch to move new commits from the kustomize + # branch and/or new images into this stage + targetRevision: placeholder + path: . + destination: + server: https://kubernetes.default.svc + namespace: kargo-git-kustomize-commit-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision \ No newline at end of file diff --git a/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..3cd71025a0 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_commit_n_image/testdata/kargo/kargo.yaml @@ -0,0 +1,205 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-git-kustomize-commit-image +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-git-kustomize-commit-image + labels: + kargo.akuity.io/cred-type: git +stringData: + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-git-kustomize-commit-image +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + # Watch this branch instead of main. This is the "trunk" for this example. + branch: kustomize + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-git-kustomize-commit-image +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - commit: ${{ commitFrom(vars.gitRepo).ID }} + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ${{ vars.outPath }} + - uses: git-clear + config: + path: ${{ vars.outPath }} + - uses: kustomize-set-image + as: update-image + config: + path: ./src/base + images: + - image: public.ecr.aws/nginx/nginx + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./src/stages/${{ ctx.stage }} + outPath: ${{ vars.outPath }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: ${{ task.outputs['update-image'].commitMessage }} + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-git-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + targetBranch: ${{ vars.targetBranch }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-git-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + targetBranch: ${{ vars.targetBranch }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-git-kustomize-commit-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-commit-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + - uses: git-push + as: push + config: + path: ${{ vars.outPath }} + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitRepo }} + createTargetBranch: true + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + - uses: git-wait-for-pr + as: wait-for-pr + config: + repoURL: ${{ vars.gitRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs['wait-for-pr'].commit }} + updateTargetRevision: true diff --git a/hack/test/e2e/suites/git_kustomize_image/README.md b/hack/test/e2e/suites/git_kustomize_image/README.md new file mode 100644 index 0000000000..970910ec80 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_image/README.md @@ -0,0 +1,21 @@ +# git_kustomize_image + +Git driven, Kustomize image-only. Kargo builds the overlay to a stage-specific branch; `prod` is promoted via a pull request. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | +| `git_pat` | GitHub personal access token with **write** access to that fork. The promotion pushes stage-specific branches and, for the `prod` stage, opens and merges a pull request. | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git + git_pat: +``` diff --git a/hack/test/e2e/suites/git_kustomize_image/feature.go b/hack/test/e2e/suites/git_kustomize_image/feature.go new file mode 100644 index 0000000000..f0158e2eb0 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_image/feature.go @@ -0,0 +1,123 @@ +//nolint:forcetypeassert +package git_kustomize_image + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("git-kustomize-image") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-git-kustomize-image" + origin := "kargo-demo" + + feature.Setup(utils.SetupArgocdClient) + // Point the Argo CD ApplicationSet's source at the fork of the demo GitOps + // repository, mirroring the substitution applied to the Kargo fixtures. + feature.Setup(utils.SetupArgoCDFixturesWithRepoURL(project)) + feature.Teardown(utils.TeardownArgoCDFixtures) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the git + // credentials Secret and the per-Stage gitRepo var with the fork and PAT + // from the test env. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + creds := utils.RequireGitCreds(ctx, t) + return utils.NewSetupKargoFixtures( + utils.UpdateGitCredentialsSecret("manifests", creds.RepoURL, creds.Username, creds.Password), + utils.UpdateStagePromotionVar("", "gitRepo", creds.RepoURL), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + // test and uat push directly to their stage branches; prod is handled + // separately below because it is gated on a pull request merge. + for _, stage := range []string{"test", "uat"} { + feature.Assess("promote "+stage, func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + } + + feature.Assess("promote prod", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + creds := utils.RequireGitCreds(ctx, t) + stage := "prod" + + t.Logf("Promoting prod (merging its pull request) to %v \n", freightID) + + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + if _, err := utils.PromoteWithPRMerge( + ctx, t, + project, stage, freightID, + creds.RepoURL, creds.Password, "open-pr", + 15*time.Minute, + ); err != nil { + t.Fatal(err) + } + + _ = utils.WaitForFreightToBeVerified(ctx, t, project, freightID, stage, 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/git_kustomize_image/git_kustomize_image_test.go b/hack/test/e2e/suites/git_kustomize_image/git_kustomize_image_test.go new file mode 100644 index 0000000000..a6c69b486f --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_image/git_kustomize_image_test.go @@ -0,0 +1,32 @@ +//go:build e2e +//nolint:forcetypeassert +package git_kustomize_image + +// This test implements the Git driven, kustomize image-only example from +// https://github.com/akuity/kargo-examples +// (02-git-driven/03-kustomize-driven/01-image-only/01-basic). +// Kargo watches a container image repository for new versions and advances them +// from stage to stage by rendering the kustomize base with the new image to a +// stage-specific branch, then pointing the Argo CD Application at the pushed +// commit. +// +// The prod stage opens a pull request and waits for it to be merged +// (git-open-pr / git-wait-for-pr); the test merges that PR with the configured +// PAT so the promotion can complete. AnalysisTemplate verification is stripped +// (see testdata/review/verification.yaml). + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestGitKustomizeImage(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/git_kustomize_image/testdata/argocd/argocd.yaml b/hack/test/e2e/suites/git_kustomize_image/testdata/argocd/argocd.yaml new file mode 100644 index 0000000000..8e254050b1 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_image/testdata/argocd/argocd.yaml @@ -0,0 +1,33 @@ +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: kargo-git-kustomize-image + namespace: argocd +spec: + generators: + - list: + elements: + - stage: test + - stage: uat + - stage: prod + template: + metadata: + name: kargo-git-kustomize-image-{{stage}} + annotations: + kargo.akuity.io/authorized-stage: kargo-git-kustomize-image:{{stage}} + spec: + project: default + source: + repoURL: https://github.com//kargo-demo-gitops.git + # Kargo will updates this branch to move new images into this stage + targetRevision: placeholder + path: . + destination: + server: https://kubernetes.default.svc + namespace: kargo-git-kustomize-image-{{stage}} + syncPolicy: + syncOptions: + - CreateNamespace=true + ignoreApplicationDifferences: + - jqPathExpressions: + - .spec.source.targetRevision \ No newline at end of file diff --git a/hack/test/e2e/suites/git_kustomize_image/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/git_kustomize_image/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..0e2f35ff76 --- /dev/null +++ b/hack/test/e2e/suites/git_kustomize_image/testdata/kargo/kargo.yaml @@ -0,0 +1,200 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-git-kustomize-image +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: manifests + namespace: kargo-git-kustomize-image + labels: + kargo.akuity.io/cred-type: git +stringData: + repoURL: https://github.com//kargo-demo-gitops.git + username: + password: +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-git-kustomize-image +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: base-promo-process + namespace: kargo-git-kustomize-image +spec: + vars: + - name: gitRepo + - name: targetBranch + - name: outPath + - name: imageRepo + value: public.ecr.aws/nginx/nginx + steps: + - uses: git-clone + config: + repoURL: ${{ vars.gitRepo }} + checkout: + - branch: kustomize + path: ./src + - branch: ${{ vars.targetBranch }} + create: true + path: ${{ vars.outPath }} + - uses: git-clear + config: + path: ${{ vars.outPath }} + - uses: kustomize-set-image + as: update-image + config: + path: ./src/base + images: + - image: public.ecr.aws/nginx/nginx + tag: ${{ imageFrom(vars.imageRepo).Tag }} + - uses: kustomize-build + config: + path: ./src/stages/${{ ctx.stage }} + outPath: ${{ vars.outPath }} + - uses: git-commit + as: commit + config: + path: ${{ vars.outPath }} + message: ${{ task.outputs['update-image'].commitMessage }} + - uses: compose-output + config: + commit: ${{ task.outputs.commit.commit }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-git-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-git-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + as: base + - uses: git-push + config: + path: ${{ vars.outPath }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs.base.commit }} + updateTargetRevision: true +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-git-kustomize-image +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + promotionTemplate: + spec: + vars: + - name: gitRepo + value: https://github.com//kargo-demo-gitops.git + - name: targetBranch + value: kustomize-image/promotion/${{ ctx.promotion }} + - name: outPath + value: ./out + steps: + - task: + name: base-promo-process + - uses: git-push + as: push + config: + path: ${{ vars.outPath }} + generateTargetBranch: true + - uses: git-open-pr + as: open-pr + config: + repoURL: ${{ vars.gitRepo }} + createTargetBranch: true + sourceBranch: ${{ outputs.push.branch }} + targetBranch: ${{ vars.targetBranch }} + - uses: git-wait-for-pr + as: wait-for-pr + config: + repoURL: ${{ vars.gitRepo }} + prNumber: ${{ outputs['open-pr'].pr.id }} + - uses: argocd-update + config: + apps: + - name: ${{ ctx.project }}-${{ ctx.stage }} + sources: + - repoURL: ${{ vars.gitRepo }} + desiredRevision: ${{ outputs['wait-for-pr'].commit }} + updateTargetRevision: true + diff --git a/hack/test/e2e/suites/http_promo_step/README.md b/hack/test/e2e/suites/http_promo_step/README.md new file mode 100644 index 0000000000..47377029f8 --- /dev/null +++ b/hack/test/e2e/suites/http_promo_step/README.md @@ -0,0 +1,19 @@ +# http_promo_step + +Runs an `http` promotion step that POSTs a Slack-style message to a configured endpoint. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `http_endpoint` | URL of an HTTP endpoint that accepts a POST and returns a 2xx response, reachable from **inside the cluster** (e.g. an in-cluster echo `Service`). The promotion's `http` step posts a Slack-style message to it. | + +Example: + +```yaml +context: + http_endpoint: http://echo.default.svc.cluster.local:80 +``` diff --git a/hack/test/e2e/suites/http_promo_step/feature.go b/hack/test/e2e/suites/http_promo_step/feature.go new file mode 100644 index 0000000000..0e26915579 --- /dev/null +++ b/hack/test/e2e/suites/http_promo_step/feature.go @@ -0,0 +1,96 @@ +//nolint:forcetypeassert +package http_promo_step + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("http-promo-step") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-http-promo-step" + origin := "kargo-demo" + stage := "test" + + // Skip the tests if http_endpoint is not set + feature.Setup(utils.SkipIfNoEnvValue([]string{"context", "http_endpoint"})) + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. Substitute the http + // endpoint the promotion posts to with the one configured in the test env. + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + endpointVal, err := envfuncs.GetEnv(ctx, []string{"context", "http_endpoint"}) + if err != nil { + t.Fatalf("cannot get context.http_endpoint from env; "+ + "configure it to an HTTP endpoint that returns 2xx to a POST: %v", err) + } + endpoint := endpointVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdatePromotionTasksVar("promo-process", "url", endpoint), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + feature.Assess("http step posts to the configured endpoint", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + + // The http step fails the promotion on a non-2xx response, so reaching + // Succeeded means the configured endpoint accepted the POST. + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/http_promo_step/http_promo_step_test.go b/hack/test/e2e/suites/http_promo_step/http_promo_step_test.go new file mode 100644 index 0000000000..b2d2ec651e --- /dev/null +++ b/hack/test/e2e/suites/http_promo_step/http_promo_step_test.go @@ -0,0 +1,31 @@ +//go:build e2e +//nolint:forcetypeassert +package http_promo_step + +// This test implements the http promotion step example from +// https://github.com/akuity/kargo-examples (03-features/01-http-promo-step). +// The promotion posts a Slack-style message to an HTTP endpoint. The http step +// fails the promotion on a non-2xx response, so a successful promotion confirms +// the endpoint accepted the POST. +// +// The source example hard-codes the endpoint to the operator's host machine. +// This suite instead reads it from the test env (context.http_endpoint), which +// must point at an HTTP endpoint that accepts the POST and returns 2xx (e.g. an +// echo server). The multi-stage soak/verification pipeline is reduced to a +// single stage focused on the http step. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestHTTPPromoStep(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/http_promo_step/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/http_promo_step/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..b75c922011 --- /dev/null +++ b/hack/test/e2e/suites/http_promo_step/testdata/kargo/kargo.yaml @@ -0,0 +1,81 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-http-promo-step +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-http-promo-step +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: slack + namespace: kargo-http-promo-step +stringData: + token: foo +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-http-promo-step +spec: + vars: + - name: repoURL + value: public.ecr.aws/nginx/nginx + - name: url + # Substituted at runtime from the test env (context.http_endpoint). Must be + # an HTTP endpoint that accepts the POST and returns a 2xx response. + value: http://:8080 + - name: slackChannel + value: C123456 # Totally fake + steps: + - uses: http + config: + method: POST + url: ${{ vars.url }} + headers: + - name: Content-Type + value: application/json + - name: Authorization + value: Bearer ${{ secrets.slack.token }} + body: | + ${{ quote({ + "channel": vars.slackChannel, + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Updated " + ctx.stage + " to use image " + vars.repoURL + ":" + imageFrom(vars.repoURL).Tag + } + } + ] + }) }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-http-promo-step +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/kargo_fixtures/feature.go b/hack/test/e2e/suites/kargo_fixtures/feature.go new file mode 100644 index 0000000000..bc7bee09a1 --- /dev/null +++ b/hack/test/e2e/suites/kargo_fixtures/feature.go @@ -0,0 +1,82 @@ +//nolint:forcetypeassert +package kargo_fixtures + +import ( + "context" + "embed" + "slices" + "testing" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" + "github.com/akuity/kargo/pkg/x/client/generated" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("Example kargo fixtures") + project := "kargo-fixtures" + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + feature.Setup(utils.SetupKargoClients) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("fixture project is created", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + kargoClient := ctx.Value(utils.KargoCLIKey).(generated.APIClient) + // FIXME: do we need to pass client.Options? + // FIXME: move this to helper functions package? + res, httpRes, err := kargoClient.CoreAPI.ListProjects(ctx).Execute() + if httpRes != nil { + _ = httpRes.Body.Close() + } + if err != nil { + t.Fatalf("list projects: %v", err) + } + projects := res.Items + index := slices.IndexFunc(projects, func(proj generated.Project) bool { + return *proj.Metadata.Name == project + }) + if index < 0 { + t.Fatalf("cannot find project `%s`", project) + } + return ctx + }) + + feature.Assess("fixture warehouse is created", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + kargoClient := ctx.Value(utils.KargoCLIKey).(generated.APIClient) + // FIXME: do we need to pass client.Options? + // FIXME: move this to helper functions package? + res, httpRes, err := kargoClient.CoreAPI.ListWarehouses(ctx, project).Execute() + if httpRes != nil { + _ = httpRes.Body.Close() + } + if err != nil { + t.Fatalf("list warehouses: %v", err) + } + warehouses := res.Items + index := slices.IndexFunc(warehouses, func(warehouse generated.Warehouse) bool { + return *warehouse.Metadata.Name == "images" + }) + if index < 0 { + t.Fatalf("cannot find warehouse `images`") + } + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/kargo_fixtures/kargo_fixtures_test.go b/hack/test/e2e/suites/kargo_fixtures/kargo_fixtures_test.go index cc66d504a0..86a2171478 100644 --- a/hack/test/e2e/suites/kargo_fixtures/kargo_fixtures_test.go +++ b/hack/test/e2e/suites/kargo_fixtures/kargo_fixtures_test.go @@ -1,20 +1,15 @@ //go:build e2e && examples + //nolint:forcetypeassert -package kargo_example +package kargo_fixtures // This test shows an example of using YAML files to define Kargo fixtures to use in tests. // It sets up fixtures and verifies that they exist. import ( - "context" - "slices" "testing" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - "github.com/akuity/kargo/hack/test/e2e/framework/utils" - "github.com/akuity/kargo/pkg/x/client/generated" ) // This file provides necessary setup for a test package to run environment setup for e2e test. @@ -24,56 +19,5 @@ func TestMain(m *testing.M) { } func TestKargoFixtures(t *testing.T) { - feature := features.New("Example kargo fixtures") - project := "kargo-fixtures" - - feature.Setup(utils.SetupKargoClients) - feature.Setup(utils.SetupKargoFixtures) - feature.Teardown(utils.TeardownKargoFixtures) - - feature.Assess("fixture project is created", - func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - kargoClient := ctx.Value(utils.KargoCLIKey).(generated.APIClient) - // FIXME: do we need to pass client.Options? - // FIXME: move this to helper functions package? - res, httpRes, err := kargoClient.CoreAPI.ListProjects(ctx).Execute() - if httpRes != nil { - _ = httpRes.Body.Close() - } - if err != nil { - t.Fatalf("list projects: %v", err) - } - projects := res.Items - index := slices.IndexFunc(projects, func(proj generated.Project) bool { - return *proj.Metadata.Name == project - }) - if index < 0 { - t.Fatalf("cannot find project `%s`", project) - } - return ctx - }) - - feature.Assess("fixture warehouse is created", - func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - kargoClient := ctx.Value(utils.KargoCLIKey).(generated.APIClient) - // FIXME: do we need to pass client.Options? - // FIXME: move this to helper functions package? - res, httpRes, err := kargoClient.CoreAPI.ListWarehouses(ctx, project).Execute() - if httpRes != nil { - _ = httpRes.Body.Close() - } - if err != nil { - t.Fatalf("list warehouses: %v", err) - } - warehouses := res.Items - index := slices.IndexFunc(warehouses, func(warehouse generated.Warehouse) bool { - return *warehouse.Metadata.Name == "images" - }) - if index < 0 { - t.Fatalf("cannot find warehouse `images`") - } - return ctx - }) - - utils.TestEnv.Test(t, feature.Feature()) + utils.TestEnv.Test(t, feature()) } diff --git a/hack/test/e2e/suites/kargo_promotion_fail/feature.go b/hack/test/e2e/suites/kargo_promotion_fail/feature.go new file mode 100644 index 0000000000..fdb039954d --- /dev/null +++ b/hack/test/e2e/suites/kargo_promotion_fail/feature.go @@ -0,0 +1,59 @@ +//nolint:forcetypeassert +package kargo_promotion_fail + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("Example kargo promotion") + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + project := "kargo-promotion-fail" + // Setup and teardown fixtures from testdata folder + feature.Setup(utils.SetupKargoClients) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("promotion fails", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + stage := "kargo-promotion-fail-stage" + origin := "images" + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 5*time.Minute) + if err != nil { + t.Fatal(err) + } + + _, err = utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, anyFreightID, + kargoapi.PromotionPhaseFailed, + 5*time.Minute) + if err != nil { + t.Fatal(err) + } + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/kargo_promotion_fail/kargo_promotion_fail_test.go b/hack/test/e2e/suites/kargo_promotion_fail/kargo_promotion_fail_test.go index 8114468c9d..771f55d446 100644 --- a/hack/test/e2e/suites/kargo_promotion_fail/kargo_promotion_fail_test.go +++ b/hack/test/e2e/suites/kargo_promotion_fail/kargo_promotion_fail_test.go @@ -1,19 +1,13 @@ //go:build e2e //nolint:forcetypeassert -package kargo_promotion_fail_test +package kargo_promotion_fail // This test shows an example of running Kargo promotion with stage defined in YAML fixtures. // Specifically it executes the `fail` stage and checks that promotion fails. import ( - "context" "testing" - "time" - "sigs.k8s.io/e2e-framework/pkg/envconf" - "sigs.k8s.io/e2e-framework/pkg/features" - - kargoapi "github.com/akuity/kargo/api/v1alpha1" "github.com/akuity/kargo/hack/test/e2e/framework/utils" ) @@ -24,34 +18,5 @@ func TestMain(m *testing.M) { } func TestKargoPromotionFail(t *testing.T) { - feature := features.New("Example kargo promotion") - project := "kargo-promotion-fail" - // Setup and teardown fixtures from testdata folder - feature.Setup(utils.SetupKargoClients) - feature.Setup(utils.SetupKargoFixtures) - feature.Teardown(utils.TeardownKargoFixtures) - - feature.Assess("promotion fails", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { - stage := "kargo-promotion-fail-stage" - origin := "images" - - anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 5*time.Minute) - if err != nil { - t.Fatal(err) - } - - _, err = utils.PromoteAndWaitForPhase( - ctx, t, - project, stage, anyFreightID, - kargoapi.PromotionPhaseFailed, - 5*time.Minute) - if err != nil { - t.Fatal(err) - } - - return ctx - }) - - utils.TestEnv.Test(t, feature.Feature()) - + utils.TestEnv.Test(t, feature()) } diff --git a/hack/test/e2e/suites/shared/shared_test.go b/hack/test/e2e/suites/shared/shared_test.go new file mode 100644 index 0000000000..64fb3d9f65 --- /dev/null +++ b/hack/test/e2e/suites/shared/shared_test.go @@ -0,0 +1,45 @@ +//go:build e2e && shared + +//nolint:forcetypeassert +package shared_test + +// This suite runs all test features registered in utils.TestFeatures +// This is useful to share multiple suites in the same kind cluster (for performance) +// Suites need to be imported in order to run as a part of shared test + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_chart" + _ "github.com/akuity/kargo/hack/test/e2e/suites/kargo_fixtures" + + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_chart" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_chart_n_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_commit_n_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_image_chart_repo" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_helm_image_git_repo" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_kustomize_commit_n_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_kustomize_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_update" + _ "github.com/akuity/kargo/hack/test/e2e/suites/argocd_wait" + _ "github.com/akuity/kargo/hack/test/e2e/suites/git_commit_only" + _ "github.com/akuity/kargo/hack/test/e2e/suites/git_helm_commit_n_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/git_helm_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/git_kustomize_commit_n_image" + _ "github.com/akuity/kargo/hack/test/e2e/suites/git_kustomize_image" + // _ "github.com/akuity/kargo/hack/test/e2e/suites/http_promo_step" + _ "github.com/akuity/kargo/hack/test/e2e/suites/kargo_fixtures" + _ "github.com/akuity/kargo/hack/test/e2e/suites/kargo_promotion_fail" + _ "github.com/akuity/kargo/hack/test/e2e/suites/soak_time" + _ "github.com/akuity/kargo/hack/test/e2e/suites/vars" + _ "github.com/akuity/kargo/hack/test/e2e/suites/yaml_parse_update" +) + +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestAll(t *testing.T) { + utils.TestEnv.TestInParallel(t, utils.TestFeatures...) +} diff --git a/hack/test/e2e/suites/soak_time/feature.go b/hack/test/e2e/suites/soak_time/feature.go new file mode 100644 index 0000000000..88a8bd5418 --- /dev/null +++ b/hack/test/e2e/suites/soak_time/feature.go @@ -0,0 +1,127 @@ +//nolint:forcetypeassert +package soak_time + +import ( + "context" + "embed" + "net/http" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +// soakTime must match the requiredSoakTime configured on the uat Stage in +// testdata/kargo/kargo.yaml. +const soakTime = 2 * time.Minute + +func feature() features.Feature { + feature := features.New("soak-time") + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + + project := "kargo-soak-time" + origin := "kargo-demo" + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. + feature.Setup(utils.RequireKargoCli) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + feature.Assess("uat only accepts freight after soak time", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + // Record the soak clock start before the freight enters test: the + // freight becomes "currently in" test no earlier than this, so uat + // cannot become eligible before soakStart + soakTime. + soakStart := time.Now() + + t.Logf("Promoting test to %v \n", freightID) + if err := utils.RefreshStage(ctx, t, project, "test"); err != nil { + t.Fatal(err) + } + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, "test", freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ); err != nil { + t.Fatal(err) + } + utils.WaitForFreightToBeVerified(ctx, t, project, freightID, "test", 10*time.Minute) + + // The freight is now verified in test but has not soaked long enough, + // so uat must reject a promotion attempt with 400 Bad Request. + if err := utils.RefreshStage(ctx, t, project, "uat"); err != nil { + t.Fatal(err) + } + status, err := utils.TryPromoteToStage(ctx, project, "uat", freightID) + if err == nil || status != http.StatusBadRequest { + t.Fatalf( + "expected uat to reject freight before soak time with 400, got status %d, err %v", + status, err) + } + if elapsed := time.Since(soakStart); elapsed >= soakTime { + t.Fatalf( + "soak time %v already elapsed (%v) before the rejection check; test is inconclusive", + soakTime, + elapsed) + } + t.Logf("uat correctly rejected freight before soak time (status %d)", status) + + // Promote to uat. StartPromotion retries the 400 until the freight has + // soaked, so this call blocks until the soak time elapses and succeeds. + t.Logf("Promoting uat (waiting out the %v soak time) \n", soakTime) + if _, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, "uat", freightID, + kargoapi.PromotionPhaseSucceeded, + soakTime+5*time.Minute, + ); err != nil { + t.Fatal(err) + } + + if elapsed := time.Since(soakStart); elapsed < soakTime { + t.Fatalf("uat promotion succeeded after %v, before the required soak time of %v", elapsed, soakTime) + } else { + t.Logf("uat promotion succeeded after %v (>= soak time %v)", elapsed, soakTime) + } + + utils.WaitForFreightToBeVerified(ctx, t, project, freightID, "uat", 10*time.Minute) + + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/soak_time/soak_time_test.go b/hack/test/e2e/suites/soak_time/soak_time_test.go new file mode 100644 index 0000000000..f6fa22a049 --- /dev/null +++ b/hack/test/e2e/suites/soak_time/soak_time_test.go @@ -0,0 +1,29 @@ +//go:build e2e +//nolint:forcetypeassert +package soak_time + +// This test implements the soak-time example from +// https://github.com/akuity/kargo-examples (03-features/02-soak-time). Freight +// must "soak" in an upstream stage for a required duration before it becomes +// eligible for promotion to the downstream stage. The example uses a 10m soak; +// this suite reduces it to 2m so the test can exercise the behavior end to end. +// +// The test promotes freight to test, then asserts that uat rejects the freight +// until the soak time has elapsed and only accepts it afterwards. +// AnalysisTemplate verification is stripped (see testdata/review/verification.yaml). + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestSoakTime(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/soak_time/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/soak_time/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..04b9e7162f --- /dev/null +++ b/hack/test/e2e/suites/soak_time/testdata/kargo/kargo.yaml @@ -0,0 +1,86 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-soak-time +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-soak-time +spec: + subscriptions: + - image: + repoURL: public.ecr.aws/nginx/nginx # Watch for new versions of this image + semverConstraint: ^1.24.0 +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-soak-time +spec: + steps: + - uses: compose-output + config: + value: foo +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: test + namespace: kargo-soak-time +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: uat + namespace: kargo-soak-time +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - test + # Freight must soak in test for this long before it can be promoted here. + # Reduced from the example's 10m to keep the e2e run short. + requiredSoakTime: 2m + promotionTemplate: + spec: + steps: + - task: + name: promo-process +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: prod + namespace: kargo-soak-time +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + stages: + - uat + requiredSoakTime: 2m + promotionTemplate: + spec: + steps: + - task: + name: promo-process diff --git a/hack/test/e2e/suites/vars/feature.go b/hack/test/e2e/suites/vars/feature.go new file mode 100644 index 0000000000..f204349284 --- /dev/null +++ b/hack/test/e2e/suites/vars/feature.go @@ -0,0 +1,110 @@ +//nolint:forcetypeassert +package vars + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("vars") + + project := "kargo-vars" + origin := "vars" + stage := "vars" + + // This setup step is necessary to use this feature as a part of shared package test + // It sets the path to look up the fixtures files. + feature.Setup(utils.TestData(TestData)) + feature.Setup(utils.SetupKargoClients) + + // The chart Warehouse and pokeapi are public, so no substitution is needed. + feature.Setup(utils.RequireKargoCli) + feature.Setup(utils.SetupKargoFixtures) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + feature.Assess("vars resolve at every level", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + promotion, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ) + if err != nil { + t.Fatal(err) + } + + // key -> (var level, expected pokemon name) + expected := []struct { + key string + level string + value string + }{ + {"pokemon1", "Stage spec.vars", "pikachu"}, + {"pokemon2", "Stage promotionTemplate.spec.vars", "charmander"}, + {"pokemon3", "Stage promotionTemplate.spec.steps[].vars", "bulbasaur"}, + {"pokemon4", "PromotionTask spec.vars", "ditto"}, + } + for _, e := range expected { + got, ok := utils.PromotionStepOutput(promotion, "output", e.key) + if !ok { + t.Fatalf( + "promotion output is missing %q (%s); state: %v", + e.key, e.level, promotion.Status.GetState()) + } + if got != e.value { + t.Fatalf("var at %s resolved to %q, want %q", e.level, got, e.value) + } + t.Logf("var at %s resolved to %q", e.level, got) + } + + return ctx + }) + + feature.Assess("stage is verified", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + // The AnalysisTemplate argument is populated from vars.pokemon_1, the + // fifth var level; a successful verification confirms it resolved. + utils.WaitForStageVerified(ctx, t, project, stage, 10*time.Minute) + t.Logf("stage %q verified successfully", stage) + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/vars/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/vars/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..8da3894f20 --- /dev/null +++ b/hack/test/e2e/suites/vars/testdata/kargo/kargo.yaml @@ -0,0 +1,127 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-vars +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: vars + namespace: kargo-vars +spec: + subscriptions: + - chart: + repoURL: https://grafana-community.github.io/helm-charts + name: grafana +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: PromotionTask +metadata: + name: promo-process + namespace: kargo-vars +spec: + vars: + - name: pokemon_3 + - name: pokemon_4 + value: ditto + steps: + # Each step looks up a pokemon by a var defined at a different level and + # records the name pokeapi returns. If the var resolved correctly, the + # returned name equals the var's value. + - uses: http + as: query-1 + config: + # Defined in Stage spec.vars + url: https://pokeapi.co/api/v2/pokemon/${{ vars.pokemon_1 }} + outputs: + - name: response + fromExpression: response.body.name + - uses: http + as: query-2 + config: + # Defined in Stage spec.promotionTemplate.spec.vars + url: https://pokeapi.co/api/v2/pokemon/${{ vars.pokemon_2 }} + outputs: + - name: response + fromExpression: response.body.name + - uses: http + as: query-3 + config: + # Defined in Stage spec.promotionTemplate.spec.steps[].vars + url: https://pokeapi.co/api/v2/pokemon/${{ vars.pokemon_3 }} + outputs: + - name: response + fromExpression: response.body.name + - uses: http + as: query-4 + config: + # Defined in PromotionTask spec.vars + url: https://pokeapi.co/api/v2/pokemon/${{ vars.pokemon_4 }} + outputs: + - name: response + fromExpression: response.body.name + # Surface each resolved name as the task's output. + - uses: compose-output + config: + pokemon1: ${{ task.outputs['query-1'].response }} + pokemon2: ${{ task.outputs['query-2'].response }} + pokemon3: ${{ task.outputs['query-3'].response }} + pokemon4: ${{ task.outputs['query-4'].response }} +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: vars + namespace: kargo-vars +spec: + vars: + - name: pokemon_1 + value: pikachu + requestedFreight: + - origin: + kind: Warehouse + name: vars + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: pokemon_2 + value: charmander + steps: + - task: + name: promo-process + as: promo + vars: + - name: pokemon_3 + value: bulbasaur + # Re-surface the task output at the top level so the test can read it. + - uses: compose-output + as: output + config: + pokemon1: ${{ outputs.promo.pokemon1 }} + pokemon2: ${{ outputs.promo.pokemon2 }} + pokemon3: ${{ outputs.promo.pokemon3 }} + pokemon4: ${{ outputs.promo.pokemon4 }} + verification: + analysisTemplates: + - name: pokemon-query + args: + - name: pokemon + value: ${{ vars.pokemon_1 }} +--- +apiVersion: argoproj.io/v1alpha1 +kind: AnalysisTemplate +metadata: + name: pokemon-query + namespace: kargo-vars +spec: + args: + - name: pokemon + metrics: + - name: pokemon-xp + provider: + web: + url: https://pokeapi.co/api/v2/pokemon/{{args.pokemon}} + jsonPath: "{$.base_experience}" + successCondition: result < 200 diff --git a/hack/test/e2e/suites/vars/vars_test.go b/hack/test/e2e/suites/vars/vars_test.go new file mode 100644 index 0000000000..410d73f3e7 --- /dev/null +++ b/hack/test/e2e/suites/vars/vars_test.go @@ -0,0 +1,34 @@ +//go:build e2e +//nolint:forcetypeassert +package vars + +// This test implements the vars example from +// https://github.com/akuity/kargo-examples (03-features/04-vars). It exercises +// promotion variables defined at four different levels, each used to look up a +// pokemon by name via pokeapi.co. Because pokeapi echoes the requested name +// back, the test can assert that every var resolved to the expected value: +// +// pokemon_1 (pikachu) -- Stage spec.vars +// pokemon_2 (charmander) -- Stage promotionTemplate.spec.vars +// pokemon_3 (bulbasaur) -- Stage promotionTemplate.spec.steps[].vars +// pokemon_4 (ditto) -- PromotionTask spec.vars +// +// The fifth level -- a var in an AnalysisTemplate argument -- is covered by the +// "stage is verified" assessment, which requires the pokemon-query verification +// (parameterized by vars.pokemon_1) to succeed. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestVars(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/suites/yaml_parse_update/README.md b/hack/test/e2e/suites/yaml_parse_update/README.md new file mode 100644 index 0000000000..64ae095cd1 --- /dev/null +++ b/hack/test/e2e/suites/yaml_parse_update/README.md @@ -0,0 +1,19 @@ +# yaml_parse_update + +Parses a value from a YAML file in the repo and writes it into another field with `yaml-update`, then verifies (via a second `yaml-parse`) that the field was updated. Nothing is committed back to the repo. + +## Required environment context + +This suite reads the following from the `context` section of the env file +passed with `-env-file` (see [`../../envs`](../../envs)): + +| Variable | Description | +| --- | --- | +| `kargo_demo_gitops_repo` | HTTPS URL of a fork of the `kargo-demo-gitops` repository. Substituted into the fixtures at runtime (the Warehouse subscription, the promotion's `gitRepo` var, and/or the Argo CD `ApplicationSet` source). | + +Example: + +```yaml +context: + kargo_demo_gitops_repo: https://github.com//kargo-demo-gitops.git +``` diff --git a/hack/test/e2e/suites/yaml_parse_update/feature.go b/hack/test/e2e/suites/yaml_parse_update/feature.go new file mode 100644 index 0000000000..2ec85e1433 --- /dev/null +++ b/hack/test/e2e/suites/yaml_parse_update/feature.go @@ -0,0 +1,117 @@ +//nolint:forcetypeassert +package yaml_parse_update + +import ( + "context" + "embed" + "testing" + "time" + + "sigs.k8s.io/e2e-framework/pkg/envconf" + "sigs.k8s.io/e2e-framework/pkg/features" + + kargoapi "github.com/akuity/kargo/api/v1alpha1" + "github.com/akuity/kargo/hack/test/e2e/envfuncs" + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +func init() { + utils.TestFeatures = append(utils.TestFeatures, feature()) +} + +var ( + //go:embed testdata/* + TestData embed.FS +) + +func feature() features.Feature { + feature := features.New("yaml-parse-update") + + feature.Setup(utils.TestData(TestData)) + + project := "kargo-yaml-parse-update" + origin := "kargo-demo" + stage := "yaml-parse-update" + + feature.Setup(utils.SetupKargoClients) + + // Setup and teardown fixtures from testdata folder. The demo repo is public, + // so no credentials are needed; only the repo URL is substituted with the + // fork from the test env (Warehouse subscription and the promotion var). + feature.Setup(utils.RequireKargoCli) + feature.Setup(func(ctx context.Context, t *testing.T, cfg *envconf.Config) context.Context { + kargoDemoRepoVal, err := envfuncs.GetEnv(ctx, []string{"context", "kargo_demo_gitops_repo"}) + if err != nil { + t.Fatalf("cannot get kargo_demo_gitops_repo %v", err) + } + kargoDemoRepo := kargoDemoRepoVal.(string) + + return utils.NewSetupKargoFixtures( + utils.UpdateWarehouseGitRepoURL("kargo-demo", kargoDemoRepo), + utils.UpdateStagePromotionVar("", "repoURL", kargoDemoRepo), + )(ctx, t, cfg) + }) + feature.Teardown(utils.TeardownKargoFixtures) + + feature.Assess("require freight", func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + t.Logf("Require freight \n") + + anyFreightID, err := utils.WaitForLatestFreight(ctx, project, origin, 10*time.Minute) + if err != nil { + t.Fatal(err) + } + + t.Logf("Freight: %v", anyFreightID) + return context.WithValue(ctx, envfuncs.ContextKey("freight_id"), anyFreightID) + }) + + feature.Assess("yaml-update updates the parsed field", + func(ctx context.Context, t *testing.T, _ *envconf.Config) context.Context { + freightID := ctx.Value(envfuncs.ContextKey("freight_id")).(string) + + t.Logf("Promoting %v to %v \n", stage, freightID) + if err := utils.RefreshStage(ctx, t, project, stage); err != nil { + t.Fatal(err) + } + promotion, err := utils.PromoteAndWaitForPhase( + ctx, t, + project, stage, freightID, + kargoapi.PromotionPhaseSucceeded, + 10*time.Minute, + ) + if err != nil { + t.Fatal(err) + } + + original, ok := utils.PromotionStepOutput(promotion, "output", "originalImage") + if !ok { + t.Fatalf("promotion output %q is missing originalImage; state: %v", + "output", promotion.Status.GetState()) + } + updated, ok := utils.PromotionStepOutput(promotion, "output", "updatedImage") + if !ok { + t.Fatalf("promotion output %q is missing updatedImage; state: %v", + "output", promotion.Status.GetState()) + } + expected, ok := utils.PromotionStepOutput(promotion, "output", "expectedImage") + if !ok { + t.Fatalf("promotion output %q is missing expectedImage; state: %v", + "output", promotion.Status.GetState()) + } + + // yaml-update must have written the new value, which the second + // yaml-parse then read back. + if updated != expected { + t.Fatalf("yaml-update did not update the field: got image.name %q, want %q", updated, expected) + } + // The field must have actually changed from its original value. + if updated == original { + t.Fatalf("yaml-update left the field unchanged at %q", original) + } + + t.Logf("yaml-update changed image.name from %q to %q", original, updated) + return ctx + }) + + return feature.Feature() +} diff --git a/hack/test/e2e/suites/yaml_parse_update/testdata/kargo/kargo.yaml b/hack/test/e2e/suites/yaml_parse_update/testdata/kargo/kargo.yaml new file mode 100644 index 0000000000..ef37d93fb5 --- /dev/null +++ b/hack/test/e2e/suites/yaml_parse_update/testdata/kargo/kargo.yaml @@ -0,0 +1,74 @@ +apiVersion: kargo.akuity.io/v1alpha1 +kind: Project +metadata: + name: kargo-yaml-parse-update +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Warehouse +metadata: + name: kargo-demo + namespace: kargo-yaml-parse-update +spec: + subscriptions: + - git: + repoURL: https://github.com//kargo-demo-gitops.git + branch: new-helm +--- +apiVersion: kargo.akuity.io/v1alpha1 +kind: Stage +metadata: + name: yaml-parse-update + namespace: kargo-yaml-parse-update +spec: + requestedFreight: + - origin: + kind: Warehouse + name: kargo-demo + sources: + direct: true + promotionTemplate: + spec: + vars: + - name: repoURL + value: https://github.com//kargo-demo-gitops.git + - name: newImageName + value: public.ecr.aws/nginx/nginx:1.25.0 + steps: + # Clone the commit the Warehouse discovered into ./src. + - uses: git-clone + config: + repoURL: ${{ vars.repoURL }} + checkout: + - commit: ${{ commitFrom(vars.repoURL, warehouse("kargo-demo")).ID }} + path: ./src + # Parse the current image.name from the values file (the "source" value). + - uses: yaml-parse + as: source + config: + path: ./src/charts/kargo-demo/values.yaml + outputs: + - name: imageName + fromExpression: image.name + # Update image.name in the working copy. There is no git-commit / git-push + # step, so the change is never written back to the repository. + - uses: yaml-update + config: + path: ./src/charts/kargo-demo/values.yaml + updates: + - key: image.name + value: ${{ vars.newImageName }} + # Re-parse the updated file to read the field back for verification. + - uses: yaml-parse + as: updated + config: + path: ./src/charts/kargo-demo/values.yaml + outputs: + - name: imageName + fromExpression: image.name + # Surface the before/after values so the test can assert the update. + - uses: compose-output + as: output + config: + originalImage: ${{ outputs.source.imageName }} + updatedImage: ${{ outputs.updated.imageName }} + expectedImage: ${{ vars.newImageName }} diff --git a/hack/test/e2e/suites/yaml_parse_update/yaml_parse_update_test.go b/hack/test/e2e/suites/yaml_parse_update/yaml_parse_update_test.go new file mode 100644 index 0000000000..05fb4e3b90 --- /dev/null +++ b/hack/test/e2e/suites/yaml_parse_update/yaml_parse_update_test.go @@ -0,0 +1,31 @@ +//go:build e2e +//nolint:forcetypeassert +package yaml_parse_update + +// This test is adapted from the yaml-parse / yaml-update example at +// https://github.com/akuity/kargo-examples (03-features/03-yaml-parse-update). +// +// Differences from the source example: +// - It uses the shared kargo-demo-gitops repository (like the other suites) +// instead of the kargo-examples repository, parsing/updating image.name in +// charts/kargo-demo/values.yaml on the new-helm branch. +// - The git-commit and git-push steps are removed, so the working copy is +// never written back to the repository. +// - A second yaml-parse step re-reads the updated field, and the test asserts +// that yaml-update actually changed it. + +import ( + "testing" + + "github.com/akuity/kargo/hack/test/e2e/framework/utils" +) + +// This file provides necessary setup for a test package to run environment setup for e2e test. +// Because golang doesn't allow import of test code, this code needs to be added to each test package. +func TestMain(m *testing.M) { + utils.InitEnv(m) +} + +func TestYAMLParseUpdate(t *testing.T) { + utils.TestEnv.Test(t, feature()) +} diff --git a/hack/test/e2e/values.argocd.test.yaml b/hack/test/e2e/values.argocd.test.yaml new file mode 100644 index 0000000000..82645b9615 --- /dev/null +++ b/hack/test/e2e/values.argocd.test.yaml @@ -0,0 +1,3 @@ +configs: + secret: + argocdServerAdminPassword: $2a$10$Zrhhie4vLz5ygtVSaif6o.qN36jgs6vjtMBdM6yrU1FOeiAAMMxOm \ No newline at end of file diff --git a/hack/test/e2e/values.test.yaml b/hack/test/e2e/values.test.yaml new file mode 100644 index 0000000000..9c7bf03b9a --- /dev/null +++ b/hack/test/e2e/values.test.yaml @@ -0,0 +1,57 @@ +image: + repository: docker.io/library/kargo + tag: dev +global: + clusterSecretsNamespace: kargo-cluster-secrets +api: + logLevel: DEBUG + tls: + enabled: false + permissiveCORSPolicyEnabled: true + probes: + enabled: false + adminAccount: + # The password is 'admin' + passwordHash: "$2a$10$Zrhhie4vLz5ygtVSaif6o.qN36jgs6vjtMBdM6yrU1FOeiAAMMxOm" + tokenSigningKey: iwishtowashmyirishwristwatch + rollouts: + logs: + enabled: true + # This is Pride and Prejudice. It's an adequate volume of text to validate + # that log streaming via SSE truly works as intended. + urlTemplate: https://www.gutenberg.org/files/1342/1342-0.txt + oidc: + enabled: true + dex: + enabled: true + connectors: + - id: mock + name: Example + type: mockCallback + probes: + enabled: false + admins: + claims: + email: + # This email claim is hard-coded in the Dex mockCallback connector + - kilgore@kilgore.trout +controller: + logLevel: DEBUG + images: + cache: + cacheByTagPolicy: Force +crds: + install: true +externalWebhooksServer: + logLevel: DEBUG + host: localhost:30083 + tls: + enabled: false + probes: + enabled: false +garbageCollector: + logLevel: DEBUG +managementController: + logLevel: DEBUG +webhooksServer: + logLevel: DEBUG