diff --git a/Makefile b/Makefile index 796761c..84e3270 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ GO_REQUIRED_VERSION = 1.21 # Uncomment below if you need to override the version. # GOLANGCILINT_VERSION ?= 1.54.0 -GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/uptest $(GO_PROJECT)/cmd/crddiff $(GO_PROJECT)/cmd/buildtagger $(GO_PROJECT)/cmd/updoc $(GO_PROJECT)/cmd/ttr $(GO_PROJECT)/cmd/perf $(GO_PROJECT)/cmd/linter/lint-provider-family +GO_STATIC_PACKAGES = $(GO_PROJECT)/cmd/crddiff $(GO_PROJECT)/cmd/buildtagger $(GO_PROJECT)/cmd/updoc $(GO_PROJECT)/cmd/ttr $(GO_PROJECT)/cmd/perf $(GO_PROJECT)/cmd/linter/lint-provider-family GO_LDFLAGS += -X $(GO_PROJECT)/internal/version.Version=$(VERSION) GO_SUBDIRS += cmd internal GO111MODULE = on diff --git a/README.md b/README.md index cfdb2a8..f3d765a 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,24 @@ -# UPTEST - -The end to end integration testing tool for Crossplane providers and configurations. - -Uptest comes as a binary which can be installed from the releases section. It runs end-to-end tests -by applying the provided examples and waiting for the expected conditions. Other than that, it enables templating to -insert dynamic values into the examples and supports running scripts as hooks just before and right after applying -the examples. - -## Usage - -```shell -$ uptest e2e --help -usage: uptest e2e [] [] - -Run e2e tests for manifests by applying them to a control plane and waiting until a given condition is met. - -Flags: - --help Show context-sensitive help (also try --help-long and --help-man). - --data-source="" File path of data source that will be used for injection some values. - --setup-script="" Script that will be executed before running tests. - --teardown-script="" Script that will be executed after running tests. - --default-timeout=1200 Default timeout in seconds for the test. Timeout could be overridden per resource using - "uptest.upbound.io/timeout" annotation. - --default-conditions="Ready" Comma seperated list of default conditions to wait for a successful test. Conditions could be - overridden per resource using "uptest.upbound.io/conditions" annotation. - -Args: - [] List of manifests. Value of this option will be used to trigger/configure the tests.The possible usage: - 'provider-aws/examples/s3/bucket.yaml,provider-gcp/examples/storage/bucket.yaml': The comma separated resources - are used as test inputs. If this option is not set, 'MANIFEST_LIST' env var is used as default. -``` - -Uptest expects a running control-plane (a.k.a. k8s + crossplane) where required providers are running and/or required -configuration were applied. - -Example run: - -```shell -uptest e2e examples/user.yaml,examples/bucket.yaml --setup-script="test/hooks/setup.sh" -``` - -### Injecting Dynamic Values (and Datasource) - -Uptest supports injecting dynamic values into the examples by using a data source. The data source is a yaml file -storing key-value pairs. The values can be used in the examples by using the following syntax: - -``` -${data.key} -``` - -Example data source file content: - -```yaml -aws_account_id: 123456789012 -aws_region: us-east-1 -``` - -Example manifest: - -```yaml -apiVersion: athena.aws.upbound.io/v1beta1 -kind: DataCatalog -metadata: - labels: - testing.upbound.io/example-name: example - name: example -spec: - forProvider: - description: Example Athena data catalog - parameters: - function: arn:aws:lambda:${data.aws_region}:${data.aws_account_id}:function:upbound-example-function - region: us-west-1 - tags: - Name: example-athena-data-catalog - type: LAMBDA -``` - -Uptest also supports generating random strings as follows: - -``` -${Rand.RFC1123Subdomain} -``` - -Example Manifest: - -```yaml -apiVersion: s3.aws.upbound.io/v1beta1 -kind: Bucket -metadata: - name: ${Rand.RFC1123Subdomain} - labels: - testing.upbound.io/example-name: s3 -spec: - forProvider: - region: us-west-1 - objectLockEnabled: true - tags: - Name: SampleBucket -``` - -### Hooks - -There are 6 types of hooks that can be used to customize the test flow: - -1. `setup-script`: This hook will be executed before running the tests case. It is useful to set up the control plane - before running the tests. For example, you can use it to create a provider config and your cloud credentials. This - can be configured via `--setup-script` flag as a relative path to where uptest is executed. -2. `teardown-script`: This hook will be executed after running the tests case. This can be configured via - `--teardown-script` flag as a relative path to where uptest is executed. -3. `pre-assert-hook`: This hook will be executed before running the assertions and after applying a specific manifest. - This can be configured via `uptest.upbound.io/pre-assert-hook` annotation on the manifest as a relative path to the - manifest file. -4. `post-assert-hook`: This hook will be executed after running the assertions. This can be configured via - `uptest.upbound.io/post-assert-hook` annotation on the manifest as a relative path to the manifest file. -5. `pre-delete-hook`: This hook will be executed just before deleting the resource. This can be configured via - `uptest.upbound.io/pre-delete-hook` annotation on the manifest as a relative path to the manifest file. -6. `post-delete-hook`: This hook will be executed right after the resource is deleted. This can be configured via - `uptest.upbound.io/post-delete-hook` annotation on the manifest as a relative path to the manifest file. - -> All hooks need to be executables, please make sure to set the executable bit on your scripts, e.g. with `chmod +x`. - -### Troubleshooting - -Uptest uses [kuttl](https://kuttl.dev/) under the hood and generates a `kuttl` test case based on the provided input. -You can inspect the generated kuttl test case by checking the temporary test directory which is printed in the beginning -of uptest e2e output. For example: - -```shell -Running kuttl tests at /var/folders/_5/jc7399qx6cn6t5535npv9z4c0000gn/T/uptest-e2e -``` +# official-providers-ci +Repository for the CI tooling of the Upbound official providers repositories: +- `crddiff`: A tool for checking breaking API changes between two CRD OpenAPI v3 schemas. The schemas can come from either two revisions of a CRD, or from the versions declared in a single CRD. +- `buildtagger`: A tool for generating build tags (constraints) for the source modules of the official provider families. +- `lint-provider-family`: A linter for the official provider families. Checks whether all CRDs generated for a provider family are packaged in the corresponding service-scoped provider and checks the provider metadata. +- `perf`: A tool for running performance experiments in the official provider repositories and for collecting & reporting the CPU & Memory utilizations and time to readiness (TTR) for MRs in these experiments. +- `ttr`: A tool that reports the time-to-readiness (TTR) measurements for a subset of the managed resources in a Kubernetes cluster. +- `updoc`: Upbound enhanced document processor. + +This repository is also the home of the Upbound official providers reusable workflows: +- `.github/workflows/provider-ci.yml`: A reusable CI workflow for building, linting & validating the official providers. +- `.github/workflows/pr-comment-trigger.yml`: A reusable workflow for triggering `uptest` runs using a specified set of example manifests via pull request comments. +- `.github/workflows/provider-publish-service-artifacts.yml`: A reusable workflow for building the official provider families and pushing their packages to the Upbound registry. +- `.github/workflows/native-provider-bump.yml`: A reusable workflow for bumping the underlying Terraform provider versions of upjet-based official providers. +- `.github/workflows/provider-backport.yml`: A reusable workflow for opening backport PRs in the specified release branches by inspecting the labels on merged PRs. +- `.github/workflows/issue-triage.yml`: A reusable workflow for identifying and labeling issues opened by the community. +- `.github/workflows/pr-triage.yml`: A reusable workflow for identifying and labeling pull requests opened by the community. +- `.github/workflows/provider-tag.yml`: A reusable workflow for tagging commits in the release process. +- `.github/workflows/provider-updoc.yml`: A reusable workflow for running `updoc` and publishing the provider documentation to the [Upbound marketplace](https://marketplace.upbound.io/providers). +- `.github/workflows/scan.yml`: A reusable workflow for running [Trivy](https://trivy.dev) scans in the official provider repositories. +- `.github/workflows/provider-commands.yml`: A reusable workflow for opening backport PRs in the specified release branches via PR comments. ## Report a Bug diff --git a/cmd/uptest/main.go b/cmd/uptest/main.go deleted file mode 100644 index 5dac2d6..0000000 --- a/cmd/uptest/main.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2022 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// main package for the uptest tooling. -package main - -import ( - "os" - "path/filepath" - "strings" - - "gopkg.in/alecthomas/kingpin.v2" - - "github.com/upbound/uptest/internal" - "github.com/upbound/uptest/internal/config" -) - -var ( - app = kingpin.New("uptest", "Automated Test Tool for Upbound Official Providers").DefaultEnvars() - // e2e command (single command is preserved for backward compatibility) - // and we may have further commands in the future. - e2e = app.Command("e2e", "Run e2e tests for manifests by applying them to a control plane and waiting until a given condition is met.") -) - -var ( - manifestList = e2e.Arg("manifest-list", "List of manifests. Value of this option will be used to trigger/configure the tests."+ - "The possible usage:\n"+ - "'provider-aws/examples/s3/bucket.yaml,provider-gcp/examples/storage/bucket.yaml': "+ - "The comma separated resources are used as test inputs.\n"+ - "If this option is not set, 'MANIFEST_LIST' env var is used as default.").Envar("MANIFEST_LIST").String() - dataSourcePath = e2e.Flag("data-source", "File path of data source that will be used for injection some values.").Envar("UPTEST_DATASOURCE_PATH").Default("").String() - setupScript = e2e.Flag("setup-script", "Script that will be executed before running tests.").Default("").String() - teardownScript = e2e.Flag("teardown-script", "Script that will be executed after running tests.").Default("").String() - - defaultTimeout = e2e.Flag("default-timeout", "Default timeout in seconds for the test.\n"+ - "Timeout could be overridden per resource using \"uptest.upbound.io/timeout\" annotation.").Default("1200").Int() - defaultConditions = e2e.Flag("default-conditions", "Comma separated list of default conditions to wait for a successful test.\n"+ - "Conditions could be overridden per resource using \"uptest.upbound.io/conditions\" annotation.").Default("Ready").String() - - skipDelete = e2e.Flag("skip-delete", "Skip the delete step of the test.").Default("false").Bool() - testDir = e2e.Flag("test-directory", "Directory where kuttl test case will be generated and executed.").Envar("UPTEST_TEST_DIR").Default(filepath.Join(os.TempDir(), "uptest-e2e")).String() - onlyCleanUptestResources = e2e.Flag("only-clean-uptest-resources", "While deletion step, only clean resources that were created by uptest").Default("false").Bool() -) - -func main() { - if kingpin.MustParse(app.Parse(os.Args[1:])) == e2e.FullCommand() { - e2eTests() - } -} - -func e2eTests() { - cd, err := os.Getwd() - if err != nil { - kingpin.FatalIfError(err, "cannot get current directory") - } - - list := strings.Split(*manifestList, ",") - examplePaths := make([]string, 0, len(list)) - for _, e := range list { - if e == "" { - continue - } - examplePaths = append(examplePaths, filepath.Join(cd, filepath.Clean(e))) - } - if len(examplePaths) == 0 { - kingpin.Fatalf("No manifest to test provided.") - } - - setupPath := "" - if *setupScript != "" { - setupPath, err = filepath.Abs(*setupScript) - if err != nil { - kingpin.FatalIfError(err, "cannot get absolute path of setup script") - } - } - - teardownPath := "" - if *teardownScript != "" { - teardownPath, err = filepath.Abs(*teardownScript) - if err != nil { - kingpin.FatalIfError(err, "cannot get absolute path of teardown script") - } - } - o := &config.AutomatedTest{ - ManifestPaths: examplePaths, - DataSourcePath: *dataSourcePath, - SetupScriptPath: setupPath, - TeardownScriptPath: teardownPath, - DefaultConditions: strings.Split(*defaultConditions, ","), - DefaultTimeout: *defaultTimeout, - Directory: *testDir, - SkipDelete: *skipDelete, - OnlyCleanUptestResources: *onlyCleanUptestResources, - } - - kingpin.FatalIfError(internal.RunTest(o), "cannot run e2e tests successfully") -} diff --git a/docs/integrating-uptest-for-e2e-testing.md b/docs/integrating-uptest-for-e2e-testing.md deleted file mode 100644 index 4e10f03..0000000 --- a/docs/integrating-uptest-for-e2e-testing.md +++ /dev/null @@ -1,157 +0,0 @@ -# Integrating Uptest for End to End Testing - -In this tutorial, we will integrate [uptest](https://github.com/upbound/uptest) to a Github repository to automate end to end -testing managed resources. While we will use a `Provider` repository as an example, the process will be almost identical -for a `Configuration` repository. - -Starting with a provider repository with no end to end testing capability, we will end up having: -- A make target to locally test examples end to end -- A GitHub action triggered for PRs whenever a comment as `/test-examples=` is left - -## Setting up the Make targets - -1. Go to the [demo repository](https://github.com/upbound/demo-uptest-integration) which contains a GitHub provider - generated using upjet and hit the `Use this template` button to initialize your demo repository under your own - GitHub organization. -1. Clone your demo repository on your local and `cd` into the root directory. -2. Initialize build submodule with - - ```bash - make submodules - ``` - -4. First we will add a simple setup script that will deploy a secret and a provider config for our provider. - - ```bash - mkdir -p cluster/test - touch cluster/test/setup.sh - chmod +x cluster/test/setup.sh - - cat < cluster/test/setup.sh - #!/usr/bin/env bash - set -aeuo pipefail - - echo "Running setup.sh" - echo "Creating cloud credential secret..." - \${KUBECTL} -n upbound-system create secret generic provider-secret --from-literal=credentials="{\"token\":\"\${UPTEST_CLOUD_CREDENTIALS}\"}" \ - --dry-run=client -o yaml | \${KUBECTL} apply -f - - - echo "Waiting until provider is healthy..." - \${KUBECTL} wait provider.pkg --all --for condition=Healthy --timeout 5m - - echo "Waiting for all pods to come online..." - \${KUBECTL} -n upbound-system wait --for=condition=Available deployment --all --timeout=5m - - echo "Creating a default provider config..." - cat < Note: If you're following this tutorial for a `Configuration` repository, you will need to add - > `local.xpkg.deploy.configuration.$(PROJECT_NAME)` instead of `local.xpkg.deploy.provider.$(PROJECT_NAME)` to the - > `e2e` target. - -6. Commit the changes we did so far. - - ```bash - git add Makefile cluster/test/setup.sh - git commit -m "Add uptest and e2e targets" - ``` - -## Testing Locally - -1. Generate a [Personal Access Token](https://github.com/settings/tokens/new) for your Github account with - `repo/public_repo` and `delete_repo` scopes. -2. Run the following: - - ```bash - export UPTEST_CLOUD_CREDENTIALS= - UPTEST_EXAMPLE_LIST=examples/repository/repository.yaml make e2e - ``` - -You should see a `PASS` at the end of logs indicating everything worked fine. - -## Adding the GitHub workflow - -Now we have things working locally, let's add a GitHub workflow to automate end to end testing with CI. - -1. Run the following to add the GitHub workflow definition which will be triggered for `issue_comment` events and will call -uptests reusable workflow: - - ```bash - cat < .github/workflows/e2e.yaml - name: End to End Testing - - on: - issue_comment: - types: [created] - - jobs: - e2e: - uses: upbound/uptest/.github/workflows/pr-comment-trigger.yml@main - secrets: - UPTEST_CLOUD_CREDENTIALS: \${{ secrets.UPTEST_CLOUD_CREDENTIALS }} - UPTEST_DATASOURCE: \${{ secrets.UPTEST_DATASOURCE }} - EOF - ``` - - > See [Injecting Dynamic Values (and Datasource)](../README.md#injecting-dynamic-values-and-datasource) for more - > details on `UPTEST_DATASOURCE` secret. - -1. Commit and push to the `main` branch of the repository. - - ``` - git add .github/workflows/e2e.yaml - git commit -s -m "Add e2e workflow" - git push origin main - ``` - -3. Lastly, we need to add a Repository Secret with our GitHub token. - 1. Go to your repository Settings in GitHub UI. - 2. On the left side, select `Secrets` -> `Actions` under `Security` section. - 3. Hit `New repository secret` - 4. Enter `UPTEST_CLOUD_CREDENTIALS` as `Name` and your GitHub Token as `Secret` and hit `Add secret` - -## Testing via CI - -We are now ready to test our changes end to end via GitHub Actions. We will try that out by opening a test PR. - -1. Go to the `examples/repository/repository.yaml` file and make some wording changes in `description` field, e.g. - Add `CI -` as a prefix. -2. Create a PR with that change. -3. Add the following comment on the PR: - - ``` - /test-examples="examples/repository/repository.yaml" - ``` - -4. Check the Actions and follow how end to end testing goes. diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 5423f65..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package config contains configuration options for configuring uptest runtime. -package config - -import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - -const ( - // AnnotationKeyTimeout defines a test time for the annotated resource. - AnnotationKeyTimeout = "uptest.upbound.io/timeout" - // AnnotationKeyConditions defines the list of status conditions to - // assert on the tested resource. - AnnotationKeyConditions = "uptest.upbound.io/conditions" - // AnnotationKeyPreAssertHook defines the path to a pre-assert - // hook script to be executed before the resource is tested. - AnnotationKeyPreAssertHook = "uptest.upbound.io/pre-assert-hook" - // AnnotationKeyPostAssertHook defines the path to a post-assert - // hook script to be executed after the resource is tested. - AnnotationKeyPostAssertHook = "uptest.upbound.io/post-assert-hook" - // AnnotationKeyPreDeleteHook defines the path to a pre-delete - // hook script to be executed before the tested resource is deleted. - AnnotationKeyPreDeleteHook = "uptest.upbound.io/pre-delete-hook" - // AnnotationKeyPostDeleteHook defines the path to a post-delete - // hook script to be executed after the tested resource is deleted. - AnnotationKeyPostDeleteHook = "uptest.upbound.io/post-delete-hook" - // AnnotationKeyUpdateParameter defines the update parameter that will be - // used during the update step - AnnotationKeyUpdateParameter = "uptest.upbound.io/update-parameter" - // AnnotationKeyExampleID is id of example that populated from example - // manifest. This information will be used for determining the root resource - AnnotationKeyExampleID = "meta.upbound.io/example-id" - // AnnotationKeyDisableImport determines whether the Import - // step of the resource to be tested will be executed or not. - AnnotationKeyDisableImport = "uptest.upbound.io/disable-import" -) - -// AutomatedTest represents an automated test of resource example -// manifests to be run with uptest. -type AutomatedTest struct { - Directory string - - ManifestPaths []string - DataSourcePath string - - SetupScriptPath string - TeardownScriptPath string - - DefaultTimeout int - DefaultConditions []string - - SkipDelete bool - - OnlyCleanUptestResources bool -} - -// Manifest represents a resource loaded from an example resource manifest file. -type Manifest struct { - FilePath string - Object *unstructured.Unstructured - YAML string -} - -// TestCase represents a test-case to be run by kuttl. -type TestCase struct { - Timeout int - SetupScriptPath string - TeardownScriptPath string - SkipUpdate bool - SkipImport bool - - OnlyCleanUptestResources bool -} - -// Resource represents a Kubernetes object to be tested and asserted -// by uptest. -type Resource struct { - Name string - Namespace string - KindGroup string - YAML string - - Timeout int - Conditions []string - PreAssertScriptPath string - PostAssertScriptPath string - PreDeleteScriptPath string - PostDeleteScriptPath string - - UpdateParameter string - UpdateAssertKey string - UpdateAssertValue string - - SkipImport bool - - Root bool -} diff --git a/internal/prepare.go b/internal/prepare.go deleted file mode 100644 index c928fb5..0000000 --- a/internal/prepare.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package internal implements the uptest runtime for running -// automated tests using resource example manifests -// using kuttl. -package internal - -import ( - "bytes" - "fmt" - "io" - "math/rand" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - kyaml "k8s.io/apimachinery/pkg/util/yaml" - "sigs.k8s.io/yaml" - - "github.com/upbound/uptest/internal/config" -) - -var ( - charset = []rune("abcdefghijklmnopqrstuvwxyz0123456789") - - dataSourceRegex = regexp.MustCompile(`\${data\.(.*?)}`) - randomStrRegex = regexp.MustCompile(`\${Rand\.(.*?)}`) - - caseDirectory = "case" -) - -type preparerOption func(*preparer) - -func withDataSource(path string) preparerOption { - return func(p *preparer) { - p.dataSourcePath = path - } -} - -func withTestDirectory(path string) preparerOption { - return func(p *preparer) { - p.testDirectory = path - } -} - -func newPreparer(testFilePaths []string, opts ...preparerOption) *preparer { - p := &preparer{ - testFilePaths: testFilePaths, - testDirectory: os.TempDir(), - } - for _, f := range opts { - f(p) - } - return p -} - -type preparer struct { - testFilePaths []string - dataSourcePath string - testDirectory string -} - -//nolint:gocyclo // This function is not complex, gocyclo threshold was reached due to the error handling. -func (p *preparer) prepareManifests() ([]config.Manifest, error) { - caseDirectory := filepath.Join(p.testDirectory, caseDirectory) - if err := os.RemoveAll(caseDirectory); err != nil { - return nil, errors.Wrapf(err, "cannot clean directory %s", caseDirectory) - } - if err := os.MkdirAll(caseDirectory, os.ModePerm); err != nil { - return nil, errors.Wrapf(err, "cannot create directory %s", caseDirectory) - } - - injectedFiles, err := p.injectVariables() - if err != nil { - return nil, errors.Wrap(err, "cannot inject variables") - } - - manifests := make([]config.Manifest, 0, len(injectedFiles)) - for path, data := range injectedFiles { - decoder := kyaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(data), 1024) - for { - u := &unstructured.Unstructured{} - if err := decoder.Decode(&u); err != nil { - if errors.Is(err, io.EOF) { - break - } - return nil, errors.Wrap(err, "cannot decode manifest") - } - if u != nil { - if v, ok := u.GetAnnotations()["upjet.upbound.io/manual-intervention"]; ok { - fmt.Printf("Skipping %s with name %s since it requires the following manual intervention: %s\n", u.GroupVersionKind().String(), u.GetName(), v) - continue - } - y, err := yaml.Marshal(u) - if err != nil { - return nil, errors.Wrapf(err, "cannot marshal manifest for \"%s/%s\"", u.GetObjectKind(), u.GetName()) - } - manifests = append(manifests, config.Manifest{ - FilePath: path, - Object: u, - YAML: string(y), - }) - } - } - } - return manifests, nil -} - -func (p *preparer) injectVariables() (map[string]string, error) { - dataSourceMap := make(map[string]string) - if p.dataSourcePath != "" { - dataSource, err := os.ReadFile(p.dataSourcePath) - if err != nil { - return nil, errors.Wrap(err, "cannot read data source file") - } - if err := yaml.Unmarshal(dataSource, &dataSourceMap); err != nil { - return nil, errors.Wrap(err, "cannot prepare data source map") - } - } - - inputs := make(map[string]string, len(p.testFilePaths)) - for _, f := range p.testFilePaths { - manifestData, err := os.ReadFile(filepath.Clean(f)) - if err != nil { - return nil, errors.Wrapf(err, "cannot read %s", f) - } - inputs[f] = p.injectValues(string(manifestData), dataSourceMap) - } - return inputs, nil -} - -func (p *preparer) injectValues(manifestData string, dataSourceMap map[string]string) string { - // Inject data source values such as tenantID, objectID, accountID - dataSourceKeys := dataSourceRegex.FindAllStringSubmatch(manifestData, -1) - for _, dataSourceKey := range dataSourceKeys { - if v, ok := dataSourceMap[dataSourceKey[1]]; ok { - manifestData = strings.ReplaceAll(manifestData, dataSourceKey[0], v) - } - } - // Inject random strings - randomKeys := randomStrRegex.FindAllStringSubmatch(manifestData, -1) - for _, randomKey := range randomKeys { - switch randomKey[1] { - case "RFC1123Subdomain": - r := generateRFC1123SubdomainCompatibleString() - manifestData = strings.Replace(manifestData, randomKey[0], r, 1) - default: - continue - } - } - return manifestData -} - -func generateRFC1123SubdomainCompatibleString() string { - s := make([]rune, 8) - for i := range s { - s[i] = charset[rand.Intn(len(charset))] //nolint:gosec // no need for crypto/rand here - } - return fmt.Sprintf("op-%s", string(s)) -} diff --git a/internal/runner.go b/internal/runner.go deleted file mode 100644 index 38fe5b5..0000000 --- a/internal/runner.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "fmt" - "os" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - - "github.com/upbound/uptest/internal/config" -) - -// RunTest runs the specified automated test -func RunTest(o *config.AutomatedTest) error { - defer func() { - if err := os.RemoveAll(o.Directory); err != nil { - fmt.Println(fmt.Sprint(err, "cannot clean the test directory")) - } - }() - - // Read examples and inject data source values to manifests - manifests, err := newPreparer(o.ManifestPaths, withDataSource(o.DataSourcePath), withTestDirectory(o.Directory)).prepareManifests() - if err != nil { - return errors.Wrap(err, "cannot prepare manifests") - } - - // Prepare assert environment and run tests - if err := newTester(manifests, o).executeTests(); err != nil { - return errors.Wrap(err, "cannot execute tests") - } - - return nil -} diff --git a/internal/templates/00-apply.yaml.tmpl b/internal/templates/00-apply.yaml.tmpl deleted file mode 100644 index 2734913..0000000 --- a/internal/templates/00-apply.yaml.tmpl +++ /dev/null @@ -1,11 +0,0 @@ -# This file belongs to the resource apply step. -{{ if .TestCase.SetupScriptPath -}} -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: {{ .TestCase.SetupScriptPath }} -{{ end }} -{{- range $resource := .Resources -}} ---- -{{ $resource.YAML }} -{{- end }} \ No newline at end of file diff --git a/internal/templates/00-assert.yaml.tmpl b/internal/templates/00-assert.yaml.tmpl deleted file mode 100644 index 95b184a..0000000 --- a/internal/templates/00-assert.yaml.tmpl +++ /dev/null @@ -1,26 +0,0 @@ -# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: {{ .TestCase.Timeout }} -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if $resource.PreAssertScriptPath }} -- command: {{ $resource.PreAssertScriptPath }} -{{- end }} -{{- range $condition := $resource.Conditions }} -{{- if $resource.Namespace }} -- command: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=condition={{ $condition }} --timeout 10s --namespace {{ $resource.Namespace }} -{{- else }} -- command: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=condition={{ $condition }} --timeout 10s -{{- end }} -{{- end }} -{{- if $resource.PostAssertScriptPath }} -- command: {{ $resource.PostAssertScriptPath }} -{{- end }} -{{- end }} diff --git a/internal/templates/01-assert.yaml.tmpl b/internal/templates/01-assert.yaml.tmpl deleted file mode 100644 index c8a83a3..0000000 --- a/internal/templates/01-assert.yaml.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: {{ .TestCase.Timeout }} -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if not $resource.Namespace }} -{{- if $resource.Root }} -- script: ${KUBECTL} get {{ $resource.KindGroup }}/{{ $resource.Name }} -o=jsonpath='{.status.atProvider{{ $resource.UpdateAssertKey }}}' | grep -q "^{{ $resource.UpdateAssertValue }}$" -{{- end }} -{{- end }} -{{- end }} diff --git a/internal/templates/01-update.yaml.tmpl b/internal/templates/01-update.yaml.tmpl deleted file mode 100644 index 621e97d..0000000 --- a/internal/templates/01-update.yaml.tmpl +++ /dev/null @@ -1,14 +0,0 @@ -# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if not $resource.Namespace }} -{{- if $resource.Root }} -- command: ${KUBECTL} patch {{ $resource.KindGroup }}/{{ $resource.Name }} --type=merge -p '{"spec":{"forProvider":{{ $resource.UpdateParameter }}}}' -{{- end }} -{{- end }} -{{- end }} diff --git a/internal/templates/02-assert.yaml.tmpl b/internal/templates/02-assert.yaml.tmpl deleted file mode 100644 index ac67afe..0000000 --- a/internal/templates/02-assert.yaml.tmpl +++ /dev/null @@ -1,19 +0,0 @@ -# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: {{ .TestCase.Timeout }} -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- range $condition := $resource.Conditions }} -{{- if not $resource.Namespace }} -- command: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=condition={{ $condition }} --timeout 10s -{{- end }} -{{- end }} -{{- if not (or $resource.Namespace $resource.SkipImport) }} -- script: new_id="$(${KUBECTL} get {{ $resource.KindGroup }}/{{ $resource.Name }} -o=jsonpath='{.status.atProvider.id}')" && old_id="$(${KUBECTL} get {{ $resource.KindGroup }}/{{ $resource.Name }} -o=jsonpath='{.metadata.annotations.uptest-old-id}')" && [ "$new_id" = "$old_id" ] -{{- end }} -{{- end }} diff --git a/internal/templates/02-import.yaml.tmpl b/internal/templates/02-import.yaml.tmpl deleted file mode 100644 index 8c4441f..0000000 --- a/internal/templates/02-import.yaml.tmpl +++ /dev/null @@ -1,17 +0,0 @@ -# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if not $resource.Namespace }} -- command: ${KUBECTL} --subresource=status patch {{ $resource.KindGroup }}/{{ $resource.Name }} --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate {{ $resource.KindGroup }}/{{ $resource.Name }} uptest-old-id=$(${KUBECTL} get {{ $resource.KindGroup }}/{{ $resource.Name }} -o=jsonpath='{.status.atProvider.id}') --overwrite -{{- end }} -{{- end }} -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 diff --git a/internal/templates/03-assert.yaml.tmpl b/internal/templates/03-assert.yaml.tmpl deleted file mode 100644 index f3b9315..0000000 --- a/internal/templates/03-assert.yaml.tmpl +++ /dev/null @@ -1,23 +0,0 @@ -# This assert file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: {{ .TestCase.Timeout }} -commands: -- script: echo "Dump MR manifests for the delete assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the delete assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if $resource.Namespace }} -- script: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=delete --timeout 10s --namespace {{ $resource.Namespace }} -{{- else }} -- command: ${KUBECTL} wait {{ $resource.KindGroup }}/{{ $resource.Name }} --for=delete --timeout 10s -{{- end }} -{{- end }} -{{- if not .TestCase.OnlyCleanUptestResources }} -- command: ${KUBECTL} wait managed --all --for=delete --timeout 10s -{{- end }} -{{- if .TestCase.TeardownScriptPath }} -- command: {{ .TestCase.TeardownScriptPath }} -{{- end }} diff --git a/internal/templates/03-delete.yaml.tmpl b/internal/templates/03-delete.yaml.tmpl deleted file mode 100644 index c291505..0000000 --- a/internal/templates/03-delete.yaml.tmpl +++ /dev/null @@ -1,20 +0,0 @@ -# This file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -{{- range $resource := .Resources }} -{{- if eq $resource.KindGroup "secret." -}} - {{continue}} -{{- end -}} -{{- if $resource.PreDeleteScriptPath }} -- command: {{ $resource.PreDeleteScriptPath }} -{{- end }} -{{- if $resource.Namespace }} -- command: ${KUBECTL} delete {{ $resource.KindGroup }}/{{ $resource.Name }} --wait=false --namespace {{ $resource.Namespace }} --ignore-not-found -{{- else }} -- command: ${KUBECTL} delete {{ $resource.KindGroup }}/{{ $resource.Name }} --wait=false --ignore-not-found -{{- end }} -{{- if $resource.PostDeleteScriptPath }} -- command: {{ $resource.PostDeleteScriptPath }} -{{- end }} -{{- end }} diff --git a/internal/templates/embed.go b/internal/templates/embed.go deleted file mode 100644 index ccc07a3..0000000 --- a/internal/templates/embed.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package templates - -import _ "embed" - -// inputFileTemplate is the template for the input file. -// -//go:embed 00-apply.yaml.tmpl -var inputFileTemplate string - -// assertFileTemplate is the template for the assert file. -// -//go:embed 00-assert.yaml.tmpl -var assertFileTemplate string - -// updateFileTemplate is the template for the update file. -// -//go:embed 01-update.yaml.tmpl -var updateFileTemplate string - -// assertUpdatedFileTemplate is the template for update assert file. -// -//go:embed 01-assert.yaml.tmpl -var assertUpdatedFileTemplate string - -// deleteFileTemplate is the template for the import file. -// -//go:embed 02-import.yaml.tmpl -var importFileTemplate string - -// assertDeletedFileTemplate is the template for import assert file. -// -//go:embed 02-assert.yaml.tmpl -var assertImportedFileTemplate string - -// deleteFileTemplate is the template for the delete file. -// -//go:embed 03-delete.yaml.tmpl -var deleteFileTemplate string - -// assertDeletedFileTemplate is the template for delete assert file. -// -//go:embed 03-assert.yaml.tmpl -var assertDeletedFileTemplate string diff --git a/internal/templates/renderer.go b/internal/templates/renderer.go deleted file mode 100644 index dc0ac6c..0000000 --- a/internal/templates/renderer.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package templates contains utilities for rendering kuttl test cases using -// the templates contained in the package. -package templates - -import ( - "strings" - "text/template" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - - "github.com/upbound/uptest/internal/config" -) - -var fileTemplates = map[string]string{ - "00-apply.yaml": inputFileTemplate, - "00-assert.yaml": assertFileTemplate, - "01-update.yaml": updateFileTemplate, - "01-assert.yaml": assertUpdatedFileTemplate, - "02-import.yaml": importFileTemplate, - "02-assert.yaml": assertImportedFileTemplate, - "03-delete.yaml": deleteFileTemplate, - "03-assert.yaml": assertDeletedFileTemplate, -} - -// Render renders the specified list of resources as a test case -// with the specified configuration. -func Render(tc *config.TestCase, resources []config.Resource, skipDelete bool) (map[string]string, error) { - data := struct { - Resources []config.Resource - TestCase config.TestCase - }{ - Resources: resources, - TestCase: *tc, - } - - res := make(map[string]string, len(fileTemplates)) - for name, tmpl := range fileTemplates { - // Skip templates with names starting with "01-" if skipUpdate is true - if tc.SkipUpdate && strings.HasPrefix(name, "01-") { - continue - } - // Skip templates with names starting with "02-" if skipImport is true - if tc.SkipImport && strings.HasPrefix(name, "02-") { - continue - } - // Skip templates with names starting with "03-" if skipDelete is true - if skipDelete && strings.HasPrefix(name, "03-") { - continue - } - - t, err := template.New(name).Parse(tmpl) - if err != nil { - return nil, errors.Wrapf(err, "cannot parse template %q", name) - } - - var b strings.Builder - if err := t.Execute(&b, data); err != nil { - return nil, errors.Wrapf(err, "cannot execute template %q", name) - } - res[name] = b.String() - } - - return res, nil -} diff --git a/internal/templates/renderer_test.go b/internal/templates/renderer_test.go deleted file mode 100644 index 755e332..0000000 --- a/internal/templates/renderer_test.go +++ /dev/null @@ -1,542 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package templates - -import ( - "testing" - - "github.com/crossplane/crossplane-runtime/pkg/test" - "github.com/google/go-cmp/cmp" - - "github.com/upbound/uptest/internal/config" -) - -const ( - bucketManifest = `apiVersion: s3.aws.crossplane.io/v1beta1 -kind: Bucket -metadata: - name: test-bucket -spec: - deletionPolicy: Delete -` - - claimManifest = `apiVersion: gcp.platformref.upbound.io/v1alpha1 -kind: Cluster -metadata: - name: test-cluster-claim - namespace: upbound-system -spec: - parameters: - nodes: - count: 1 - size: small -` - - secretManifest = `apiVersion: v1 -kind: Secret -metadata: - name: test-secret - namespace: upbound-system -type: Opaque -data: - key: dmFsdWU= -` -) - -func TestRender(t *testing.T) { - type args struct { - tc *config.TestCase - resources []config.Resource - } - type want struct { - out map[string]string - err error - } - tests := map[string]struct { - args args - want want - }{ - "SuccessSingleResource": { - args: args{ - tc: &config.TestCase{ - Timeout: 10, - }, - resources: []config.Resource{ - { - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - YAML: bucketManifest, - Conditions: []string{"Test"}, - }, - }, - }, - want: want{ - out: map[string]string{ - "00-apply.yaml": "# This file belongs to the resource apply step.\n---\n" + bucketManifest, - "00-assert.yaml": `# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -`, - "01-update.yaml": `# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -`, - "01-assert.yaml": `# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -`, - "02-assert.yaml": `# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: new_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}')" && old_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.metadata.annotations.uptest-old-id}')" && [ "$new_id" = "$old_id" ] -`, - "02-import.yaml": `# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -- command: ${KUBECTL} --subresource=status patch s3.aws.upbound.io/example-bucket --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate s3.aws.upbound.io/example-bucket uptest-old-id=$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}') --overwrite -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 -`, - - "03-assert.yaml": `# This assert file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the delete assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the delete assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=delete --timeout 10s -- command: ${KUBECTL} wait managed --all --for=delete --timeout 10s -`, - "03-delete.yaml": `# This file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} delete s3.aws.upbound.io/example-bucket --wait=false --ignore-not-found -`, - }, - }, - }, - "SuccessMultipleResource": { - args: args{ - tc: &config.TestCase{ - Timeout: 10, - SetupScriptPath: "/tmp/setup.sh", - TeardownScriptPath: "/tmp/teardown.sh", - }, - resources: []config.Resource{ - { - YAML: bucketManifest, - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - PreAssertScriptPath: "/tmp/bucket/pre-assert.sh", - PostDeleteScriptPath: "/tmp/bucket/post-delete.sh", - Conditions: []string{"Test"}, - }, - { - YAML: claimManifest, - Name: "test-cluster-claim", - KindGroup: "cluster.gcp.platformref.upbound.io", - Namespace: "upbound-system", - PostAssertScriptPath: "/tmp/claim/post-assert.sh", - PreDeleteScriptPath: "/tmp/claim/pre-delete.sh", - Conditions: []string{"Ready", "Synced"}, - }, - { - YAML: secretManifest, - Name: "test-secret", - KindGroup: "secret.", - Namespace: "upbound-system", - }, - }, - }, - want: want{ - out: map[string]string{ - "00-apply.yaml": `# This file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: /tmp/setup.sh -` + "---\n" + bucketManifest + "---\n" + claimManifest + "---\n" + secretManifest, - "00-assert.yaml": `# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: /tmp/bucket/pre-assert.sh -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Ready --timeout 10s --namespace upbound-system -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Synced --timeout 10s --namespace upbound-system -- command: /tmp/claim/post-assert.sh -`, - "01-update.yaml": `# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -`, - "01-assert.yaml": `# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -`, - "02-assert.yaml": `# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: new_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}')" && old_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.metadata.annotations.uptest-old-id}')" && [ "$new_id" = "$old_id" ] -`, - "02-import.yaml": `# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -- command: ${KUBECTL} --subresource=status patch s3.aws.upbound.io/example-bucket --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate s3.aws.upbound.io/example-bucket uptest-old-id=$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}') --overwrite -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 -`, - "03-assert.yaml": `# This assert file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the delete assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the delete assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=delete --timeout 10s -- script: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=delete --timeout 10s --namespace upbound-system -- command: ${KUBECTL} wait managed --all --for=delete --timeout 10s -- command: /tmp/teardown.sh -`, - "03-delete.yaml": `# This file belongs to the resource delete step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} delete s3.aws.upbound.io/example-bucket --wait=false --ignore-not-found -- command: /tmp/bucket/post-delete.sh -- command: /tmp/claim/pre-delete.sh -- command: ${KUBECTL} delete cluster.gcp.platformref.upbound.io/test-cluster-claim --wait=false --namespace upbound-system --ignore-not-found -`, - }, - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got, err := Render(tc.args.tc, tc.args.resources, false) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("Render(...): -want error, +got error:\n%s", diff) - } - if diff := cmp.Diff(tc.want.out, got); diff != "" { - t.Errorf("Render(...): -want, +got:\n%s", diff) - } - }) - } -} - -func TestRenderWithSkipDelete(t *testing.T) { - type args struct { - tc *config.TestCase - resources []config.Resource - } - type want struct { - out map[string]string - err error - } - tests := map[string]struct { - args args - want want - }{ - "SuccessSingleResource": { - args: args{ - tc: &config.TestCase{ - Timeout: 10, - }, - resources: []config.Resource{ - { - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - YAML: bucketManifest, - Conditions: []string{"Test"}, - }, - }, - }, - want: want{ - out: map[string]string{ - "00-apply.yaml": "# This file belongs to the resource apply step.\n---\n" + bucketManifest, - "00-assert.yaml": `# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -`, - "01-update.yaml": `# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -`, - "01-assert.yaml": `# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -`, - "02-assert.yaml": `# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: new_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}')" && old_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.metadata.annotations.uptest-old-id}')" && [ "$new_id" = "$old_id" ] -`, - "02-import.yaml": `# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -- command: ${KUBECTL} --subresource=status patch s3.aws.upbound.io/example-bucket --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate s3.aws.upbound.io/example-bucket uptest-old-id=$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}') --overwrite -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 -`, - }, - }, - }, - "SkipImport": { - args: args{ - tc: &config.TestCase{ - Timeout: 10, - SetupScriptPath: "/tmp/setup.sh", - TeardownScriptPath: "/tmp/teardown.sh", - }, - resources: []config.Resource{ - { - YAML: bucketManifest, - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - PreAssertScriptPath: "/tmp/bucket/pre-assert.sh", - PostDeleteScriptPath: "/tmp/bucket/post-delete.sh", - SkipImport: true, - Conditions: []string{"Test"}, - }, - { - YAML: claimManifest, - Name: "test-cluster-claim", - KindGroup: "cluster.gcp.platformref.upbound.io", - Namespace: "upbound-system", - PostAssertScriptPath: "/tmp/claim/post-assert.sh", - PreDeleteScriptPath: "/tmp/claim/pre-delete.sh", - Conditions: []string{"Ready", "Synced"}, - }, - { - YAML: secretManifest, - Name: "test-secret", - KindGroup: "secret.", - Namespace: "upbound-system", - }, - }, - }, - want: want{ - out: map[string]string{ - "00-apply.yaml": `# This file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: /tmp/setup.sh -` + "---\n" + bucketManifest + "---\n" + claimManifest + "---\n" + secretManifest, - "00-assert.yaml": `# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: /tmp/bucket/pre-assert.sh -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Ready --timeout 10s --namespace upbound-system -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Synced --timeout 10s --namespace upbound-system -- command: /tmp/claim/post-assert.sh -`, - "01-update.yaml": `# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -`, - "01-assert.yaml": `# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -`, - "02-assert.yaml": `# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -`, - "02-import.yaml": `# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -- command: ${KUBECTL} --subresource=status patch s3.aws.upbound.io/example-bucket --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate s3.aws.upbound.io/example-bucket uptest-old-id=$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}') --overwrite -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 -`, - }, - }, - }, - "SuccessMultipleResource": { - args: args{ - tc: &config.TestCase{ - Timeout: 10, - SetupScriptPath: "/tmp/setup.sh", - TeardownScriptPath: "/tmp/teardown.sh", - }, - resources: []config.Resource{ - { - YAML: bucketManifest, - Name: "example-bucket", - KindGroup: "s3.aws.upbound.io", - PreAssertScriptPath: "/tmp/bucket/pre-assert.sh", - PostDeleteScriptPath: "/tmp/bucket/post-delete.sh", - Conditions: []string{"Test"}, - }, - { - YAML: claimManifest, - Name: "test-cluster-claim", - KindGroup: "cluster.gcp.platformref.upbound.io", - Namespace: "upbound-system", - PostAssertScriptPath: "/tmp/claim/post-assert.sh", - PreDeleteScriptPath: "/tmp/claim/pre-delete.sh", - Conditions: []string{"Ready", "Synced"}, - }, - { - YAML: secretManifest, - Name: "test-secret", - KindGroup: "secret.", - Namespace: "upbound-system", - }, - }, - }, - want: want{ - out: map[string]string{ - "00-apply.yaml": `# This file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: /tmp/setup.sh -` + "---\n" + bucketManifest + "---\n" + claimManifest + "---\n" + secretManifest, - "00-assert.yaml": `# This assert file belongs to the resource apply step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- command: ${KUBECTL} annotate managed --all upjet.upbound.io/test=true --overwrite -- script: echo "Dump MR manifests for the apply assertion step:"; ${KUBECTL} get managed -o yaml -- script: echo "Dump Claim manifests for the apply assertion step:" || ${KUBECTL} get claim --all-namespaces -o yaml -- command: /tmp/bucket/pre-assert.sh -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Ready --timeout 10s --namespace upbound-system -- command: ${KUBECTL} wait cluster.gcp.platformref.upbound.io/test-cluster-claim --for=condition=Synced --timeout 10s --namespace upbound-system -- command: /tmp/claim/post-assert.sh -`, - "01-update.yaml": `# This file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -`, - "01-assert.yaml": `# This assert file belongs to the resource update step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the update assertion step:"; ${KUBECTL} get managed -o yaml -`, - "02-assert.yaml": `# This assert file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -timeout: 10 -commands: -- script: echo "Dump MR manifests for the import assertion step:"; ${KUBECTL} get managed -o yaml -- command: ${KUBECTL} wait s3.aws.upbound.io/example-bucket --for=condition=Test --timeout 10s -- script: new_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}')" && old_id="$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.metadata.annotations.uptest-old-id}')" && [ "$new_id" = "$old_id" ] -`, - "02-import.yaml": `# This file belongs to the resource import step. -apiVersion: kuttl.dev/v1beta1 -kind: TestStep -commands: -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=0 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=0 -- command: ${KUBECTL} --subresource=status patch s3.aws.upbound.io/example-bucket --type=merge -p '{"status":{"conditions":[]}}' -- script: ${KUBECTL} annotate s3.aws.upbound.io/example-bucket uptest-old-id=$(${KUBECTL} get s3.aws.upbound.io/example-bucket -o=jsonpath='{.status.atProvider.id}') --overwrite -- command: ${KUBECTL} scale deployment crossplane -n ${CROSSPLANE_NAMESPACE} --replicas=1 -- script: ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} get deploy --no-headers -o custom-columns=":metadata.name" | grep "provider-" | xargs ${KUBECTL} -n ${CROSSPLANE_NAMESPACE} scale deploy --replicas=1 -`, - }, - }, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - got, err := Render(tc.args.tc, tc.args.resources, true) - if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" { - t.Errorf("Render(...): -want error, +got error:\n%s", diff) - } - if diff := cmp.Diff(tc.want.out, got); diff != "" { - t.Errorf("Render(...): -want, +got:\n%s", diff) - } - }) - } -} diff --git a/internal/tester.go b/internal/tester.go deleted file mode 100644 index 21c0464..0000000 --- a/internal/tester.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2023 Upbound Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package internal - -import ( - "bufio" - "encoding/json" - "fmt" - "io/fs" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - - "github.com/crossplane/crossplane-runtime/pkg/errors" - - "github.com/upbound/uptest/internal/config" - "github.com/upbound/uptest/internal/templates" -) - -func newTester(ms []config.Manifest, opts *config.AutomatedTest) *tester { - return &tester{ - options: opts, - manifests: ms, - } -} - -type tester struct { - options *config.AutomatedTest - manifests []config.Manifest -} - -func (t *tester) executeTests() error { - if err := t.writeKuttlFiles(); err != nil { - return errors.Wrap(err, "cannot write kuttl test files") - } - fmt.Println("Running kuttl tests at " + t.options.Directory) - cmd := exec.Command("bash", "-c", fmt.Sprintf(`"${KUTTL}" test --start-kind=false --skip-cluster-delete %s --timeout %d 2>&1`, t.options.Directory, t.options.DefaultTimeout)) // #nosec G204 - stdout, _ := cmd.StdoutPipe() - if err := cmd.Start(); err != nil { - return errors.Wrap(err, "cannot start kuttl") - } - sc := bufio.NewScanner(stdout) - sc.Split(bufio.ScanLines) - for sc.Scan() { - fmt.Println(sc.Text()) - } - return errors.Wrap(cmd.Wait(), "kuttl failed") -} - -func (t *tester) prepareConfig() (*config.TestCase, []config.Resource, error) { //nolint:gocyclo // TODO: can we break this? - tc := &config.TestCase{ - Timeout: t.options.DefaultTimeout, - SetupScriptPath: t.options.SetupScriptPath, - TeardownScriptPath: t.options.TeardownScriptPath, - OnlyCleanUptestResources: t.options.OnlyCleanUptestResources, - } - examples := make([]config.Resource, 0, len(t.manifests)) - - for _, m := range t.manifests { - obj := m.Object - groupVersionKind := obj.GroupVersionKind() - kg := strings.ToLower(groupVersionKind.Kind + "." + groupVersionKind.Group) - - example := config.Resource{ - Name: obj.GetName(), - Namespace: obj.GetNamespace(), - KindGroup: kg, - YAML: m.YAML, - Timeout: t.options.DefaultTimeout, - Conditions: t.options.DefaultConditions, - } - - var err error - annotations := obj.GetAnnotations() - if v, ok := annotations[config.AnnotationKeyTimeout]; ok { - example.Timeout, err = strconv.Atoi(v) - if err != nil { - return nil, nil, errors.Wrap(err, "timeout value is not valid") - } - if example.Timeout > tc.Timeout { - tc.Timeout = example.Timeout - } - } - - if v, ok := annotations[config.AnnotationKeyConditions]; ok { - example.Conditions = strings.Split(v, ",") - } - - if v, ok := annotations[config.AnnotationKeyPreAssertHook]; ok { - example.PreAssertScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(m.FilePath), filepath.Clean(v))) - if err != nil { - return nil, nil, errors.Wrap(err, "cannot find absolute path for pre assert hook") - } - } - - if v, ok := annotations[config.AnnotationKeyPostAssertHook]; ok { - example.PostAssertScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(m.FilePath), filepath.Clean(v))) - if err != nil { - return nil, nil, errors.Wrap(err, "cannot find absolute path for post assert hook") - } - } - - if v, ok := annotations[config.AnnotationKeyPreDeleteHook]; ok { - example.PreDeleteScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(m.FilePath), filepath.Clean(v))) - if err != nil { - return nil, nil, errors.Wrap(err, "cannot find absolute path for pre delete hook") - } - } - - if v, ok := annotations[config.AnnotationKeyPostDeleteHook]; ok { - example.PostDeleteScriptPath, err = filepath.Abs(filepath.Join(filepath.Dir(m.FilePath), filepath.Clean(v))) - if err != nil { - return nil, nil, errors.Wrap(err, "cannot find absolute path for post delete hook") - } - } - - updateParameter, ok := annotations[config.AnnotationKeyUpdateParameter] - if !ok { - updateParameter = os.Getenv("UPTEST_UPDATE_PARAMETER") - } - if updateParameter != "" { - example.UpdateParameter = updateParameter - var data map[string]interface{} - if err := json.Unmarshal([]byte(updateParameter), &data); err != nil { - return nil, nil, errors.Wrapf(err, "cannot unmarshal JSON object: %s", updateParameter) - } - example.UpdateAssertKey, example.UpdateAssertValue = convertToJSONPath(data, "") - } - disableImport, ok := annotations[config.AnnotationKeyDisableImport] - if ok && disableImport == "true" { - example.SkipImport = true - } - - if exampleID, ok := annotations[config.AnnotationKeyExampleID]; ok { - if exampleID == strings.ToLower(fmt.Sprintf("%s/%s/%s", strings.Split(groupVersionKind.Group, ".")[0], groupVersionKind.Version, groupVersionKind.Kind)) { - if disableImport == "true" { - tc.SkipImport = true - } - if updateParameter == "" { - tc.SkipUpdate = true - } - example.Root = true - } - } - - examples = append(examples, example) - } - - return tc, examples, nil -} - -func (t *tester) writeKuttlFiles() error { - tc, examples, err := t.prepareConfig() - if err != nil { - return errors.Wrap(err, "cannot build examples config") - } - - files, err := templates.Render(tc, examples, t.options.SkipDelete) - if err != nil { - return errors.Wrap(err, "cannot render kuttl templates") - } - - for k, v := range files { - if err := os.WriteFile(filepath.Join(filepath.Join(t.options.Directory, caseDirectory), k), []byte(v), fs.ModePerm); err != nil { - return errors.Wrapf(err, "cannot write file %q", k) - } - } - - return nil -} - -func convertToJSONPath(data map[string]interface{}, currentPath string) (string, string) { - for key, value := range data { - newPath := currentPath + "." + key - switch v := value.(type) { - case map[string]interface{}: - return convertToJSONPath(v, newPath) - default: - return newPath, fmt.Sprintf("%v", v) - } - } - return currentPath, "" -}