diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ef3f5e..c33ef01 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,10 @@ name: Run Tests on: push +env: + GO_VERSION: ">=1.15" + TERRAFORM_VERSION: "0.12.29" + jobs: tf_acc_tests: runs-on: ubuntu-latest @@ -15,10 +19,10 @@ jobs: with: version: "v0.6.1" - - name: 'Setup Go' - uses: actions/setup-go@v1 + - name: Set up Go + uses: actions/setup-go@v2 with: - go-version: "1.13.5" + go-version: "${{ env.GO_VERSION }}" - name: 'Run TF acceptance tests' run: | @@ -34,62 +38,56 @@ jobs: - name: 'Checkout' uses: actions/checkout@v1 - - name: 'Setup Go' - uses: actions/setup-go@v1 + - name: Set up Go + uses: actions/setup-go@v2 with: - go-version: "1.13.5" - - - name: 'Compile provider' - run: | - go build -o terraform.d/plugins/linux_amd64/terraform-provider-kustomization - ls -al - pwd + go-version: "${{ env.GO_VERSION }}" - - name: 'Upload provider binary' - uses: actions/upload-artifact@v1 + - name: Run goreleaser build + if: startsWith(github.ref, 'refs/tags/v') + uses: kbst/goreleaser-action@v2 with: - name: terraform.d - path: terraform.d - - download_terraform: - runs-on: ubuntu-latest - - steps: + version: latest + args: build --config .goreleaser-build.yml --rm-dist - - name: 'Download Terraform' - run: | - wget https://releases.hashicorp.com/terraform/0.12.20/terraform_0.12.20_linux_amd64.zip - unzip terraform_0.12.20_linux_amd64.zip - ./terraform version + - name: Run goreleaser build --snapshot + if: startsWith(github.ref, 'refs/tags/v') == false + uses: kbst/goreleaser-action@v2 + with: + version: latest + args: build --config .goreleaser-build.yml --rm-dist --snapshot - - name: 'Upload terraform binary' - uses: actions/upload-artifact@v1 + - name: 'Upload dist' + uses: actions/upload-artifact@v2 with: - name: terraform - path: terraform + name: dist + path: dist/ int_test_kubeconfig_path: runs-on: ubuntu-latest - needs: [compile_provider, download_terraform] + needs: [compile_provider] steps: - name: 'Checkout' uses: actions/checkout@v1 - - name: 'Download terraform binary' - uses: actions/download-artifact@v1 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1.2.0 with: - name: terraform + terraform_wrapper: false + terraform_version: "${{ env.TERRAFORM_VERSION }}" - name: 'Download provider binary' - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: - name: terraform.d + name: dist + path: dist - - name: 'Make binaries executable' + - name: 'Prepare provider binary' run: | - chmod +x ./terraform/terraform - chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization + mkdir -p ./terraform.d/plugins/linux_amd64/ + cp ./dist/terraform-provider-kustomization_linux_amd64/terraform-provider-kustomization_* ./terraform.d/plugins/linux_amd64/ + chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization_* - name: 'Setup Kind' uses: engineerd/setup-kind@v0.1.0 @@ -101,14 +99,14 @@ jobs: export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" - name: 'Terraform Init' - run: ./terraform/terraform init + run: terraform init - name: 'Terraform Apply' - run: ./terraform/terraform apply --auto-approve + run: terraform apply --auto-approve int_test_kubeconfig_raw: runs-on: ubuntu-latest - needs: [compile_provider, download_terraform] + needs: [compile_provider] steps: - name: 'Checkout' @@ -119,20 +117,23 @@ jobs: with: version: "v0.6.1" - - name: 'Download terraform binary' - uses: actions/download-artifact@v1 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1.2.0 with: - name: terraform + terraform_wrapper: false + terraform_version: "${{ env.TERRAFORM_VERSION }}" - name: 'Download provider binary' - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: - name: terraform.d + name: dist + path: dist - - name: 'Make binaries executable' + - name: 'Prepare provider binary' run: | - chmod +x ./terraform/terraform - chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization + mkdir -p ./terraform.d/plugins/linux_amd64/ + cp ./dist/terraform-provider-kustomization_linux_amd64/terraform-provider-kustomization_* ./terraform.d/plugins/linux_amd64/ + chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization_* - name: 'Create provider.tf with kubeconfig_raw' run: | @@ -144,14 +145,14 @@ jobs: echo "}" >> provider.tf - name: 'Terraform Init' - run: ./terraform/terraform init + run: terraform init - name: 'Terraform Apply' - run: ./terraform/terraform apply --auto-approve + run: terraform apply --auto-approve int_test_state_import: runs-on: ubuntu-latest - needs: [compile_provider, download_terraform] + needs: [compile_provider] steps: - name: 'Checkout' @@ -162,20 +163,23 @@ jobs: with: version: "v0.6.1" - - name: 'Download terraform binary' - uses: actions/download-artifact@v1 + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1.2.0 with: - name: terraform + terraform_wrapper: false + terraform_version: "${{ env.TERRAFORM_VERSION }}" - name: 'Download provider binary' - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v2 with: - name: terraform.d + name: dist + path: dist - - name: 'Make binaries executable' + - name: 'Prepare provider binary' run: | - chmod +x ./terraform/terraform - chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization + mkdir -p ./terraform.d/plugins/linux_amd64/ + cp ./dist/terraform-provider-kustomization_linux_amd64/terraform-provider-kustomization_* ./terraform.d/plugins/linux_amd64/ + chmod +x ./terraform.d/plugins/linux_amd64/terraform-provider-kustomization_* - name: 'Set KUBECONFIG env var' run: | @@ -192,14 +196,43 @@ jobs: kustomize build test_kustomizations/basic/initial | kubectl apply -f - - name: 'Terraform Init' - run: ./terraform/terraform init + run: terraform init - name: 'Terraform Import' run: | - ./terraform/terraform import 'kustomization_resource.test["~G_v1_Namespace|~X|test-basic"]' '~G_v1_Namespace|~X|test-basic' - ./terraform/terraform import 'kustomization_resource.test["apps_v1_Deployment|test-basic|test"]' 'apps_v1_Deployment|test-basic|test' - ./terraform/terraform import 'kustomization_resource.test["~G_v1_Service|test-basic|test"]' '~G_v1_Service|test-basic|test' - ./terraform/terraform import 'kustomization_resource.test["networking.k8s.io_v1beta1_Ingress|test-basic|test"]' 'networking.k8s.io_v1beta1_Ingress|test-basic|test' + terraform import 'kustomization_resource.test["~G_v1_Namespace|~X|test-basic"]' '~G_v1_Namespace|~X|test-basic' + terraform import 'kustomization_resource.test["apps_v1_Deployment|test-basic|test"]' 'apps_v1_Deployment|test-basic|test' + terraform import 'kustomization_resource.test["~G_v1_Service|test-basic|test"]' '~G_v1_Service|test-basic|test' + terraform import 'kustomization_resource.test["networking.k8s.io_v1beta1_Ingress|test-basic|test"]' 'networking.k8s.io_v1beta1_Ingress|test-basic|test' - name: 'Terraform Apply' - run: ./terraform/terraform apply --auto-approve + run: terraform apply --auto-approve + + goreleaser: + runs-on: ubuntu-latest + needs: [tf_acc_tests, int_test_kubeconfig_path, int_test_kubeconfig_raw, int_test_state_import] + if: startsWith(github.ref, 'refs/tags/v') + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: "${{ env.GO_VERSION }}" + + - name: Import GPG key + id: import_gpg + uses: kbst/ghaction-import-gpg@v3 + with: + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.GPG_PASSPHRASE }} + + - name: Run goreleaser release + uses: kbst/goreleaser-action@v2 + with: + version: latest + args: release + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 551ec31..a83ea8c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ terraform.d *.tfstate* crash.log +dist/ \ No newline at end of file diff --git a/.goreleaser-build.yml b/.goreleaser-build.yml new file mode 100644 index 0000000..5555dbb --- /dev/null +++ b/.goreleaser-build.yml @@ -0,0 +1,17 @@ +project_name: terraform-provider-kustomization +before: + hooks: + - go mod tidy +builds: +- env: + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + goos: + - linux + goarch: + - amd64 + binary: '{{ .ProjectName }}_v{{ .Version }}' diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..b460436 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,49 @@ +project_name: terraform-provider-kustomization +before: + hooks: + - go mod tidy +builds: +- env: + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + - goos: darwin + goarch: '386' + binary: '{{ .ProjectName }}_v{{ .Version }}' +archives: +- format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' +checksum: + name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' + algorithm: sha256 +signs: + - artifacts: checksum + args: + # if you are using this is a GitHub action or some other automated pipeline, you + # need to pass the batch flag to indicate its not interactive. + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" +release: + draft: true + prerelease: auto +changelog: + skip: false diff --git a/Makefile b/Makefile index 0121837..e73429d 100644 --- a/Makefile +++ b/Makefile @@ -3,15 +3,3 @@ build: test: TF_ACC=1 go test -v ./kustomize - -RELEASE := $(shell git describe --tags) - -release-binaries: - GOOS=linux GOARCH=amd64 go build -o terraform.d/plugins/linux_amd64/terraform-provider-kustomization_$(RELEASE) - tar -caf terraform-provider-kustomization-linux-amd64_$(RELEASE).tgz terraform.d/plugins/linux_amd64/terraform-provider-kustomization_$(RELEASE) - - GOOS=darwin GOARCH=amd64 go build -o terraform.d/plugins/darwin_amd64/terraform-provider-kustomization_$(RELEASE) - tar -caf terraform-provider-kustomization-darwin-amd64_$(RELEASE).tgz terraform.d/plugins/darwin_amd64/terraform-provider-kustomization_$(RELEASE) - - GOOS=windows GOARCH=amd64 go build -o terraform.d/plugins/windows_amd64/terraform-provider-kustomization_$(RELEASE) - tar -caf terraform-provider-kustomization-windows-amd64_$(RELEASE).tgz terraform.d/plugins/windows_amd64/terraform-provider-kustomization_$(RELEASE) \ No newline at end of file diff --git a/README.md b/README.md index f717836..98785b4 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ This provider aims to solve 3 common issues of applying a kustomization using kubectl by integrating Kustomize and Terraform. - 1. Lack of feedback what changes will be applied. - 1. Resources from a previous apply not in the current apply are not purged. - 1. Immutable changes like e.g. changing a deployment's selector cause the apply to fail mid way. +1. Lack of feedback what changes will be applied. +1. Resources from a previous apply not in the current apply are not purged. +1. Immutable changes like e.g. changing a deployment's selector cause the apply to fail mid way. To solve this the provider uses the Terraform state to show changes to each resource individually during plan as well as track resources in need of purging. @@ -16,8 +16,8 @@ As such it can be useful both to replace kustomize/kubectl integrated into a Ter ## Requirements -- [Terraform](https://www.terraform.io/downloads.html) 0.12.x -- [Go](https://golang.org/doc/install) 1.13 (to build the provider plugin) +- [Terraform](https://www.terraform.io/downloads.html) 0.12.x +- [Go](https://golang.org/doc/install) 1.13 (to build the provider plugin) ## Usage @@ -66,14 +66,14 @@ terraform import 'kustomization_resource.test["apps_v1_Deployment|test-basic|tes To work on the provider, you need go installed on your machine (version 1.13.x tested). The provider uses go mod to manage its dependencies, so GOPATH is not required. -To compile the provider, run `go build` as shown below. This will build the provider and put the provider binary in the `terraform.d/plugins/linux_amd64/` directory. The provider has not been tested yet on other platforms. +To compile the provider, run `make build` as shown below. This will build the provider and put the provider binary in the `terraform.d/plugins/linux_amd64/` directory. ```sh -$ go build -o terraform.d/plugins/linux_amd64/terraform-provider-kustomization +$ make build ``` -In order to test the provider, you can simply run the acceptance tests using `go test`. You can set the `KUBECONFIG` environment variable to point the tests to a specific cluster or set the context of your current config accordingly. The tests create namespaces on the current context. [Kind](https://github.com/kubernetes-sigs/kind) or [Minikube](https://github.com/kubernetes/minikube) clusters work well for testing. +In order to test the provider, you can simply run the acceptance tests using `make test`. You can set the `KUBECONFIG` environment variable to point the tests to a specific cluster or set the context of your current config accordingly. The tests create namespaces on the current context. [Kind](https://github.com/kubernetes-sigs/kind) or [Minikube](https://github.com/kubernetes/minikube) clusters work well for testing. ```sh -$ TF_ACC=1 go test -v ./kustomize +$ make test ``` diff --git a/docs/data-sources/kustomization.md b/docs/data-sources/kustomization.md new file mode 100644 index 0000000..80d6e6d --- /dev/null +++ b/docs/data-sources/kustomization.md @@ -0,0 +1,28 @@ +# `kustomization` Data Source + +Data source to `kustomize build` a kustomization and return a set of `ids` and hash map of `manifests` by `id`. + +## Example Usage + +```hcl +data "kustomization" "example" { + # path to kustomization directory + path = "test_kustomizations/basic/initial" +} + +resource "kustomization_resource" "example" { + for_each = data.kustomization.example.ids + + manifest = data.kustomization.example.manifests[each.value] +} + +``` + +## Argument Reference + +- `path` - (Required) Path to a kustomization directory. + +## Attribute Reference + +- `ids` - Set of Kubernetes resource IDs. +- `manifests` - JSON encoded Kubernetes resource manifests. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..75a25d5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,43 @@ +# Kustomize Provider + +This provider aims to solve 3 common issues of applying a kustomization using kubectl by integrating Kustomize and Terraform. + +1. Lack of feedback what changes will be applied. +1. Resources from a previous apply not in the current apply are not purged. +1. Immutable changes like e.g. changing a deployment's selector cause the apply to fail mid way. + +To solve this the provider uses the Terraform state to show changes to each resource individually during plan as well as track resources in need of purging. + +It also uses [server side dry runs](https://kubernetes.io/docs/reference/using-api/api-concepts/#dry-run) to validate changes to the desired state and translate this into a Terraform plan that will show if a resource will be updated in-place or requires a delete and recreate to apply the changes. + +As such it can be useful both to replace kustomize/kubectl integrated into a Terraform configuration as a provisioner as well as standalone `kubectl diff/apply` steps in CI/CD. The provider was primarily developed for Kubestack, the [Terraform GitOps framework](https://www.kubestack.com/), but is supported and tested to be used standalone. + +## Example Usage + +```hcl +data "kustomization" "example" { + # path to kustomization directory + path = "test_kustomizations/basic/initial" +} + +resource "kustomization_resource" "example" { + for_each = data.kustomization.example.ids + + manifest = data.kustomization.example.manifests[each.value] +} + +``` + +## Argument Reference + +- `kubeconfig_path` - (Optional) Path to a kubeconfig file. Defaults to `KUBECONFIG`, `KUBE_CONFIG` or `~/.kube/config`. +- `kubeconfig_raw` - (Optional) Raw kubeconfig file. If kubeconfig_raw is set, kubeconfig_path is ignored. +- `context` - (Optional) Context to use in kubeconfig with multiple contexts, if not specified the default context is to be used. + +## Imports + +To import existing Kubernetes resources into the Terraform state for above usage example, use a command like below and replace `apps_v1_Deployment|test-basic|test` accordingly. Please note the single quotes required for most shells. + +``` +terraform import 'kustomization_resource.test["apps_v1_Deployment|test-basic|test"]' 'apps_v1_Deployment|test-basic|test' +``` diff --git a/docs/resources/kustomization_resource.md b/docs/resources/kustomization_resource.md new file mode 100644 index 0000000..ca7c307 --- /dev/null +++ b/docs/resources/kustomization_resource.md @@ -0,0 +1,23 @@ +# `kustomization_resource` Resource + +Resource to provision JSON encoded Kubernetes resource manifests as produced by the `kustomization` data source on a Kubernetes cluster. Uses client-go dynamic client and uses server side dry runs to determine the Terraform plan for changing a resource. + +## Example Usage + +```hcl +data "kustomization" "example" { + # path to kustomization directory + path = "test_kustomizations/basic/initial" +} + +resource "kustomization_resource" "example" { + for_each = data.kustomization.example.ids + + manifest = data.kustomization.example.manifests[each.value] +} + +``` + +## Argument Reference + +- `manifest` - (Required) JSON encoded Kubernetes resource manifest. diff --git a/go.sum b/go.sum index 54f65b6..8aa918f 100644 --- a/go.sum +++ b/go.sum @@ -879,7 +879,6 @@ k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg= k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA= k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.19.0 h1:gjKnAda/HZp5k4xQYjL0K/Yb66IvNqjthCb03QlKpaQ= k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg= k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k= k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=