diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2b685284e2e5cc7207a33d189e7c9a6690fb763d..7946fbfa11124b987f5fa324e7d17a35e87ccb07 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,22 +6,25 @@ workflow: - if: $CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true' include: + - local: .gitlab/terraform-test.gitlab-ci.yml + - local: .gitlab/tofu-test.gitlab-ci.yml - project: "gitlab-org/quality/pipeline-common" file: - "/ci/danger-review.yml" variables: BASE_IMAGE: "alpine:3.18.4" - BUILD_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TERRAFORM_VERSION:$CI_COMMIT_SHA" + TERRAFORM_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TERRAFORM_VERSION:$CI_COMMIT_SHA" + TOFU_IMAGE_NAME: "$CI_REGISTRY_IMAGE/branches/$CI_COMMIT_REF_SLUG-$TOFU_VERSION:$CI_COMMIT_SHA" DOCKER_DIND_IMAGE: "docker:24.0.7-dind" NODE_IMAGE: "node:lts-slim" PLATFORMS: linux/amd64,linux/arm64 - RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/$TERRAFORM_VERSION" STABLE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/stable:latest" STABLE_VERSION: "1.5" + STABLE_TOFU_VERSION: "1.6" TF_STATE_NAME: ci-$CI_JOB_ID -.versions: +.terraform-versions: parallel: matrix: - TERRAFORM_BINARY_VERSION: "1.5.7" @@ -35,6 +38,13 @@ variables: - TERRAFORM_BINARY_VERSION: "1.1.9" TERRAFORM_VERSION: "1.1" +.tofu-versions: + parallel: + matrix: + # latest version from https://pkgs.alpinelinux.org/packages?name=opentofu + - TOFU_BINARY_VERSION: "1.6.0_beta1-r0" + TOFU_VERSION: "1.6" + stages: - lint - build @@ -82,13 +92,18 @@ commit lint: dockerfile check: stage: lint image: hadolint/hadolint:latest-alpine + parallel: + matrix: + - FILE: Dockerfile.terraform + - FILE: Dockerfile.tofu + before_script: - hadolint --version script: - - hadolint Dockerfile + - hadolint $FILE -build: - extends: .versions +build terraform: + extends: .terraform-versions stage: build services: - "$DOCKER_DIND_IMAGE" @@ -114,8 +129,36 @@ build: --platform "$PLATFORMS" --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg TERRAFORM_BINARY_VERSION=$TERRAFORM_BINARY_VERSION - --file Dockerfile - --tag "$BUILD_IMAGE_NAME" + --file Dockerfile.terraform + --tag "$TERRAFORM_IMAGE_NAME" + --provenance=false + --push + . + +build tofu: + extends: .tofu-versions + stage: build + services: + - "$DOCKER_DIND_IMAGE" + image: "$DOCKER_DIND_IMAGE" + before_script: + # See note on the `build terraform` job about this image + - docker run --rm --privileged tonistiigi/binfmt + # Registry auth + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" + script: + - docker buildx create --use + # NOTE: we disable provenance for now + # because it causes troubles with the registry and older clients. + # See + # - https://gitlab.com/gitlab-org/terraform-images/-/issues/104 + # - https://gitlab.com/gitlab-org/terraform-images/-/merge_requests/184#note_1328485943 + - docker buildx build + --platform "$PLATFORMS" + --build-arg BASE_IMAGE=$BASE_IMAGE + --build-arg TOFU_BINARY_VERSION=$TOFU_BINARY_VERSION + --file Dockerfile.tofu + --tag "$TOFU_IMAGE_NAME" --provenance=false --push . @@ -132,361 +175,39 @@ upload: - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' when: manual -.test-base: - image: "$BUILD_IMAGE_NAME" - before_script: - - gitlab-terraform version - - jq --version - cache: - key: "$TERRAFORM_VERSION-$CI_COMMIT_REF_SLUG" - paths: - - tests/.terraform/ - -.test: - extends: - - .test-base - before_script: - - !reference [.test-base, before_script] - - cd tests - -.test-tf-root: - extends: - - .test-base - variables: - TF_ROOT: tests - -test-init: - extends: - - .test - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - gitlab-terraform init - -test-init-with-args: - extends: - - .test - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - gitlab-terraform init -get=true -no-color - -test-init-with-flags: - extends: - - .test - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - export TF_INIT_FLAGS="-get=true -no-color" - - gitlab-terraform init - -test-init-with-flags-and-args: - extends: - - .test - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - export TF_INIT_FLAGS="-get=true" - - gitlab-terraform init -no-color - -test-init-tf-root: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - gitlab-terraform init - -test-init-tf-root-with-cd: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - cd tests - - export DEBUG_OUTPUT=true - - gitlab-terraform init - -test-init-tf-root-with-args: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - gitlab-terraform init -get=true -no-color - -test-init-tf-root-with-flags: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - export TF_INIT_FLAGS="-get=true -no-color" - - gitlab-terraform init - -test-init-tf-root-with-flags-and-args: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - export DEBUG_OUTPUT=true - - export TF_INIT_FLAGS="-get=true" - - gitlab-terraform init -no-color - -test-init-without-reconfigure: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - gitlab-terraform init - - | - cat <<EOF > $TF_ROOT/backend_override.tf - terraform { - backend "local" {} - } - EOF - - export TF_INIT_NO_RECONFIGURE=true - - FAILED=false - - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true - - cat /tmp/output.txt - - test $FAILED = true - - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"' - -test-init-with-reconfigure: - extends: - - .test-tf-root - - .versions - stage: test-init - script: - - gitlab-terraform init - - | - cat <<EOF > $TF_ROOT/backend_override.tf - terraform { - backend "local" {} - } - EOF - - gitlab-terraform init - -test-init-with-prepared-registry-token: - extends: - - .test - stage: test-init - variables: - TERRAFORM_VERSION: $STABLE_VERSION - script: - - apk add --update $PKG - - | - cat <<'EOF' > test.sh - set -x - export TF_TOKEN_gitlab_com=mysecrettoken - . $(which gitlab-terraform) - terraform_authenticate_private_registry - test "$TF_TOKEN_gitlab_com" = "mysecrettoken" - EOF - - $SHELL test.sh - parallel: - matrix: - - SHELL: "bash" - PKG: "bash" - - SHELL: "zsh" - PKG: "zsh" - - SHELL: "ksh" - PKG: "loksh" - -test-init-without-prepared-registry-token: - extends: - - .test - stage: test-init - variables: - TERRAFORM_VERSION: $STABLE_VERSION - script: - - apk add --update $PKG - - | - cat <<'EOF' > test.sh - set -x - . $(which gitlab-terraform) - terraform_authenticate_private_registry - test -n "$TF_TOKEN_gitlab_com" - EOF - - $SHELL test.sh - parallel: - matrix: - - SHELL: "bash" - PKG: "bash" - - SHELL: "zsh" - PKG: "zsh" - - SHELL: "ksh" - PKG: "loksh" - -test-fmt: - extends: - - .test - - .versions - stage: test-fmt - script: - - gitlab-terraform fmt - -test-validate: - extends: - - .test - - .versions - stage: test-validate - script: - - gitlab-terraform validate - -test-plan: - extends: - - .test - - .versions - stage: test-plan - variables: - TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache - script: - - gitlab-terraform plan - - if [[ ! -f "$TERRAFORM_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi - - gitlab-terraform plan-json - - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi - artifacts: - paths: - - "tests/*-plan.cache" - -test-apply: - extends: - - .test - - .versions - stage: test-apply - variables: - TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache - script: - - gitlab-terraform apply - -test-destroy: - extends: - - .test - - .versions - stage: test-destroy - script: - - gitlab-terraform destroy - -test-source-script: - extends: - - .test - stage: test-misc - needs: [build] - variables: - TERRAFORM_VERSION: $STABLE_VERSION - before_script: - - !reference [.test-base, before_script] - - apk add --update $PKG - script: - - | - cat <<'EOF' > test.sh - set -x - test -z "$TF_GITLAB_SOURCED" - . $(which gitlab-terraform) - test $TF_GITLAB_SOURCED - EOF - - | - mkdir /usr/local/sbin - cat <<'EOF' > /usr/local/sbin/terraform - #/!usr/bin/env sh -e - echo "Called Terraform, but shouldn't have!!" - false - EOF - chmod +x /usr/local/sbin/terraform - - $SHELL test.sh - parallel: - matrix: - - SHELL: "bash" - PKG: "bash" - - SHELL: "zsh" - PKG: "zsh" - - SHELL: "ksh" - PKG: "loksh" - -test-without-implicit-init: - extends: - - .test - stage: test-misc - needs: [build] - cache: - variables: - TERRAFORM_VERSION: $STABLE_VERSION - STATE_NAME: $CI_JOB_NAME - script: - - export TF_IMPLICIT_INIT=false - - FAILED=false - - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true - - cat /tmp/output.txt - - test $FAILED = true - - 'cat /tmp/output.txt | grep "$ERROR"' - parallel: - matrix: - - CMD: apply - ERROR: 'Error: Failed to load "plan.cache" as a plan' - - CMD: destroy - ERROR: 'Error: Backend initialization required, please run "terraform init"' - - CMD: plan - ERROR: 'Error: Backend initialization required, please run "terraform init"' - - CMD: validate - ERROR: 'Run "terraform init" to install all modules' - -test-no-wrapper: - extends: - - .test - stage: test-misc - needs: [build] - cache: +release-terraform: + extends: .terraform-versions + stage: release variables: - TERRAFORM_VERSION: $STABLE_VERSION - STATE_NAME: $CI_JOB_NAME + RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/$TERRAFORM_VERSION" + image: + name: gcr.io/go-containerregistry/crane:debug + entrypoint: [""] script: - # NOTE: running `gitlab-terraform apply` wouldn't fail - # because of the implicit `terraform init`. - - FAILED=false - - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true - - cat /tmp/output.txt - - test $FAILED = true - - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"terraform init\""' - -integration-test-template: - stage: test-integration - variables: - TERRAFORM_VERSION: $STABLE_VERSION - TF_STATE_NAME: ci-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX - TF_ROOT: tests - trigger: - include: .gitlab/integration-test/Test-$TEMPLATE - strategy: depend + # https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_copy.md + - crane auth login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" + - crane copy "$TERRAFORM_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest" + - crane copy "$TERRAFORM_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/terraform:$TERRAFORM_BINARY_VERSION" + - crane copy "$TERRAFORM_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG" + - if [ "$TERRAFORM_VERSION" = "$STABLE_VERSION" ]; then crane copy "$TERRAFORM_IMAGE_NAME" "$STABLE_IMAGE_NAME"; fi rules: - - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"' - - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"' - parallel: - matrix: - - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml] + - if: $CI_COMMIT_TAG -release: - extends: .versions +release-tofu: + extends: .tofu-versions stage: release image: name: gcr.io/go-containerregistry/crane:debug entrypoint: [""] + variables: + RELEASE_IMAGE_NAME: "$CI_REGISTRY_IMAGE/releases/tofu/$TOFU_VERSION" script: # https://github.com/google/go-containerregistry/blob/main/cmd/crane/doc/crane_copy.md - crane auth login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY" - - crane copy "$BUILD_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest" - - crane copy "$BUILD_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/terraform:$TERRAFORM_BINARY_VERSION" - - crane copy "$BUILD_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG" - - if [ "$TERRAFORM_VERSION" = "$STABLE_VERSION" ]; then crane copy "$BUILD_IMAGE_NAME" "$STABLE_IMAGE_NAME"; fi + - crane copy "$TOFU_IMAGE_NAME" "$RELEASE_IMAGE_NAME:latest" + - crane copy "$TOFU_IMAGE_NAME" "$CI_REGISTRY_IMAGE/releases/tofu:$TOFU_BINARY_VERSION" + - crane copy "$TOFU_IMAGE_NAME" "$RELEASE_IMAGE_NAME:$CI_COMMIT_TAG" + # Removed the "stable" release for now, since tofu is still in beta itself rules: - if: $CI_COMMIT_TAG diff --git a/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml b/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml index 7a7b49c3a11b49f5e97c1cbfa30e8d99cd4ab3c3..be7adef5641674fde6375030febe8993d1f14a58 100644 --- a/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml +++ b/.gitlab/integration-test/Test-Terraform.gitlab-ci.yml @@ -5,8 +5,7 @@ workflow: include: - template: Terraform.gitlab-ci.yml -image: "$BUILD_IMAGE_NAME" - +image: "$IMAGE" # The `terraform apply` should always happen for the integration tests. # This prevents stalled manual pipelines, but more importantly tests diff --git a/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml b/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml index 7e7411047e8864d7aebc0c4a09ccd40e23ca5c55..8c2901c8dc3c47e6303fe3ac1893305be5755ade 100644 --- a/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml +++ b/.gitlab/integration-test/Test-Terraform.latest.gitlab-ci.yml @@ -6,7 +6,7 @@ include: - template: Terraform.latest.gitlab-ci.yml default: - image: "$BUILD_IMAGE_NAME" + image: "$IMAGE" .run-always: rules: diff --git a/.gitlab/terraform-test.gitlab-ci.yml b/.gitlab/terraform-test.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..3c82aeed066632d4bad347c7ac681ba43ee0ad6f --- /dev/null +++ b/.gitlab/terraform-test.gitlab-ci.yml @@ -0,0 +1,342 @@ +.terraform-test-base: + image: "$TERRAFORM_IMAGE_NAME" + before_script: + - gitlab-terraform version + - jq --version + cache: + key: "$TERRAFORM_VERSION-$CI_COMMIT_REF_SLUG" + paths: + - tests/.terraform/ + +.terraform-test: + extends: + - .terraform-test-base + before_script: + - !reference [.terraform-test-base, before_script] + - cd tests + +.test-tf-root: + extends: + - .terraform-test-base + variables: + TF_ROOT: tests + +terraform-test-init: + extends: + - .terraform-test + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +terraform-test-init-with-args: + extends: + - .terraform-test + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init -get=true -no-color + +terraform-test-init-with-flags: + extends: + - .terraform-test + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true -no-color" + - gitlab-terraform init + +terraform-test-init-with-flags-and-args: + extends: + - .terraform-test + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true" + - gitlab-terraform init -no-color + +terraform-test-init-tf-root: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +terraform-test-init-tf-root-with-cd: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - cd tests + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +terraform-test-init-tf-root-with-args: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init -get=true -no-color + +terraform-test-init-tf-root-with-flags: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true -no-color" + - gitlab-terraform init + +terraform-test-init-tf-root-with-flags-and-args: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true" + - gitlab-terraform init -no-color + +terraform-test-init-without-reconfigure: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - gitlab-terraform init + - | + cat <<EOF > $TF_ROOT/backend_override.tf + terraform { + backend "local" {} + } + EOF + - export TF_INIT_NO_RECONFIGURE=true + - FAILED=false + - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"' + +terraform-test-init-with-reconfigure: + extends: + - .test-tf-root + - .terraform-versions + stage: test-init + script: + - gitlab-terraform init + - | + cat <<EOF > $TF_ROOT/backend_override.tf + terraform { + backend "local" {} + } + EOF + - gitlab-terraform init + +terraform-test-init-with-prepared-registry-token: + extends: + - .terraform-test + stage: test-init + variables: + TERRAFORM_VERSION: $STABLE_VERSION + script: + - apk add --update $PKG + - | + cat <<'EOF' > test.sh + set -x + export TF_TOKEN_gitlab_com=mysecrettoken + . $(which gitlab-terraform) + terraform_authenticate_private_registry + test "$TF_TOKEN_gitlab_com" = "mysecrettoken" + EOF + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +terraform-test-init-without-prepared-registry-token: + extends: + - .terraform-test + stage: test-init + variables: + TERRAFORM_VERSION: $STABLE_VERSION + script: + - apk add --update $PKG + - | + cat <<'EOF' > test.sh + set -x + . $(which gitlab-terraform) + terraform_authenticate_private_registry + test -n "$TF_TOKEN_gitlab_com" + EOF + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +terraform-test-fmt: + extends: + - .terraform-test + - .terraform-versions + stage: test-fmt + script: + - gitlab-terraform fmt + +terraform-test-validate: + extends: + - .terraform-test + - .terraform-versions + stage: test-validate + script: + - gitlab-terraform validate + +terraform-test-plan: + extends: + - .terraform-test + - .terraform-versions + stage: test-plan + variables: + TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache + script: + - gitlab-terraform plan + - if [[ ! -f "$TERRAFORM_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi + - gitlab-terraform plan-json + - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi + artifacts: + paths: + - "tests/*-plan.cache" + +terraform-test-apply: + extends: + - .terraform-test + - .terraform-versions + stage: test-apply + variables: + TF_PLAN_CACHE: $TERRAFORM_VERSION-plan.cache + script: + - gitlab-terraform apply + +terraform-test-destroy: + extends: + - .terraform-test + - .terraform-versions + stage: test-destroy + script: + - gitlab-terraform destroy + +terraform-test-source-script: + extends: + - .terraform-test + stage: test-misc + needs: [build terraform] + variables: + TERRAFORM_VERSION: $STABLE_VERSION + before_script: + - !reference [.terraform-test-base, before_script] + - apk add --update $PKG + script: + - | + cat <<'EOF' > test.sh + set -x + test -z "$TF_GITLAB_SOURCED" + . $(which gitlab-terraform) + test $TF_GITLAB_SOURCED + EOF + - | + mkdir /usr/local/sbin + cat <<'EOF' > /usr/local/sbin/terraform + #/!usr/bin/env sh -e + echo "Called Terraform, but shouldn't have!!" + false + EOF + chmod +x /usr/local/sbin/terraform + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +terraform-test-without-implicit-init: + extends: + - .terraform-test + stage: test-misc + needs: [build terraform] + cache: + variables: + TERRAFORM_VERSION: $STABLE_VERSION + STATE_NAME: $CI_JOB_NAME + script: + - export TF_IMPLICIT_INIT=false + - FAILED=false + - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "$ERROR"' + parallel: + matrix: + - CMD: apply + ERROR: 'Error: Failed to load "plan.cache" as a plan' + - CMD: destroy + ERROR: 'Error: Backend initialization required, please run "terraform init"' + - CMD: plan + ERROR: 'Error: Backend initialization required, please run "terraform init"' + - CMD: validate + ERROR: 'Run "terraform init" to install all modules' + +terraform-test-no-wrapper: + extends: + - .terraform-test + stage: test-misc + needs: [build terraform] + cache: + variables: + TERRAFORM_VERSION: $STABLE_VERSION + STATE_NAME: $CI_JOB_NAME + script: + # NOTE: running `gitlab-terraform apply` wouldn't fail + # because of the implicit `terraform init`. + - FAILED=false + - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"terraform init\""' + +terraform-integration-test-template: + stage: test-integration + variables: + IMAGE: $TERRAFORM_IMAGE_NAME + TERRAFORM_VERSION: $STABLE_VERSION + TF_STATE_NAME: ci-terraform-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX + TF_ROOT: tests + trigger: + include: .gitlab/integration-test/Test-$TEMPLATE + strategy: depend + rules: + - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"' + - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"' + parallel: + matrix: + - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml] diff --git a/.gitlab/tofu-test.gitlab-ci.yml b/.gitlab/tofu-test.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..3e9b2ae17b6c2887d678fa0dd2617e13c2d76909 --- /dev/null +++ b/.gitlab/tofu-test.gitlab-ci.yml @@ -0,0 +1,343 @@ +.tofu-test-base: + image: "$TOFU_IMAGE_NAME" + before_script: + - gitlab-terraform version + - jq --version + cache: + key: "$TOFU_VERSION-$CI_COMMIT_REF_SLUG" + paths: + - tests/.terraform/ + +.tofu-test: + extends: + - .tofu-test-base + before_script: + - !reference [.tofu-test-base, before_script] + - cd tests + +.test-tofu-root: + extends: + - .tofu-test-base + variables: + TF_ROOT: tests + +tofu-test-init: + extends: + - .tofu-test + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +tofu-test-init-with-args: + extends: + - .tofu-test + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init -get=true -no-color + +tofu-test-init-with-flags: + extends: + - .tofu-test + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true -no-color" + - gitlab-terraform init + +tofu-test-init-with-flags-and-args: + extends: + - .tofu-test + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true" + - gitlab-terraform init -no-color + +tofu-test-init-tf-root: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +tofu-test-init-tf-root-with-cd: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - cd tests + - export DEBUG_OUTPUT=true + - gitlab-terraform init + +tofu-test-init-tf-root-with-args: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - gitlab-terraform init -get=true -no-color + +tofu-test-init-tf-root-with-flags: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true -no-color" + - gitlab-terraform init + +tofu-test-init-tf-root-with-flags-and-args: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - export DEBUG_OUTPUT=true + - export TF_INIT_FLAGS="-get=true" + - gitlab-terraform init -no-color + +tofu-test-init-without-reconfigure: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - gitlab-terraform init + - | + cat <<EOF > $TF_ROOT/backend_override.tf + terraform { + backend "local" {} + } + EOF + - export TF_INIT_NO_RECONFIGURE=true + - FAILED=false + - gitlab-terraform init -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "Error: Backend configuration changed"' + +tofu-test-init-with-reconfigure: + extends: + - .test-tofu-root + - .tofu-versions + stage: test-init + script: + - gitlab-terraform init + - | + cat <<EOF > $TF_ROOT/backend_override.tf + terraform { + backend "local" {} + } + EOF + - gitlab-terraform init + +tofu-test-init-with-prepared-registry-token: + extends: + - .tofu-test + stage: test-init + variables: + TOFU_VERSION: $STABLE_TOFU_VERSION + script: + - apk add --update $PKG + - | + cat <<'EOF' > test.sh + set -x + export TF_TOKEN_gitlab_com=mysecrettoken + . $(which gitlab-terraform) + terraform_authenticate_private_registry + test "$TF_TOKEN_gitlab_com" = "mysecrettoken" + EOF + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +tofu-test-init-without-prepared-registry-token: + extends: + - .tofu-test + stage: test-init + variables: + TOFU_VERSION: $STABLE_TOFU_VERSION + script: + - apk add --update $PKG + - | + cat <<'EOF' > test.sh + set -x + . $(which gitlab-terraform) + terraform_authenticate_private_registry + test -n "$TF_TOKEN_gitlab_com" + EOF + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +tofu-test-fmt: + extends: + - .tofu-test + - .tofu-versions + stage: test-fmt + script: + - gitlab-terraform fmt + +tofu-test-validate: + extends: + - .tofu-test + - .tofu-versions + stage: test-validate + script: + - gitlab-terraform validate + +tofu-test-plan: + extends: + - .tofu-test + - .tofu-versions + stage: test-plan + variables: + TF_PLAN_CACHE: $TOFU_VERSION-plan.cache + script: + - gitlab-terraform plan + - if [[ ! -f "$TOFU_VERSION-plan.cache" ]]; then echo "expected to find a plan.cache file"; exit 1; fi + - gitlab-terraform plan-json + - if [[ ! -f "plan.json" ]]; then echo "expected to find a plan.json file"; exit 1; fi + artifacts: + paths: + - "tests/*-plan.cache" + +tofu-test-apply: + extends: + - .tofu-test + - .tofu-versions + stage: test-apply + variables: + TF_PLAN_CACHE: $TOFU_VERSION-plan.cache + script: + - gitlab-terraform apply + +tofu-test-destroy: + extends: + - .tofu-test + - .tofu-versions + stage: test-destroy + script: + - gitlab-terraform destroy + +tofu-test-source-script: + extends: + - .tofu-test + stage: test-misc + needs: [build tofu] + variables: + TOFU_VERSION: $STABLE_TOFU_VERSION + before_script: + - !reference [.tofu-test-base, before_script] + - apk add --update $PKG + script: + - | + cat <<'EOF' > test.sh + set -x + test -z "$TF_GITLAB_SOURCED" + . $(which gitlab-terraform) + test $TF_GITLAB_SOURCED + EOF + - | + mkdir /usr/local/sbin + cat <<'EOF' > /usr/local/sbin/terraform + #/!usr/bin/env sh -e + echo "Called Terraform, but shouldn't have!!" + false + EOF + chmod +x /usr/local/sbin/terraform + - $SHELL test.sh + parallel: + matrix: + - SHELL: "bash" + PKG: "bash" + - SHELL: "zsh" + PKG: "zsh" + - SHELL: "ksh" + PKG: "loksh" + +tofu-test-without-implicit-init: + extends: + - .tofu-test + stage: test-misc + needs: [build tofu] + cache: + variables: + TOFU_VERSION: $STABLE_TOFU_VERSION + STATE_NAME: $CI_JOB_NAME + script: + - export TF_IMPLICIT_INIT=false + - FAILED=false + - gitlab-terraform $CMD -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "$ERROR"' + parallel: + matrix: + - CMD: apply + ERROR: 'Error: Failed to load "plan.cache" as a plan' + - CMD: destroy + ERROR: 'Error: Backend initialization required, please run "tofu init"' + - CMD: plan + ERROR: 'Error: Backend initialization required, please run "tofu init"' + - CMD: validate + ERROR: 'Run "tofu init" to install all modules' + +tofu-test-no-wrapper: + extends: + - .tofu-test + stage: test-misc + needs: [build tofu] + cache: + variables: + TOFU_VERSION: $STABLE_TOFU_VERSION + STATE_NAME: $CI_JOB_NAME + script: + # NOTE: running `gitlab-terraform apply` wouldn't fail + # because of the implicit `terraform init`. + - FAILED=false + - gitlab-terraform -- apply -no-color >/tmp/output.txt 2>&1 || FAILED=true + - cat /tmp/output.txt + - test $FAILED = true + - 'cat /tmp/output.txt | grep "Error: Backend initialization required, please run \"tofu init\""' + +tofu-integration-test-template: + stage: test-integration + variables: + IMAGE: $TOFU_IMAGE_NAME + # Hard-coded for now since there's no tofu stable version yet + TOFU_VERSION: "1.6" + TF_STATE_NAME: ci-tofu-integration-test-template-$CI_PIPELINE_IID-$CI_NODE_INDEX + TF_ROOT: tests + trigger: + include: .gitlab/integration-test/Test-$TEMPLATE + strategy: depend + rules: + - if: '$CI_PROJECT_PATH == "gitlab-org/terraform-images"' + - if: '$CI_MERGE_REQUEST_EVENT_TYPE == "merge_train"' + parallel: + matrix: + - TEMPLATE: [Terraform.gitlab-ci.yml, Terraform.latest.gitlab-ci.yml] diff --git a/Dockerfile b/Dockerfile.terraform similarity index 100% rename from Dockerfile rename to Dockerfile.terraform diff --git a/Dockerfile.tofu b/Dockerfile.tofu new file mode 100644 index 0000000000000000000000000000000000000000..ae7943cdba348fe0293d030b43f7f880c2a98175 --- /dev/null +++ b/Dockerfile.tofu @@ -0,0 +1,28 @@ +ARG BASE_IMAGE + +FROM $BASE_IMAGE + +ARG TOFU_BINARY_VERSION + +RUN apk add --no-cache \ + curl \ + gcompat \ + git \ + idn2-utils \ + jq \ + openssh + + +# Install tofu from the alpine registry and symlink to terraform (for the utility script) +RUN apk add --no-cache opentofu=$TOFU_BINARY_VERSION --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/ && \ + ln -s "$(which tofu)" /usr/local/bin/terraform && \ + terraform --version && \ + tofu --version + +WORKDIR / + +COPY src/bin/gitlab-terraform.sh /usr/bin/gitlab-terraform +RUN chmod +x /usr/bin/gitlab-terraform + +# Override ENTRYPOINT +ENTRYPOINT [] diff --git a/src/bin/gitlab-terraform.sh b/src/bin/gitlab-terraform.sh index 79056b437623b200e5b28caf2314542767c15bbb..380e561f1131f2d05196da29728489d63fcb397d 100755 --- a/src/bin/gitlab-terraform.sh +++ b/src/bin/gitlab-terraform.sh @@ -5,11 +5,20 @@ if [ "${DEBUG_OUTPUT}" = "true" ]; then fi # Helpers +# Check if the terraform version is greater than the input argument terraform_is_at_least() { [ "${1}" = "$(terraform -version | awk -v min="${1}" '/^Terraform v/{ sub(/^v/, "", $2); print min; print $2 }' | sort -V | head -n1)" ] return $? } +# Check if the script is using OpenTofu or Terraform, returns 0 +# if tofu is present, 1 if it's not +using_open_tofu() { + # If tofu is on the path, we're using tofu, not terraform. + [ "$(which tofu)" ] + return $? +} + # Evaluate if this script is being sourced or executed directly. # See https://stackoverflow.com/a/28776166 sourced=0 @@ -103,8 +112,8 @@ fi terraform_authenticate_private_registry() { - if terraform_is_at_least 1.2.0; then - # From Terraform 1.2.0 and later, we can use TF_TOKEN_your_domain_name to authenticate to registry. + if terraform_is_at_least 1.2.0 || using_open_tofu ; then + # From Terraform 1.2.0 and later (or all versions of OpenTofu), we can use TF_TOKEN_your_domain_name to authenticate to registry. # The credential environment variable has the following requirements: # - Domain names containing non-ASCII characters are converted to their punycode equivalent with an ACE prefix # - Periods are encoded as underscores