diff --git a/.dictionary b/.dictionary new file mode 100644 index 0000000..249cde1 --- /dev/null +++ b/.dictionary @@ -0,0 +1,2 @@ +OpenTofu +wp-opentofu diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index d54505e..0000000 --- a/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.git -build-docker.sh -CHANGELOG.md diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 94354d8..0000000 --- a/.drone.yml +++ /dev/null @@ -1,12 +0,0 @@ -workspace: - base: /go - path: src/github.com/drone-plugins/drone-terraform - -pipeline: - test: - image: golang:1.9 - environment: - - CGO_ENABLED=0 - commands: - - go test -cover -coverprofile=coverage.out - - go build -ldflags "-s -w -X main.revision=$(git rev-parse HEAD)" -a diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2c4ffab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# https://editorconfig.org/ + +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab +indent_size = 4 + +[*.{go,mod}] +indent_style = tab +tab_width = 4 + +[*.star] +indent_style = space +indent_size = 4 + +[LICENSE] +indent_size = unset diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..f8b000a --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,71 @@ +repository: + name: wp-opentofu + description: Woodpecker CI plugin to manage infrastructure with OpenTofu + homepage: https://woodpecker-plugins.geekdocs.de/plugins/wp-opentofu + topics: woodpecker-ci, woodpecker, woodpecker-plugin + + private: false + has_issues: true + has_wiki: false + has_downloads: true + + default_branch: main + + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + +labels: + - name: bug + color: d73a4a + description: Something isn't working + - name: documentation + color: 0075ca + description: Improvements or additions to documentation + - name: duplicate + color: cfd3d7 + description: This issue or pull request already exists + - name: enhancement + color: a2eeef + description: New feature or request + - name: good first issue + color: 7057ff + description: Good for newcomers + - name: help wanted + color: 008672 + description: Extra attention is needed + - name: invalid + color: e4e669 + description: This doesn't seem right + - name: question + color: d876e3 + description: Further information is requested + - name: wontfix + color: ffffff + description: This will not be worked on + +branches: + - name: main + protection: + required_pull_request_reviews: null + required_status_checks: + strict: false + contexts: + - ci/woodpecker/pr/test + - ci/woodpecker/pr/build-package + - ci/woodpecker/pr/build-container + - ci/woodpecker/pr/docs + enforce_admins: false + required_linear_history: true + restrictions: null + - name: docs + protection: + required_pull_request_reviews: null + required_status_checks: null + enforce_admins: true + required_linear_history: true + restrictions: + apps: [] + users: [] + teams: + - bot diff --git a/.gitignore b/.gitignore index 2749df5..2fbeefe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,7 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof +/dist +/release +/wp-opentofu* +docs/data/data-raw.yaml coverage.out -drone-terraform +CHANGELOG.md diff --git a/.gitsv/config.yml b/.gitsv/config.yml new file mode 100644 index 0000000..acaf506 --- /dev/null +++ b/.gitsv/config.yml @@ -0,0 +1,47 @@ +--- +version: "1.1" + +versioning: + update-major: [] + update-minor: [feat] + update-patch: [fix, perf, refactor, chore, test, ci, docs] + +tag: + pattern: "v%d.%d.%d" + +release-notes: + sections: + - name: Features + commit-types: [feat] + section-type: commits + - name: Bug Fixes + commit-types: [fix] + section-type: commits + - name: Performance Improvements + commit-types: [perf] + section-type: commits + - name: Code Refactoring + commit-types: [refactor] + section-type: commits + - name: Others + commit-types: [chore] + section-type: commits + - name: Testing + commit-types: [test] + section-type: commits + - name: CI Pipeline + commit-types: [ci] + section-type: commits + - name: Documentation + commit-types: [docs] + section-type: commits + - name: BREAKING CHANGES + section-type: breaking-changes + +commit-message: + footer: + issue: + key: issue + add-value-prefix: "#" + issue: + regex: "#?[0-9]+" diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..82226be --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,101 @@ +linters: + enable-all: false + disable-all: true + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - typecheck + - unused + - asasalint + - asciicheck + - bidichk + - bodyclose + - containedctx + - contextcheck + - decorder + - dogsled + - dupl + - dupword + - durationcheck + - errchkjson + - errname + - errorlint + - execinquery + - exhaustive + - exportloopref + - forcetypeassert + - ginkgolinter + - gocheckcompilerdirectives + - gochecknoglobals + - gochecknoinits + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - goerr113 + - gofmt + - gofumpt + - goheader + - goimports + - gomnd + - gomoddirectives + - gomodguard + - goprintffuncname + - gosec + - grouper + - importas + - interfacebloat + - ireturn + - lll + - loggercheck + - maintidx + - makezero + - misspell + - musttag + - nakedret + - nestif + - nilerr + - nilnil + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - nosprintfhostport + - prealloc + - predeclared + - promlinter + - reassign + - revive + # - rowserrcheck + # - sqlclosecheck + # - structcheck + - stylecheck + - tagliatelle + - tenv + - testableexamples + - thelper + - tparallel + - unconvert + - unparam + - usestdlibvars + # - wastedassign + - whitespace + - wsl + - zerologlint + fast: false + +run: + timeout: 3m + +linters-settings: + tagliatelle: + case: + rules: + json: kebab + gofumpt: + extra-rules: true diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 0000000..b59a114 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,6 @@ +--- +default: True +MD013: False +MD041: False +MD004: + style: dash diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..135c35d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +*.tpl.md +LICENSE diff --git a/.woodpecker/build-container.yml b/.woodpecker/build-container.yml new file mode 100644 index 0000000..2c4db92 --- /dev/null +++ b/.woodpecker/build-container.yml @@ -0,0 +1,69 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - event: [push] + branch: + - renovate/* + +steps: + - name: dryrun + image: quay.io/thegeeklab/wp-docker-buildx:3 + settings: + containerfile: Containerfile.multiarch + dry_run: true + platforms: + - linux/amd64 + - linux/arm64 + provenance: false + repo: ${CI_REPO} + when: + - event: [pull_request] + + - name: publish-dockerhub + image: quay.io/thegeeklab/wp-docker-buildx:3 + group: container + settings: + auto_tag: true + containerfile: Containerfile.multiarch + password: + from_secret: docker_password + platforms: + - linux/amd64 + - linux/arm64 + provenance: false + repo: ${CI_REPO} + username: + from_secret: docker_username + when: + - event: [tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + + - name: publish-quay + image: quay.io/thegeeklab/wp-docker-buildx:3 + group: container + settings: + auto_tag: true + containerfile: Containerfile.multiarch + password: + from_secret: quay_password + platforms: + - linux/amd64 + - linux/arm64 + provenance: false + registry: quay.io + repo: quay.io/${CI_REPO} + username: + from_secret: quay_username + when: + - event: [tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +depends_on: + - test diff --git a/.woodpecker/build-package.yml b/.woodpecker/build-package.yml new file mode 100644 index 0000000..3377d20 --- /dev/null +++ b/.woodpecker/build-package.yml @@ -0,0 +1,44 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - event: [push] + branch: + - renovate/* + +steps: + - name: build + image: docker.io/techknowlogick/xgo:go-1.21.x + commands: + - ln -s $(pwd) /source + - make release + + - name: executable + image: quay.io/thegeeklab/alpine-tools + commands: + - $(find dist/ -executable -type f -iname ${CI_REPO_NAME}-linux-amd64) --help + + - name: changelog + image: quay.io/thegeeklab/git-sv + commands: + - git sv current-version + - git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md + - cat CHANGELOG.md + + - name: publish-github + image: docker.io/plugins/github-release + settings: + api_key: + from_secret: github_token + files: + - dist/* + note: CHANGELOG.md + overwrite: true + title: ${CI_COMMIT_TAG} + when: + - event: [tag] + +depends_on: + - test diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml new file mode 100644 index 0000000..ee9bf63 --- /dev/null +++ b/.woodpecker/docs.yml @@ -0,0 +1,83 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - event: [push] + branch: + - renovate/* + +steps: + - name: markdownlint + image: quay.io/thegeeklab/markdownlint-cli + group: test + commands: + - markdownlint 'README.md' 'CONTRIBUTING.md' + + - name: spellcheck + image: quay.io/thegeeklab/alpine-tools + group: test + commands: + - spellchecker --files 'docs/**/*.md' 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls + environment: + FORCE_COLOR: "true" + + - name: link-validation + image: docker.io/lycheeverse/lychee + group: test + commands: + - lychee --no-progress --format detailed docs/content README.md + + - name: publish + image: quay.io/thegeeklab/wp-git-action + settings: + action: + - pages + author_email: bot@thegeeklab.de + author_name: thegeeklab-bot + branch: docs + message: "[skip ci] auto-update documentation" + netrc_password: + from_secret: github_token + pages_directory: docs/ + when: + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + status: [success, failure] + + - name: pushrm-dockerhub + image: docker.io/chko/docker-pushrm:1 + secrets: + - source: docker_password + target: DOCKER_PASS + - source: docker_username + target: DOCKER_USER + environment: + PUSHRM_FILE: README.md + PUSHRM_SHORT: Woodpecker CI plugin to manage infrastructure with OpenTofu + PUSHRM_TARGET: ${CI_REPO} + when: + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + status: [success] + + - name: pushrm-quay + image: docker.io/chko/docker-pushrm:1 + secrets: + - source: quay_token + target: APIKEY__QUAY_IO + environment: + PUSHRM_FILE: README.md + PUSHRM_TARGET: quay.io/${CI_REPO} + when: + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + status: [success] + +depends_on: + - build-package + - build-container diff --git a/.woodpecker/notify.yml b/.woodpecker/notify.yml new file mode 100644 index 0000000..9957125 --- /dev/null +++ b/.woodpecker/notify.yml @@ -0,0 +1,26 @@ +--- +when: + - event: [tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + +runs_on: [success, failure] + +steps: + - name: matrix + image: quay.io/thegeeklab/wp-matrix + settings: + homeserver: + from_secret: matrix_homeserver + password: + from_secret: matrix_password + roomid: + from_secret: matrix_roomid + username: + from_secret: matrix_username + when: + - status: [success, failure] + +depends_on: + - docs diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml new file mode 100644 index 0000000..f85c8fc --- /dev/null +++ b/.woodpecker/test.yml @@ -0,0 +1,20 @@ +--- +when: + - event: [pull_request, tag] + - event: [push, manual] + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - event: [push] + branch: + - renovate/* + +steps: + - name: lint + image: docker.io/library/golang:1.21 + commands: + - make lint + + - name: test + image: docker.io/library/golang:1.21 + commands: + - make test diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e4b3bad..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,120 +0,0 @@ -## 8.3-1.0.2 (2021-07-09) -* Update embedded TF to `1.0.2` - * A continuation of `v0.15` release line but now following semver - * Please refer to [Terraform v1.0 Compatibility Promises](https://www.terraform.io/docs/language/v1-compatibility-promises.html) - -## 8.3-0.15.1 (2021-07-09) -* fix: remove -lock and -lock-timeout for terraform init - https://github.com/jmccann/drone-terraform/pull/130 - -## 8.2-0.15.1 (2021-06-02) -* fix: `-force` -> `-auto-approve` - https://github.com/jmccann/drone-terraform/pull/128 - -## 8.1-0.15.1 (2021-04-27) -* added ability to [disable refresh](https://github.com/jmccann/drone-terraform/pull/120) - -## 8.0-0.15.1 (2021-04-27) -* Update embedded TF to `0.15.1` - * Please refer to [Terraform v15 Migration Guide](https://www.terraform.io/upgrade-guides/0-15.html) -* Update other deps - -## 7.0-0.14.11 (2021-04-27) -* Update embedded TF to `0.14.11` - * Please refer to [Terraform v14 Migration Guide](https://www.terraform.io/upgrade-guides/0-14.html) - -## 6.4-0.13.2 (2020-09-11) -* Update embedded TF to `0.13.2` - -## 6.4-0.13.1 (2020-09-11) -* minor version bump due to major version change of terraform -* Update embedded TF to `0.13.1` -* added curl - -## 6.3-0.12.20 (2020-02-12) -* add ability to load creds from `env_file` parameter (https://github.com/jmccann/drone-terraform/pull/107). Thanks @neemiasjnr! - -## 6.2-0.12.20 (2020-02-05) -* Update embedded TF to `0.12.20` -* Update alpine to 3.11 in docker image (https://github.com/jmccann/drone-terraform/pull/109). Thanks @sgerrand! - -## 6.2-0.12.16 (2019-11-26) -* Update embedded TF to `0.12.16` - -## 6.2-0.12.11 (2019-11-26) -* tfValidate vars and var-file argument removal #106 Thanks @gsingh1 - -## 6.1-0.12.11 (2019-10-18) -* Update embedded TF to `0.12.11` - -## 6.1-0.12.10 (2019-10-15) -* Update embedded TF to `0.12.10` - -## 6.1-0.12.8 (2019-09-06) -* Support for parallel execution (https://github.com/jmccann/drone-terraform/pull/94). Thanks @caioquirino! -* Updated golang to 1.13 - -## 6.0-0.12.8 (2019-09-06) -* Update embedded TF to `0.12.8` - -## 6.0-0.12.6 (2019-08-05) -* Update embedded TF to `0.12.6` - -## 6.0-0.12.4 (2019-07-12) -* Update embedded TF to `0.12.4` - -## 6.0-0.12.1 (2019-06-05) -* Version bump plugin to `6.0` since terraform `0.12` has breaking changes -* Update embedded TF to `0.12.1` - -## 5.3-0.11.14 (2019-08-05) -* Update embedded TF to `0.11.14` - -## 5.2-0.11.13 (2019-03-27) -* Update embedded TF to `0.11.13` - -## 5.2-0.11.11 (2019-02-22) -* Add `fmt` action - -## 5.1-0.11.11 (2019-01-18) -* Update embedded TF to `0.11.11` - -## 5.1-0.11.8 (2018-10-11) -* Update embedded TF to `0.11.8` - -## 5.1-0.11.7 (2018-07-31) -* Add `vars` and `var_files` to destroy operation - -## 5.0-0.11.7 (2018-04-25) -**BREAKING CHANGE** -* Removed `destroy` param -* Removed `plan` param -* Added `actions` param to provide a list of actions to perform. -See [DOCS.md](DOCS.md) for more info and examples. - -## 4.1-0.11.7 (2018-04-25) -* Add .netrc support -* Update embedded TF to `0.11.7` - -## 4.0-0.11.3 (2018-02-07) -* Update embedded TF to `0.11.3` - -## 4.0-0.10.8 (2018-02-07) -* Pass `-var-file` to validate command -* Update embedded TF to `0.10.8` - -## 4.0-0.10.7 (2017-10-20) -* Persist state locking config (https://github.com/jmccann/drone-terraform/pull/55) -* Update embedded TF to `0.10.7` - -## 4.0-0.10.3 (2017-09-06) -**Breaking Change** -* Update embedded TF to 0.10.3 -* In order to support validate in TF 0.10.3 add `vars` to validate command. -This is not compatible with older versions of TF. - -## 3.0-0.9.11 (2017-09-06) -**Breaking Change** -* Removed `secrets` key - -**Added Features** -* Added support for `destroy` -* Add ability to specify TF version to use via `tf_version` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..72264c4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# Contributing + +## Security + +If you think you have found a **security issue**, please do not mention it in this repository. +Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential. + +## Bug Reports and Feature Requests + +If you have found a **bug** or have a **feature request** please use the search first in case a similar issue already exists. +If not, please create an issue in this repository + +## Code + +If you would like to fix a bug or implement a feature, please fork the repository and create a Pull Request. + +Before you start any Pull Request, it is recommended that you create an issue to discuss first if you have any +doubts about requirement or implementation. That way you can be sure that the maintainer(s) agree on what to change and how, +and you can hopefully get a quick merge afterwards. + +Pull Requests can only be merged once all status checks are green. + +## Do not force push to your Pull Request branch + +Please do not force push to your Pull Requests branch after you have created your Pull Request, as doing so makes it harder for us to review your work. +Pull Requests will always be squashed by us when we merge your work. Commit as many times as you need in your Pull Request branch. + +## Re-requesting a review + +Please do not ping your reviewer(s) by mentioning them in a new comment. Instead, use the re-request review functionality. +Read more about this in the [GitHub docs, Re-requesting a review](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request#re-requesting-a-review). diff --git a/Containerfile.multiarch b/Containerfile.multiarch new file mode 100644 index 0000000..d83329b --- /dev/null +++ b/Containerfile.multiarch @@ -0,0 +1,37 @@ +FROM --platform=$BUILDPLATFORM docker.io/golang:1.21@sha256:7b575fe0d9c2e01553b04d9de8ffea6d35ca3ab3380d2a8db2acc8f0f1519a53 as build + +ARG TARGETOS +ARG TARGETARCH + +ADD . /src +WORKDIR /src + +RUN make build + +FROM docker.io/alpine:3.19 + +LABEL maintainer="Robert Kaussow " +LABEL org.opencontainers.image.authors="Robert Kaussow " +LABEL org.opencontainers.image.title="wp-opentofu" +LABEL org.opencontainers.image.url="https://github.com/thegeeklab/wp-opentofu" +LABEL org.opencontainers.image.source="https://github.com/thegeeklab/wp-opentofu" +LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/wp-opentofu" + +ARG TARGETOS +ARG TARGETARCH +ARG TOFU_VERSION + +# renovate: datasource=github-releases depName=opentofu/opentofu +ENV TOFU_VERSION="${TOFU_VERSION:-v1.6.0}" + +RUN apk --update add --virtual .build-deps curl libarchive-tools && \ + curl -SsfL "https://github.com/opentofu/opentofu/releases/download/${TOFU_VERSION}/tofu_${TOFU_VERSION##v}_linux_amd64.zip" | \ + bsdtar -xf - -C /usr/local/bin tofu && \ + chmod 755 /usr/local/bin/tofu && \ + apk del .build-deps && \ + rm -rf /var/cache/apk/* && \ + rm -rf /tmp/* && \ + rm -rf /root/.cache/ + +COPY --from=build /src/dist/wp-opentofu /bin/wp-opentofu +ENTRYPOINT ["/bin/wp-opentofu"] diff --git a/DOCS.md b/DOCS.md deleted file mode 100644 index 41e9f58..0000000 --- a/DOCS.md +++ /dev/null @@ -1,274 +0,0 @@ ---- -date: 2016-01-01T00:00:00+00:00 -title: Terraform -author: jmccann -tags: [ infrastructure, build tool ] -repo: jmccann/drone-terraform -logo: terraform.svg -image: jmccann/drone-terraform ---- - -The Terraform plugin applies the infrastructure configuration contained within the repository. The below pipeline configuration demonstrates simple usage which will run a `validate`, `plan` and `apply`: - -```yaml -pipeline: - terraform: - image: jmccann/drone-terraform:5 -``` - -Example configuration passing `vars` to terraform commands: - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ vars: -+ app_name: my-project -+ app_version: 1.0.0 -``` - -Example of explicitly specifying `actions` to perform a dry run. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ actions: -+ - validate -+ - plan -``` - -Example configuration passing secrets to terraform. Please read -https://www.terraform.io/docs/configuration/variables.html#environment-variables -for more details. - -**Drone 0.6+**: - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ secrets: -+ - source: terraform_secret -+ target: tf_var_my_secret -``` - -**Drone 0.5**: - -```diff -pipeline: - terraform_1: - image: jmccann/drone-terraform:5 -+ environment: -+ TF_VAR_MY_SECRET: ${TERRAFORM_SECRET} - - terraform_2: - image: jmccann/drone-terraform:5 - plan: false -+ sensitive: true -+ vars: -+ my_secret: ${TERRAFORM_SECRET} -``` - -You may be passing sensitive vars to your terraform commands. If you do not want -the terraform commands to display in your drone logs then set `sensitive` to `true`. -The output from the commands themselves will still display, it just won't show -what command is actually being ran. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ sensitive: true -``` - -Example configuration for overriding the terraform version. This will increase -plugin execution time as it will download/unpack the version of terraform -specified instead of using the embedded version that is included. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ tf_version: 0.10.3 -``` - -Example configuration with state tracked via remote. You will need a -[backend configuration](https://www.terraform.io/docs/backends/config.html) -specified in a `.tf` file. You can then pass additional options via the `.drone.yml`. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ init_options: -+ backend-config: -+ - "bucket=my-terraform-config-bucket" -+ - "key=tf-states/my-project" -+ - "region=us-east-1" -``` - -You may want to run terraform against internal resources, like an internal -OpenStack deployment. Sometimes these resources are signed by an internal -CA Certificate. You can inject your CA Certificate into the plugin by using -`ca_certs` key as described above. Below is an example. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ ca_cert: | -+ -----BEGIN CERTIFICATE----- -+ asdfsadf -+ asdfsadf -+ -----END CERTIFICATE------- -``` - -You may want to assume another role before running the terraform commands. -This is useful for cross account access, where a central account has privileges -to assume roles in other accounts. Using the current credentials, this role will -be assumed and exported to environment variables. -See [the discussion](https://github.com/hashicorp/terraform/issues/1275) in the Terraform issues. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ role_arn_to_assume: arn:aws:iam::account-of-role-to-assume:role/name-of-role -``` - -You may want to change directories before applying the terraform commands. -This parameter is useful if you have multiple environments in different folders -and you want to use different drone configurations to apply different environments. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ root_dir: some/path/here -``` - -You may want to only target a specific list of resources within your terraform -code. To achieve this you can specify the `targets` parameter. If left undefined -all resources will be planned/applied against as the default behavior. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ targets: -+ - aws_security_group.generic_sg -+ - aws_security_group.app_sg -``` - -You may want to limit the number of concurrent operations as Terraform walks its graph. -If you want to change Terraform's default parallelism (currently equal to 10) then set the `parallelism` parameter. - -```diff -pipeline: - terraform: - image: jmccann/drone-terraform:5 -+ parallelism: 2 -``` - -Destroying the service can be done by specifying `plan-destroy` and `destroy` actions. Keep in mind that Fastly won't allow a service with active version be destroyed. Use `force_destroy` option in the service definition for terraform to handle it. - -```diff -pipeline: - destroy: - image: jmccann/drone-terraform:5 -+ actions: -+ - plan-destroy -+ - destroy -``` - -Formatting the Terraform configuration files can be done by specifying the `fmt` action. Use `fmt_options` parameter to handle formatting options. - -```diff -pipeline: - fmt: - image: jmccann/drone-terraform:5 -+ actions: -+ - fmt -+ fmt_options: -+ write: false -+ diff: true -+ check: true -``` - -You may want to run some executions in parallel without having racing condition problems with the .terraform dir and -plan's output file. - -```diff -pipeline: - backend-service: - image: jmccann/drone-terraform: -+ tf_data_dir: .backend-service.terraform - frontend-service: - image: jmccann/drone-terraform: -+ tf_data_dir: .frontend-service.terraform -``` - -# Parameter Reference - -actions -: List of terraform actions to perform with the plugin. List includes: -`fmt`, `validate`, `plan`, `apply`, `plan-destroy`, `destroy`. - -init_options -: contains the configuration for the Terraform backend. - -init_options.backend-config -: This specifies additional configuration to merge for the backend. This can be -specified multiple times. Flags specified later in the line override those -specified earlier if they conflict. - -init_options.lock -: Lock the state file when locking is supported. Default `true`. - -init_options.lock-timeout -: Duration to wait for a state lock. Default `0s`. - -fmt_options -: contains the configuration for the fmt action. - -fmt_options.list -: List files whose formatting differs (disabled if using STDIN). Default `true`. - -fmt_options.write -: Write result to source file instead of STDOUT (disabled if using STDIN or -check). Default `true`. - -fmt_options.diff -: Display diffs of formatting changes. Default `false`. - -fmt_options.check -: Check if the input is formatted. Exit status will be 0 if all input is properly formatted and non-zero otherwise. Default `false`. - -vars -: a map of variables to pass to the Terraform `plan` and `apply` commands. -Each value is passed as a `-var =` option. - -var_files -: a list of variable files to pass to the Terraform `plan` and `apply` commands. -Each value is passed as a `-var-file ` option. - -ca_cert -: ca cert to add to your environment to allow terraform to use internal/private resources - -sensitive -: (default: `false`) - Whether or not to suppress terraform commands to stdout. - -role_arn_to_assume -: A role to assume before running the terraform commands. - -root_dir -: The root directory where the terraform files live. When unset, the top level directory will be assumed. - -parallelism -: The number of concurrent operations as Terraform walks its graph. - -tf_data_dir -: changes the location where Terraform keeps its per-working-directory data, such as the current remote backend configuration. - -disable_refresh -: (default: `false`) - whether or not to disable refreshing state before `plan` and `apply` commands. diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index d8352c3..0000000 --- a/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# Docker image for the Drone Terraform plugin -# -# docker build -t jmccann/drone-terraform:latest . -FROM golang:1.16-alpine AS builder - -RUN apk add --no-cache git - -WORKDIR /tmp/drone-terraform - -COPY go.mod go.mod -COPY go.sum go.sum -RUN go mod download - -COPY . . - -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -o /go/bin/drone-terraform - -FROM alpine:3.13 - -RUN apk add --no-cache \ - ca-certificates \ - git \ - wget \ - curl \ - openssh-client - -ARG terraform_version -RUN wget -q https://releases.hashicorp.com/terraform/${terraform_version}/terraform_${terraform_version}_linux_amd64.zip -O terraform.zip && \ - unzip terraform.zip -d /bin && \ - rm -f terraform.zip - -COPY --from=builder /go/bin/drone-terraform /bin/ -ENTRYPOINT ["/bin/drone-terraform"] diff --git a/LICENSE b/LICENSE index 5c304d1..7db554f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ -Apache License + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ Apache License APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright 2023 Robert Kaussow Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 0ecaf19..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,46 +0,0 @@ -[people] - [people.bradrydzewski] - name = "Brad Rydzewski" - email = "brad@drone.io" - login = "bradrydzewski" - [people.Bugagazavr] - name = "Kirill" - email = "" - login = "Bugagazavr" - [people.donny-dont] - name = "Don Olmstead" - email = "donny-dont@gmail.com" - login = "donny-dont" - [people.jackspirou] - name = "Jack Spirou" - email = "" - login = "jackspirou" - [people.msteinert] - name = "Mike Steinert" - email = "" - login = "msteinert" - [people.nlf] - name = "Nathan LaFreniere" - email = "" - login = "nlf" - [people.tboerger] - name = "Thomas Boerger" - email = "thomas@webhippie.de" - login = "tboerger" - [people.athieriot] - name = "Aurélien Thieriot" - email = "a.thieriot@gmail.com" - login = "athieriot" - -[org] - [org.core] - people = [ - "bradrydzewski", - "Bugagazavr", - "donny-dont", - "jackspirou", - "msteinert", - "nlf", - "tboerger", - "athieriot" - ] diff --git a/Makefile b/Makefile index 0dc93e2..fc10e3c 100644 --- a/Makefile +++ b/Makefile @@ -1,34 +1,107 @@ -.PHONY: all clean deps fmt vet test docker +# renovate: datasource=github-releases depName=mvdan/gofumpt +GOFUMPT_PACKAGE_VERSION := v0.6.0 +# renovate: datasource=github-releases depName=golangci/golangci-lint +GOLANGCI_LINT_PACKAGE_VERSION := v1.55.2 -EXECUTABLE ?= drone-terraform -IMAGE ?= plugins/$(EXECUTABLE) -COMMIT ?= $(shell git rev-parse --short HEAD) +EXECUTABLE := wp-opentofu -LDFLAGS = -X "main.buildCommit=$(COMMIT)" -PACKAGES = $(shell go list ./... | grep -v /vendor/) +DIST := dist +DIST_DIRS := $(DIST) +IMPORT := github.com/thegeeklab/$(EXECUTABLE) -all: deps build test +GO ?= go +CWD ?= $(shell pwd) +PACKAGES ?= $(shell go list ./...) +SOURCES ?= $(shell find . -name "*.go" -type f) +GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION) +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION) +XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest +GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest + +GENERATE ?= +XGO_VERSION := go-1.21.x +XGO_TARGETS ?= linux/amd64,linux/arm64 + +TARGETOS ?= linux +TARGETARCH ?= amd64 +ifneq ("$(TARGETVARIANT)","") +GOARM ?= $(subst v,,$(TARGETVARIANT)) +endif +TAGS ?= netgo + +ifndef VERSION + ifneq ($(CI_COMMIT_TAG),) + VERSION ?= $(subst v,,$(CI_COMMIT_TAG)) + else + VERSION ?= $(shell git rev-parse --short HEAD) + endif +endif + +ifndef DATE + DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%z") +endif + +LDFLAGS += -s -w -X "main.BuildVersion=$(VERSION)" -X "main.BuildDate=$(DATE)" + +.PHONY: all +all: clean build + +.PHONY: clean clean: - go clean -i ./... - -deps: - go get -t ./... + $(GO) clean -i ./... + rm -rf $(DIST_DIRS) +.PHONY: fmt fmt: - go fmt $(PACKAGES) + $(GO) run $(GOFUMPT_PACKAGE) -extra -w $(SOURCES) -vet: - go vet $(PACKAGES) +.PHONY: golangci-lint +golangci-lint: + $(GO) run $(GOLANGCI_LINT_PACKAGE) run +.PHONY: lint +lint: golangci-lint + +.PHONY: generate +generate: + $(GO) generate $(GENERATE) + +.PHONY: generate-docs +generate-docs: + $(GO) generate ./cmd/$(EXECUTABLE)/flags.go + +.PHONY: test test: - @for PKG in $(PACKAGES); do go test -cover -coverprofile $$GOPATH/src/$$PKG/coverage.out $$PKG || exit 1; done; + $(GO) run $(GOTESTSUM_PACKAGE) --no-color=false -- -coverprofile=coverage.out $(PACKAGES) -docker: - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '-s -w $(LDFLAGS)' - docker build --rm -t $(IMAGE) . +.PHONY: build +build: $(DIST)/$(EXECUTABLE) -$(EXECUTABLE): $(wildcard *.go) - go build -ldflags '-s -w $(LDFLAGS)' +$(DIST)/$(EXECUTABLE): $(SOURCES) + GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) GOARM=$(GOARM) $(GO) build -v -tags '$(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -o $@ ./cmd/$(EXECUTABLE) -build: $(EXECUTABLE) +$(DIST_DIRS): + mkdir -p $(DIST_DIRS) + +.PHONY: xgo +xgo: | $(DIST_DIRS) + $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -v -ldflags '-extldflags "-static" $(LDFLAGS)' -tags '$(TAGS)' -targets '$(XGO_TARGETS)' -out $(EXECUTABLE) --pkg cmd/$(EXECUTABLE) . + cp /build/* $(CWD)/$(DIST) + ls -l $(CWD)/$(DIST) + +.PHONY: checksum +checksum: + cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) + ls -l $(CWD)/$(DIST) + +.PHONY: release +release: xgo checksum + +.PHONY: deps +deps: + $(GO) mod download + $(GO) install $(GOFUMPT_PACKAGE) + $(GO) install $(GOLANGCI_LINT_PACKAGE) + $(GO) install $(XGO_PACKAGE) + $(GO) install $(GOTESTSUM_PACKAGE) diff --git a/README.md b/README.md index 01ffed7..b503216 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,21 @@ -# drone-terraform +# wp-opentofu -[![Build Status](http://beta.drone.io/api/badges/jmccann/drone-terraform/status.svg)](http://beta.drone.io/jmccann/drone-terraform) +Woodpecker CI plugin to manage infrastructure with OpenTofu -Drone plugin to execute Terraform plan and apply. For the usage information and -a listing of the available options please take a look at [the docs](https://github.com/jmccann/drone-terraform/blob/master/DOCS.md). +[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/wp-opentofu/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/wp-opentofu) +[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/wp-opentofu) +[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/wp-opentofu) +[![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/wp-opentofu)](https://goreportcard.com/report/github.com/thegeeklab/wp-opentofu) +[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/wp-opentofu)](https://github.com/thegeeklab/wp-opentofu/graphs/contributors) +[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/wp-opentofu) +[![License: Apache-2.0](https://img.shields.io/github/license/thegeeklab/wp-opentofu)](https://github.com/thegeeklab/wp-opentofu/blob/main/LICENSE) -## Build +Woodpecker CI plugin to manage infrastructure with [OpenTofu](https://github.com/opentofu/opentofu). This plugin is a fork of [jmccann/drone-terraform](https://github.com/jmccann/drone-terraform). You can find the full documentation at [https://woodpecker-plugins.geekdocs.de](https://woodpecker-plugins.geekdocs.de/plugins/wp-opentofu). -Build the binary with the following commands: +## Contributors -``` -export GO111MODULE=on -go mod download -go test -go build -``` +Special thanks to all [contributors](https://github.com/thegeeklab/wp-opentofu/graphs/contributors). If you would like to contribute, please see the [instructions](https://github.com/thegeeklab/wp-opentofu/blob/main/CONTRIBUTING.md). -## Docker +## License -Build the docker image with the following commands: - -``` -docker build --rm=true \ - -t jmccann/drone-terraform \ - --build-arg terraform_version=0.12.0 . -``` - -## Usage - -Execute from the working directory: - -``` -docker run --rm \ - -v $(pwd):$(pwd) \ - -w $(pwd) \ - jmccann/drone-terraform:latest --plan -``` - -## Drone 0.4 - -Legacy `drone-terraform` plugin exists @ `jmccann/drone-terraform:0.4` +This project is licensed under the Apache-2.0 License - see the [LICENSE](https://github.com/thegeeklab/wp-opentofu/blob/main/LICENSE) file for details. diff --git a/build-docker.sh b/build-docker.sh deleted file mode 100755 index 4b23861..0000000 --- a/build-docker.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -set -e - -tag=$1 - -if [ -z $tag ]; then - echo "please provide a tag arg" - exit 1 -fi - -major=$(echo $tag | awk -F. '{print $1}') -minor=$(echo $tag | awk -F. '{print $2}') - -tf_ver="1.0.2" - -echo "Confirm building images for:" -echo " MAJOR: ${major}" -echo " MINOR: ${minor}" -echo " TF_VERSION: ${tf_ver}" - -read -p "Proceed? [Y/N] " ans - -if [[ "$ans" != "Y" && "$ans" != "y" ]]; then - echo "Cancelling" - exit 0 -fi - -set -x -docker build -t jmccann/drone-terraform:latest --build-arg terraform_version=${tf_ver} . - -docker tag jmccann/drone-terraform:latest jmccann/drone-terraform:${major} -docker tag jmccann/drone-terraform:latest jmccann/drone-terraform:${major}.${minor} -docker tag jmccann/drone-terraform:latest jmccann/drone-terraform:${major}.${minor}-${tf_ver} - -docker push jmccann/drone-terraform:latest -docker push jmccann/drone-terraform:${major} -docker push jmccann/drone-terraform:${major}.${minor} -docker push jmccann/drone-terraform:${major}.${minor}-${tf_ver} -set +x diff --git a/cmd/wp-ansible/docs.go b/cmd/wp-ansible/docs.go new file mode 100644 index 0000000..de5412d --- /dev/null +++ b/cmd/wp-ansible/docs.go @@ -0,0 +1,58 @@ +//go:build generate +// +build generate + +package main + +import ( + "bytes" + "embed" + "fmt" + "os" + "text/template" + + "github.com/thegeeklab/wp-opentofu/plugin" + "github.com/thegeeklab/wp-plugin-go/docs" + wp "github.com/thegeeklab/wp-plugin-go/plugin" + wp_template "github.com/thegeeklab/wp-plugin-go/template" + "github.com/urfave/cli/v2" +) + +//go:embed templates/docs-data.yaml.tmpl +var yamlTemplate embed.FS + +func main() { + settings := &plugin.Settings{} + app := &cli.App{ + Flags: settingsFlags(settings, wp.FlagsPluginCategory), + } + + out, err := toYAML(app) + if err != nil { + panic(err) + } + + fi, err := os.Create("../../docs/data/data-raw.yaml") + if err != nil { + panic(err) + } + defer fi.Close() + if _, err := fi.WriteString(out); err != nil { + panic(err) + } +} + +func toYAML(app *cli.App) (string, error) { + var w bytes.Buffer + + yamlTmpl, err := template.New("docs").Funcs(wp_template.LoadFuncMap()).ParseFS(yamlTemplate, "templates/docs-data.yaml.tmpl") + if err != nil { + fmt.Println(yamlTmpl) + return "", err + } + + if err := yamlTmpl.ExecuteTemplate(&w, "docs-data.yaml.tmpl", docs.GetTemplateData(app)); err != nil { + return "", err + } + + return w.String(), nil +} diff --git a/cmd/wp-ansible/flags.go b/cmd/wp-ansible/flags.go new file mode 100644 index 0000000..1dc6ce8 --- /dev/null +++ b/cmd/wp-ansible/flags.go @@ -0,0 +1,76 @@ +package main + +import ( + "github.com/thegeeklab/wp-opentofu/plugin" + "github.com/urfave/cli/v2" +) + +// SettingsFlags has the cli.Flags for the plugin.Settings. +// +//go:generate go run docs.go flags.go +func settingsFlags(settings *plugin.Settings, category string) []cli.Flag { + return []cli.Flag{ + &cli.StringSliceFlag{ + Name: "action", + Usage: "tofu actions to execute", + EnvVars: []string{"PLUGIN_ACTION"}, + Value: cli.NewStringSlice("validate", "plan", "apply"), + Destination: &settings.Action, + Category: category, + }, + &cli.StringFlag{ + Name: "init-options", + Usage: "tofu init command options, see https://opentofu.org/docs/cli/commands/init/", + EnvVars: []string{"PLUGIN_INIT_OPTIONS"}, + Category: category, + }, + &cli.StringFlag{ + Name: "fmt-options", + Usage: "options for the fmt command, see https://opentofu.org/docs/cli/commands/fmt/", + EnvVars: []string{"PLUGIN_FMT_OPTIONS"}, + Category: category, + }, + &cli.IntFlag{ + Name: "parallelism", + Usage: "number of concurrent operations", + EnvVars: []string{"PLUGIN_PARALLELISM"}, + Category: category, + }, + &cli.StringFlag{ + Name: "root-dir", + Usage: "root directory where the tofu files live", + EnvVars: []string{"PLUGIN_ROOT_DIR"}, + Destination: &settings.RootDir, + Category: category, + }, + &cli.BoolFlag{ + Name: "no-log", + Usage: "suppress tofu command output", + EnvVars: []string{"PLUGIN_NO_LOG"}, + Destination: &settings.NoLog, + Category: category, + }, + &cli.StringSliceFlag{ + Name: "targets", + Usage: "targets to run `apply` or `plan` action on", + EnvVars: []string{"PLUGIN_TARGETS"}, + Destination: &settings.Targets, + Category: category, + }, + &cli.StringFlag{ + Name: "version", + Usage: "tofu version to use", + EnvVars: []string{"PLUGIN_VERSION"}, + Destination: &settings.Version, + Category: category, + }, + &cli.BoolFlag{ + Name: "refresh", + Usage: "enables refreshing of the state before `plan` and `apply` commands", + EnvVars: []string{"PLUGIN_REFRESH"}, + Destination: &settings.Refresh, + Value: true, + Category: category, + }, + } +} diff --git a/cmd/wp-ansible/main.go b/cmd/wp-ansible/main.go new file mode 100644 index 0000000..7d69aab --- /dev/null +++ b/cmd/wp-ansible/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + + "github.com/thegeeklab/wp-opentofu/plugin" + + wp "github.com/thegeeklab/wp-plugin-go/plugin" +) + +//nolint:gochecknoglobals +var ( + BuildVersion = "devel" + BuildDate = "00000000" +) + +func main() { + settings := &plugin.Settings{} + options := wp.Options{ + Name: "wp-opentofu", + Description: "Manage infrastructure with OpenTofu", + Version: BuildVersion, + VersionMetadata: fmt.Sprintf("date=%s", BuildDate), + Flags: settingsFlags(settings, wp.FlagsPluginCategory), + } + + plugin.New(options, settings).Run() +} diff --git a/cmd/wp-ansible/templates/docs-data.yaml.tmpl b/cmd/wp-ansible/templates/docs-data.yaml.tmpl new file mode 100644 index 0000000..e453a95 --- /dev/null +++ b/cmd/wp-ansible/templates/docs-data.yaml.tmpl @@ -0,0 +1,18 @@ +--- +{{- if .GlobalArgs }} +properties: +{{- range $v := .GlobalArgs }} + - name: {{ $v.Name }} + {{- with $v.Description }} + description: | + {{ . | ToSentence }} + {{- end }} + {{- with $v.Type }} + type: {{ . }} + {{- end }} + {{- with $v.Default }} + defaultValue: {{ . }} + {{- end }} + required: {{ default false $v.Required }} +{{ end -}} +{{ end -}} diff --git a/docs/content/_index.md b/docs/content/_index.md new file mode 100644 index 0000000..9ec6f5b --- /dev/null +++ b/docs/content/_index.md @@ -0,0 +1,59 @@ +--- +title: wp-opentofu +--- + +[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/wp-opentofu/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/wp-opentofu) +[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/wp-opentofu) +[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/wp-opentofu) +[![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/wp-opentofu)](https://goreportcard.com/report/github.com/thegeeklab/wp-opentofu) +[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/wp-opentofu)](https://github.com/thegeeklab/wp-opentofu/graphs/contributors) +[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/wp-opentofu) +[![License: Apache-2.0](https://img.shields.io/github/license/thegeeklab/wp-opentofu)](https://github.com/thegeeklab/wp-opentofu/blob/main/LICENSE) + +Woodpecker CI plugin to manage infrastructure with [OpenTofu](https://github.com/opentofu/opentofu). + + + +{{< toc >}} + + + +## Usage + +```YAML +steps: + - name: tofu + image: quay.io/thegeeklab/wp-opentofu + settings: + actions: + - validate + - plan +``` + +### Parameters + + + +{{< propertylist name=wp-opentofu.data sort=name >}} + + + +## Build + +Build the binary with the following command: + +```Shell +make build +``` + +Build the Container image with the following command: + +```Shell +docker build --file Containerfile.multiarch --tag thegeeklab/wp-opentofu . +``` + +## Test + +```Shell + +``` diff --git a/docs/data/data.yaml b/docs/data/data.yaml new file mode 100644 index 0000000..8d30b9e --- /dev/null +++ b/docs/data/data.yaml @@ -0,0 +1,211 @@ +--- +properties: + - name: become + description: | + Enable privilege escalation. + type: bool + defaultValue: false + required: false + + - name: become_method + description: | + Privilege escalation method to use. + type: string + required: false + + - name: become_user + description: | + Privilege escalation user to use. + type: string + required: false + + - name: check + description: | + Run a check, do not apply any changes. + type: bool + defaultValue: false + required: false + + - name: connection + description: | + Connection type to use. + type: string + required: false + + - name: diff + description: | + Show the differences. Be careful when using it in public CI environments as it can print secrets. + type: bool + defaultValue: false + required: false + + - name: extra_vars + description: | + Set additional variables as `key=value`. + type: list + required: false + + - name: flush_cache + description: | + Clear the fact cache for every host in inventory. + type: bool + defaultValue: false + required: false + + - name: force_handlers + description: | + Run handlers even if a task fails. + type: bool + defaultValue: false + required: false + + - name: forks + description: | + Specify number of parallel processes to use. + type: integer + defaultValue: 5 + required: false + + - name: galaxy_requirements + description: | + Path to galaxy requirements file. + type: string + required: false + + - name: inventory + description: | + Path to inventory file. + type: list + required: false + + - name: limit + description: | + Limit selected hosts to an additional pattern. + type: string + required: false + + - name: list_hosts + description: | + Outputs a list of matching hosts. + type: bool + defaultValue: false + required: false + + - name: list_tags + description: | + List all available tags. + type: bool + defaultValue: false + required: false + + - name: list_tasks + description: | + List all tasks that would be executed. + type: bool + defaultValue: false + required: false + + - name: module_path + description: | + Prepend paths to module library. + type: list + required: false + + - name: playbook + description: | + List of playbooks to apply. + type: list + required: false + + - name: private_key + description: | + SSH private key used to authenticate the connection. + type: string + required: false + + - name: python_requirements + description: | + Path to python requirements file. + type: string + required: false + + - name: scp_extra_args + description: | + Specify extra arguments to pass to SCP connections only. + type: string + required: false + + - name: sftp_extra_args + description: | + Specify extra arguments to pass to SFTP connections only. + type: string + required: false + + - name: skip_tags + description: | + Only run plays and tasks whose tags do not match. + type: string + required: false + + - name: ssh_common_args + description: | + Specify common arguments to pass to SFTP, SCP and SSH connections. + type: string + required: false + + - name: ssh_extra_args + description: | + Specify extra arguments to pass to SSH connections only. + type: string + required: false + + - name: start_at_task + description: | + Start the playbook at the task matching this name. + type: string + required: false + + - name: syntax_check + description: | + Perform a syntax check on the playbook. + type: bool + defaultValue: false + required: false + + - name: tags + description: | + Only run plays and tasks tagged with these values. + type: string + required: false + + - name: timeout + description: | + Override the connection timeout in seconds. + type: integer + defaultValue: 0 + required: false + + - name: user + description: | + Connect as this user. + type: string + required: false + + - name: vault_id + description: | + The vault identity to use. + type: string + required: false + + - name: vault_password + description: | + The vault password to use. + type: string + required: false + + - name: verbose + description: | + Level of verbosity, 0 up to 4. + type: integer + defaultValue: 0 + required: false diff --git a/go.mod b/go.mod index c44c644..e0b989a 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,31 @@ -module github.com/jmccann/drone-terraform +module github.com/thegeeklab/wp-opentofu + +go 1.21 require ( - github.com/Sirupsen/logrus v0.0.0-20160829202321-3ec0642a7fb6 - github.com/aws/aws-sdk-go v0.0.0-20161007224333-c0d7d3282e4c - github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db - github.com/go-ini/ini v1.21.1 - github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7 - github.com/joho/godotenv v1.3.0 - github.com/urfave/cli v0.0.0-20161006035353-55f715e28c46 - golang.org/x/sys v0.0.0-20161006025142-8d1157a43547 + github.com/Masterminds/semver/v3 v3.2.1 + github.com/thegeeklab/wp-plugin-go v1.5.0 + github.com/urfave/cli/v2 v2.27.1 + golang.org/x/sys v0.16.0 ) -go 1.13 +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/rs/zerolog v1.31.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/net v0.19.0 // indirect +) diff --git a/go.sum b/go.sum index 4b665e7..280ac71 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,100 @@ -github.com/Sirupsen/logrus v0.0.0-20160829202321-3ec0642a7fb6 h1:Tp6VdyWz8sPuNnRbgf5jqIOV/zoG4mA5nBHtJyFb4Hc= -github.com/Sirupsen/logrus v0.0.0-20160829202321-3ec0642a7fb6/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U= -github.com/aws/aws-sdk-go v0.0.0-20161007224333-c0d7d3282e4c h1:IF3li2sCc78JFAsluLt9iiCCOnWkMYG9FApcWnq4Sv0= -github.com/aws/aws-sdk-go v0.0.0-20161007224333-c0d7d3282e4c/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k= -github.com/franela/goblin v0.0.0-20170111051028-2fa789fd0c6b h1:pFXMOmYgv60RqCPRU9v+jprPw88/oor7dQDykOfi17A= -github.com/franela/goblin v0.0.0-20170111051028-2fa789fd0c6b/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db h1:gb2Z18BhTPJPpLQWj4T+rfKHYCHxRHCtRxhKKjRidVw= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/go-ini/ini v1.21.1 h1:+QXUYsI7Tfxc64oD6R5BxU/Aq+UwGkyjH4W/hMNG7bg= -github.com/go-ini/ini v1.21.1/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7 h1:SMvOWPJCES2GdFracYbBQh93GXac8fq7HeN6JnpduB8= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/joho/godotenv v0.0.0-20150907010228-4ed13390c0ac h1:wF2VgtpbaLqhBHV9FxVWzgzgv8VcCjZ66Bl/+F6cpT0= -github.com/joho/godotenv v0.0.0-20150907010228-4ed13390c0ac/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/urfave/cli v0.0.0-20161006035353-55f715e28c46 h1:EztUvugq7AA7F3lYLmtFQyvKdcY5pisPt10DqPjRCL8= -github.com/urfave/cli v0.0.0-20161006035353-55f715e28c46/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -golang.org/x/sys v0.0.0-20161006025142-8d1157a43547/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A= +github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/thegeeklab/wp-plugin-go v1.5.0 h1:fA/kQwQrntjxdxePKKGecMRnPbj1xwpy6D2JrrPD2qA= +github.com/thegeeklab/wp-plugin-go v1.5.0/go.mod h1:ULBD5SNC7bkIB/UbLMQpcPWj7iZObCTWg8bqXf1zqsA= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI= +github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logo.svg b/logo.svg deleted file mode 100644 index f658556..0000000 --- a/logo.svg +++ /dev/null @@ -1,33 +0,0 @@ - - - - -Created by potrace 1.13, written by Peter Selinger 2001-2015 - - - - - diff --git a/main.go b/main.go deleted file mode 100644 index d396e76..0000000 --- a/main.go +++ /dev/null @@ -1,184 +0,0 @@ -package main - -import ( - "encoding/json" - "os" - - "github.com/Sirupsen/logrus" - "github.com/joho/godotenv" - "github.com/urfave/cli" -) - -var revision string // build number set at compile-time - -func main() { - app := cli.NewApp() - app.Name = "terraform plugin" - app.Usage = "terraform plugin" - app.Action = run - app.Version = revision - app.Flags = []cli.Flag{ - - // - // plugin args - // - - cli.StringSliceFlag{ - Name: "actions", - Usage: "a list of actions to have terraform perform", - EnvVar: "PLUGIN_ACTIONS", - Value: &cli.StringSlice{"validate", "plan", "apply"}, - }, - cli.StringFlag{ - Name: "ca_cert", - Usage: "ca cert to add to your environment to allow terraform to use internal/private resources", - EnvVar: "PLUGIN_CA_CERT", - }, - cli.StringFlag{ - Name: "env_file", - Usage: "pass filename to source it and load variables into current shell", - EnvVar: "PLUGIN_ENV_FILE", - }, - cli.StringFlag{ - Name: "init_options", - Usage: "options for the init command. See https://www.terraform.io/docs/commands/init.html", - EnvVar: "PLUGIN_INIT_OPTIONS", - }, - cli.StringFlag{ - Name: "fmt_options", - Usage: "options for the fmt command. See https://www.terraform.io/docs/commands/fmt.html", - EnvVar: "PLUGIN_FMT_OPTIONS", - }, - cli.IntFlag{ - Name: "parallelism", - Usage: "The number of concurrent operations as Terraform walks its graph", - EnvVar: "PLUGIN_PARALLELISM", - }, - cli.StringFlag{ - Name: "netrc.machine", - Usage: "netrc machine", - EnvVar: "DRONE_NETRC_MACHINE", - }, - cli.StringFlag{ - Name: "netrc.username", - Usage: "netrc username", - EnvVar: "DRONE_NETRC_USERNAME", - }, - cli.StringFlag{ - Name: "netrc.password", - Usage: "netrc password", - EnvVar: "DRONE_NETRC_PASSWORD", - }, - cli.StringFlag{ - Name: "role_arn_to_assume", - Usage: "A role to assume before running the terraform commands", - EnvVar: "PLUGIN_ROLE_ARN_TO_ASSUME", - }, - cli.StringFlag{ - Name: "root_dir", - Usage: "The root directory where the terraform files live. When unset, the top level directory will be assumed", - EnvVar: "PLUGIN_ROOT_DIR", - }, - cli.StringFlag{ - Name: "secrets", - Usage: "a map of secrets to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `=` option", - EnvVar: "PLUGIN_SECRETS", - }, - cli.BoolFlag{ - Name: "sensitive", - Usage: "whether or not to suppress terraform commands to stdout", - EnvVar: "PLUGIN_SENSITIVE", - }, - cli.StringSliceFlag{ - Name: "targets", - Usage: "targets to run apply or plan on", - EnvVar: "PLUGIN_TARGETS", - }, - cli.StringFlag{ - Name: "tf.version", - Usage: "terraform version to use", - EnvVar: "PLUGIN_TF_VERSION", - }, - cli.StringFlag{ - Name: "vars", - Usage: "a map of variables to pass to the Terraform `plan` and `apply` commands. Each value is passed as a `=` option", - EnvVar: "PLUGIN_VARS", - }, - cli.StringSliceFlag{ - Name: "var_files", - Usage: "a list of var files to use. Each value is passed as -var-file=", - EnvVar: "PLUGIN_VAR_FILES", - }, - cli.StringFlag{ - Name: "tf_data_dir", - Usage: "changes the location where Terraform keeps its per-working-directory data, such as the current remote backend configuration", - EnvVar: "PLUGIN_TF_DATA_DIR", - }, - cli.BoolFlag{ - Name: "disable_refresh", - Usage: "whether or not to disable refreshing state before `plan` and `apply` commands", - EnvVar: "PLUGIN_DISABLE_REFRESH", - }, - } - - if err := app.Run(os.Args); err != nil { - logrus.Fatal(err) - } -} - -func run(c *cli.Context) error { - logrus.WithFields(logrus.Fields{ - "Revision": revision, - }).Info("Drone Terraform Plugin Version") - - if c.String("env_file") != "" { - _ = godotenv.Load(c.String("env_file")) - } - - var vars map[string]string - if c.String("vars") != "" { - if err := json.Unmarshal([]byte(c.String("vars")), &vars); err != nil { - panic(err) - } - } - var secrets map[string]string - if c.String("secrets") != "" { - if err := json.Unmarshal([]byte(c.String("secrets")), &secrets); err != nil { - panic(err) - } - } - - initOptions := InitOptions{} - json.Unmarshal([]byte(c.String("init_options")), &initOptions) - fmtOptions := FmtOptions{} - json.Unmarshal([]byte(c.String("fmt_options")), &fmtOptions) - - plugin := Plugin{ - Config: Config{ - Actions: c.StringSlice("actions"), - Vars: vars, - Secrets: secrets, - InitOptions: initOptions, - FmtOptions: fmtOptions, - Cacert: c.String("ca_cert"), - Sensitive: c.Bool("sensitive"), - RoleARN: c.String("role_arn_to_assume"), - RootDir: c.String("root_dir"), - Parallelism: c.Int("parallelism"), - Targets: c.StringSlice("targets"), - VarFiles: c.StringSlice("var_files"), - TerraformDataDir: c.String("tf_data_dir"), - DisableRefresh: c.Bool("disable_refresh"), - }, - Netrc: Netrc{ - Login: c.String("netrc.username"), - Machine: c.String("netrc.machine"), - Password: c.String("netrc.password"), - }, - Terraform: Terraform{ - Version: c.String("tf.version"), - }, - } - - return plugin.Exec() -} diff --git a/plugin.go b/plugin.go deleted file mode 100644 index 6646fcf..0000000 --- a/plugin.go +++ /dev/null @@ -1,415 +0,0 @@ -package main - -import ( - "fmt" - "io/ioutil" - "os" - "os/exec" - "os/user" - "path/filepath" - "regexp" - "strings" - "time" - - "github.com/Sirupsen/logrus" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/credentials/stscreds" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/sts" -) - -type ( - // Config holds input parameters for the plugin - Config struct { - Actions []string - Vars map[string]string - Secrets map[string]string - InitOptions InitOptions - FmtOptions FmtOptions - Cacert string - Sensitive bool - RoleARN string - RootDir string - Parallelism int - Targets []string - VarFiles []string - TerraformDataDir string - DisableRefresh bool - } - - // Netrc is credentials for cloning - Netrc struct { - Machine string - Login string - Password string - } - - // InitOptions include options for the Terraform's init command - InitOptions struct { - BackendConfig []string `json:"backend-config"` - Lock *bool `json:"lock"` - LockTimeout string `json:"lock-timeout"` - } - - // FmtOptions fmt options for the Terraform's fmt command - FmtOptions struct { - List *bool `json:"list"` - Write *bool `json:"write"` - Diff *bool `json:"diff"` - Check *bool `json:"check"` - } - - // Plugin represents the plugin instance to be executed - Plugin struct { - Config Config - Netrc Netrc - Terraform Terraform - } -) - -// Exec executes the plugin -func (p Plugin) Exec() error { - // Install specified version of terraform - if p.Terraform.Version != "" { - err := installTerraform(p.Terraform.Version) - - if err != nil { - return err - } - } - - if p.Config.RoleARN != "" && !credsSet() { - assumeRole(p.Config.RoleARN) - } - - // writing the .netrc file with Github credentials in it. - err := writeNetrc(p.Netrc.Machine, p.Netrc.Login, p.Netrc.Password) - if err != nil { - return err - } - - terraformDataDir := ".terraform" - if p.Config.TerraformDataDir != "" { - terraformDataDir = p.Config.TerraformDataDir - os.Setenv("TF_DATA_DIR", p.Config.TerraformDataDir) - } - - var commands []*exec.Cmd - - commands = append(commands, exec.Command("terraform", "version")) - - CopyTfEnv() - - if p.Config.Cacert != "" { - commands = append(commands, installCaCert(p.Config.Cacert)) - } - - commands = append(commands, deleteCache(terraformDataDir)) - commands = append(commands, initCommand(p.Config.InitOptions)) - commands = append(commands, getModules()) - - // Add commands listed from Actions - for _, action := range p.Config.Actions { - switch action { - case "fmt": - commands = append(commands, tfFmt(p.Config)) - case "validate": - commands = append(commands, tfValidate()) - case "plan": - commands = append(commands, tfPlan(p.Config, false)) - case "plan-destroy": - commands = append(commands, tfPlan(p.Config, true)) - case "apply": - commands = append(commands, tfApply(p.Config)) - case "destroy": - commands = append(commands, tfDestroy(p.Config)) - default: - return fmt.Errorf("valid actions are: fmt, validate, plan, apply, plan-destroy, destroy. You provided %s", action) - } - } - - commands = append(commands, deleteCache(terraformDataDir)) - - for _, c := range commands { - if c.Dir == "" { - wd, err := os.Getwd() - if err == nil { - c.Dir = wd - } - } - if p.Config.RootDir != "" { - c.Dir = c.Dir + "/" + p.Config.RootDir - } - c.Stdout = os.Stdout - c.Stderr = os.Stderr - if !p.Config.Sensitive { - trace(c) - } - - err := c.Run() - if err != nil { - logrus.WithFields(logrus.Fields{ - "error": err, - }).Fatal("Failed to execute a command") - } - logrus.Debug("Command completed successfully") - } - - return nil -} - -// CopyTfEnv creates copies of TF_VAR_ to lowercase -func CopyTfEnv() { - tfVar := regexp.MustCompile(`^TF_VAR_.*$`) - for _, e := range os.Environ() { - pair := strings.SplitN(e, "=", 2) - if tfVar.MatchString(pair[0]) { - name := strings.Split(pair[0], "TF_VAR_") - os.Setenv(fmt.Sprintf("TF_VAR_%s", strings.ToLower(name[1])), pair[1]) - } - } -} - -func credsSet() bool { - awsTokens := []string{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"} - for _, token := range awsTokens { - if os.Getenv(token) == "" { - return false - } - } - return true -} - -func assumeRole(roleArn string) { - client := sts.New(session.New()) - duration := time.Hour * 1 - stsProvider := &stscreds.AssumeRoleProvider{ - Client: client, - Duration: duration, - RoleARN: roleArn, - RoleSessionName: "drone", - } - - value, err := credentials.NewCredentials(stsProvider).Get() - if err != nil { - logrus.WithFields(logrus.Fields{ - "error": err, - }).Fatal("Error assuming role!") - } - os.Setenv("AWS_ACCESS_KEY_ID", value.AccessKeyID) - os.Setenv("AWS_SECRET_ACCESS_KEY", value.SecretAccessKey) - os.Setenv("AWS_SESSION_TOKEN", value.SessionToken) -} - -func deleteCache(terraformDataDir string) *exec.Cmd { - return exec.Command( - "rm", - "-rf", - terraformDataDir, - ) -} - -func getModules() *exec.Cmd { - return exec.Command( - "terraform", - "get", - ) -} - -func initCommand(config InitOptions) *exec.Cmd { - args := []string{ - "init", - } - - for _, v := range config.BackendConfig { - args = append(args, fmt.Sprintf("-backend-config=%s", v)) - } - - // Fail Terraform execution on prompt - args = append(args, "-input=false") - - return exec.Command( - "terraform", - args..., - ) -} - -func installCaCert(cacert string) *exec.Cmd { - ioutil.WriteFile("/usr/local/share/ca-certificates/ca_cert.crt", []byte(cacert), 0644) - return exec.Command( - "update-ca-certificates", - ) -} - -func trace(cmd *exec.Cmd) { - fmt.Println("$", strings.Join(cmd.Args, " ")) -} - -func tfApply(config Config) *exec.Cmd { - args := []string{ - "apply", - } - for _, v := range config.Targets { - args = append(args, "--target", fmt.Sprintf("%s", v)) - } - if config.Parallelism > 0 { - args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) - } - if config.InitOptions.Lock != nil { - args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock)) - } - if config.InitOptions.LockTimeout != "" { - args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) - } - if config.DisableRefresh { - args = append(args, "-refresh=false") - } - args = append(args, getTfoutPath()) - - return exec.Command( - "terraform", - args..., - ) -} - -func tfDestroy(config Config) *exec.Cmd { - args := []string{ - "destroy", - } - for _, v := range config.Targets { - args = append(args, fmt.Sprintf("-target=%s", v)) - } - args = append(args, varFiles(config.VarFiles)...) - args = append(args, vars(config.Vars)...) - if config.Parallelism > 0 { - args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) - } - if config.InitOptions.Lock != nil { - args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock)) - } - if config.InitOptions.LockTimeout != "" { - args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) - } - args = append(args, "-auto-approve") - return exec.Command( - "terraform", - args..., - ) -} - -func tfPlan(config Config, destroy bool) *exec.Cmd { - args := []string{ - "plan", - } - - if destroy { - args = append(args, "-destroy") - } else { - args = append(args, fmt.Sprintf("-out=%s", getTfoutPath())) - } - - for _, v := range config.Targets { - args = append(args, "--target", fmt.Sprintf("%s", v)) - } - args = append(args, varFiles(config.VarFiles)...) - args = append(args, vars(config.Vars)...) - if config.Parallelism > 0 { - args = append(args, fmt.Sprintf("-parallelism=%d", config.Parallelism)) - } - if config.InitOptions.Lock != nil { - args = append(args, fmt.Sprintf("-lock=%t", *config.InitOptions.Lock)) - } - if config.InitOptions.LockTimeout != "" { - args = append(args, fmt.Sprintf("-lock-timeout=%s", config.InitOptions.LockTimeout)) - } - if config.DisableRefresh { - args = append(args, "-refresh=false") - } - return exec.Command( - "terraform", - args..., - ) -} - -func tfValidate() *exec.Cmd { - return exec.Command( - "terraform", - "validate", - ) -} - -func tfFmt(config Config) *exec.Cmd { - args := []string{ - "fmt", - } - if config.FmtOptions.List != nil { - args = append(args, fmt.Sprintf("-list=%t", *config.FmtOptions.List)) - } - if config.FmtOptions.Write != nil { - args = append(args, fmt.Sprintf("-write=%t", *config.FmtOptions.Write)) - } - if config.FmtOptions.Diff != nil { - args = append(args, fmt.Sprintf("-diff=%t", *config.FmtOptions.Diff)) - } - if config.FmtOptions.Check != nil { - args = append(args, fmt.Sprintf("-check=%t", *config.FmtOptions.Check)) - } - return exec.Command( - "terraform", - args..., - ) -} - -func getTfoutPath() string { - terraformDataDir := os.Getenv("TF_DATA_DIR") - if terraformDataDir == ".terraform" || terraformDataDir == "" { - return "plan.tfout" - } - - return fmt.Sprintf("%s.plan.tfout", terraformDataDir) -} - -func vars(vs map[string]string) []string { - var args []string - for k, v := range vs { - args = append(args, "-var", fmt.Sprintf("%s=%s", k, v)) - } - return args -} - -func varFiles(vfs []string) []string { - var args []string - for _, v := range vfs { - args = append(args, fmt.Sprintf("-var-file=%s", v)) - } - return args -} - -// helper function to write a netrc file. -// The following code comes from the official Git plugin for Drone: -// https://github.com/drone-plugins/drone-git/blob/8386effd2fe8c8695cf979427f8e1762bd805192/utils.go#L43-L68 -func writeNetrc(machine, login, password string) error { - if machine == "" { - return nil - } - out := fmt.Sprintf( - netrcFile, - machine, - login, - password, - ) - - home := "/root" - u, err := user.Current() - if err == nil { - home = u.HomeDir - } - path := filepath.Join(home, ".netrc") - return ioutil.WriteFile(path, []byte(out), 0600) -} - -const netrcFile = ` -machine %s -login %s -password %s -` diff --git a/plugin/impl.go b/plugin/impl.go new file mode 100644 index 0000000..fac0175 --- /dev/null +++ b/plugin/impl.go @@ -0,0 +1,112 @@ +package plugin + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/thegeeklab/wp-plugin-go/trace" + "golang.org/x/sys/execabs" +) + +var ( + ErrTaintedPath = errors.New("filepath is tainted") + ErrMaxSizeSizeLimit = errors.New("max size limit of decoded data exceeded") + ErrActionUnknown = errors.New("action not found") + ErrInvalidVersion = errors.New("invalid version string") +) + +const ( + maxDecompressionSize = 1024 + defaultDirPerm = 0o755 +) + +//nolint:revive +func (p *Plugin) run(ctx context.Context) error { + if err := p.Validate(); err != nil { + return fmt.Errorf("validation failed: %w", err) + } + + if err := p.Execute(); err != nil { + return fmt.Errorf("execution failed: %w", err) + } + + return nil +} + +// Validate handles the settings validation of the plugin. +func (p *Plugin) Validate() error { + p.Settings.DataDir = ".terraform" + if value, ok := os.LookupEnv("TF_DATA_DIR"); ok { + p.Settings.DataDir = value + } + + p.Settings.OutFile = "plan.tfout" + if p.Settings.DataDir == ".terraform" { + p.Settings.OutFile = fmt.Sprintf("%s.plan.tfout", p.Settings.DataDir) + } + + if p.Settings.Version != "" { + err := installPackage(p.Plugin.Network.Context, p.Plugin.Network.Client, p.Settings.Version, maxDecompressionSize) + if err != nil { + return err + } + } + + return nil +} + +// Execute provides the implementation of the plugin. +func (p *Plugin) Execute() error { + commands := []*execabs.Cmd{ + p.versionCommand(), + } + + commands = append(commands, p.initCommand()) + commands = append(commands, p.getModulesCommand()) + + for _, action := range p.Settings.Action.Value() { + switch action { + case "fmt": + commands = append(commands, p.fmtCommand()) + case "validate": + commands = append(commands, p.validateCommand()) + case "plan": + commands = append(commands, p.planCommand(false)) + case "plan-destroy": + commands = append(commands, p.planCommand(true)) + case "apply": + commands = append(commands, p.applyCommand()) + case "destroy": + commands = append(commands, p.destroyCommand()) + default: + return fmt.Errorf("%w: %s", ErrActionUnknown, action) + } + } + + if err := deleteCache(p.Settings.DataDir); err != nil { + return err + } + + for _, cmd := range commands { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if p.Settings.RootDir != "" { + cmd.Dir = p.Settings.RootDir + } + + cmd.Env = os.Environ() + + if !p.Settings.NoLog { + trace.Cmd(cmd) + } + + if err := cmd.Run(); err != nil { + return err + } + } + + return deleteCache(p.Settings.DataDir) +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..d1b5a09 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,57 @@ +package plugin + +import ( + wp "github.com/thegeeklab/wp-plugin-go/plugin" + "github.com/urfave/cli/v2" +) + +// Plugin implements provide the plugin. +type Plugin struct { + *wp.Plugin + Settings *Settings +} + +// Settings for the Plugin. +type Settings struct { + Action cli.StringSlice + + Version string + InitOptions InitOptions + FmtOptions FmtOptions + + RootDir string + DataDir string + OutFile string + Parallelism int + Targets cli.StringSlice + Refresh bool + NoLog bool +} + +// InitOptions include options for the OpenTofu init command. +type InitOptions struct { + BackendConfig []string `json:"backend-config"` + Lock *bool `json:"lock"` + LockTimeout string `json:"lock-timeout"` +} + +// FmtOptions fmt options for the OpenTofu fmt command. +type FmtOptions struct { + List *bool `json:"list"` + Write *bool `json:"write"` + Diff *bool `json:"diff"` + Check *bool `json:"check"` +} + +func New(options wp.Options, settings *Settings) *Plugin { + p := &Plugin{} + + if options.Execute == nil { + options.Execute = p.run + } + + p.Plugin = wp.New(options) + p.Settings = settings + + return p +} diff --git a/plugin/tofu.go b/plugin/tofu.go new file mode 100644 index 0000000..f26846d --- /dev/null +++ b/plugin/tofu.go @@ -0,0 +1,180 @@ +package plugin + +import ( + "fmt" + + "golang.org/x/sys/execabs" +) + +const ( + tofuBin = "/usr/local/bin/tofu" +) + +func (p *Plugin) versionCommand() *execabs.Cmd { + args := []string{ + "version", + } + + return execabs.Command( + tofuBin, + args..., + ) +} + +func (p *Plugin) initCommand() *execabs.Cmd { + args := []string{ + "init", + } + + for _, v := range p.Settings.InitOptions.BackendConfig { + args = append(args, fmt.Sprintf("-backend-config=%s", v)) + } + + // Fail tofu execution on prompt + args = append(args, "-input=false") + + return execabs.Command( + tofuBin, + args..., + ) +} + +func (p *Plugin) getModulesCommand() *execabs.Cmd { + return execabs.Command( + tofuBin, + "get", + ) +} + +func (p *Plugin) validateCommand() *execabs.Cmd { + return execabs.Command( + tofuBin, + "validate", + ) +} + +func (p *Plugin) fmtCommand() *execabs.Cmd { + args := []string{ + "fmt", + } + + if p.Settings.FmtOptions.List != nil { + args = append(args, fmt.Sprintf("-list=%t", *p.Settings.FmtOptions.List)) + } + + if p.Settings.FmtOptions.Write != nil { + args = append(args, fmt.Sprintf("-write=%t", *p.Settings.FmtOptions.Write)) + } + + if p.Settings.FmtOptions.Diff != nil { + args = append(args, fmt.Sprintf("-diff=%t", *p.Settings.FmtOptions.Diff)) + } + + if p.Settings.FmtOptions.Check != nil { + args = append(args, fmt.Sprintf("-check=%t", *p.Settings.FmtOptions.Check)) + } + + return execabs.Command( + tofuBin, + args..., + ) +} + +func (p *Plugin) planCommand(destroy bool) *execabs.Cmd { + args := []string{ + "plan", + } + + if destroy { + args = append(args, "-destroy") + } else { + args = append(args, fmt.Sprintf("-out=%s", p.Settings.OutFile)) + } + + for _, value := range p.Settings.Targets.Value() { + args = append(args, "--target", value) + } + + if p.Settings.Parallelism > 0 { + args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism)) + } + + if p.Settings.InitOptions.Lock != nil { + args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock)) + } + + if p.Settings.InitOptions.LockTimeout != "" { + args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout)) + } + + if !p.Settings.Refresh { + args = append(args, "-refresh=false") + } + + return execabs.Command( + tofuBin, + args..., + ) +} + +func (p *Plugin) applyCommand() *execabs.Cmd { + args := []string{ + "apply", + } + + for _, v := range p.Settings.Targets.Value() { + args = append(args, "--target", v) + } + + if p.Settings.Parallelism > 0 { + args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism)) + } + + if p.Settings.InitOptions.Lock != nil { + args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock)) + } + + if p.Settings.InitOptions.LockTimeout != "" { + args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout)) + } + + if !p.Settings.Refresh { + args = append(args, "-refresh=false") + } + + args = append(args, p.Settings.OutFile) + + return execabs.Command( + tofuBin, + args..., + ) +} + +func (p *Plugin) destroyCommand() *execabs.Cmd { + args := []string{ + "destroy", + } + + for _, v := range p.Settings.Targets.Value() { + args = append(args, fmt.Sprintf("-target=%s", v)) + } + + if p.Settings.Parallelism > 0 { + args = append(args, fmt.Sprintf("-parallelism=%d", p.Settings.Parallelism)) + } + + if p.Settings.InitOptions.Lock != nil { + args = append(args, fmt.Sprintf("-lock=%t", *p.Settings.InitOptions.Lock)) + } + + if p.Settings.InitOptions.LockTimeout != "" { + args = append(args, fmt.Sprintf("-lock-timeout=%s", p.Settings.InitOptions.LockTimeout)) + } + + args = append(args, "-auto-approve") + + return execabs.Command( + tofuBin, + args..., + ) +} diff --git a/plugin/utils.go b/plugin/utils.go new file mode 100644 index 0000000..8e8773c --- /dev/null +++ b/plugin/utils.go @@ -0,0 +1,151 @@ +package plugin + +import ( + "archive/zip" + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/Masterminds/semver/v3" +) + +func installPackage(ctx context.Context, client *http.Client, version string, maxSize int64) error { + // Sanitize user input + if _, err := semver.NewVersion(version); err != nil { + return fmt.Errorf("%w: %v", ErrInvalidVersion, version) + } + + err := downloadPackage( + ctx, + client, + "/tmp/tofu.zip", + fmt.Sprintf( + "https://github.com/opentofu/opentofu/releases/download/%s/tofu_%s_linux_amd64.zip", + version, + strings.TrimPrefix(version, ""), + ), + ) + if err != nil { + return err + } + + return unzip("/tmp/tofu.zip", "/bin", maxSize) +} + +func downloadPackage(ctx context.Context, client *http.Client, filepath, url string) error { + // Create the file + out, err := os.Create(filepath) + if err != nil { + return err + } + defer out.Close() + + // Get the data + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return err + } + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + // Writer the body to file + _, err = io.Copy(out, resp.Body) + if err != nil { + return err + } + + return nil +} + +func unzip(src, dest string, maxSize int64) error { + r, err := zip.OpenReader(src) + if err != nil { + return err + } + + defer func() { + if err := r.Close(); err != nil { + panic(err) + } + }() + + _ = os.MkdirAll(dest, defaultDirPerm) + + // Closure to address file descriptors issue with all the deferred .Close() methods + extractAndWriteFile := func(f *zip.File) error { + rc, err := f.Open() + if err != nil { + return err + } + + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() + + path, err := sanitizeArchivePath(dest, f.Name) + if err != nil { + return err + } + + if f.FileInfo().IsDir() { //nolint: nestif + _ = os.MkdirAll(path, f.Mode()) + } else { + _ = os.MkdirAll(filepath.Dir(path), f.Mode()) + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + defer func() { + if err := f.Close(); err != nil { + panic(err) + } + }() + + written, err := io.CopyN(f, rc, maxSize) + if err != nil { + return err + } else if written == maxSize { + return fmt.Errorf("%w: %d", ErrMaxSizeSizeLimit, maxSize) + } + } + + return nil + } + + for _, f := range r.File { + err := extractAndWriteFile(f) + if err != nil { + return err + } + } + + return nil +} + +func sanitizeArchivePath(d, t string) (string, error) { + value := filepath.Join(d, t) + if strings.HasPrefix(value, filepath.Clean(d)) { + return value, nil + } + + return "", fmt.Errorf("%w: %v", ErrTaintedPath, t) +} + +func deleteCache(path string) error { + if _, err := os.Stat(path); os.IsNotExist(err) { + return nil + } + + return os.Remove(path) +} diff --git a/plugin_test.go b/plugin_test.go deleted file mode 100644 index bce7901..0000000 --- a/plugin_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package main - -import ( - "os" - "os/exec" - "testing" - - . "github.com/franela/goblin" -) - -func TestPlugin(t *testing.T) { - g := Goblin(t) - - g.Describe("CopyTfEnv", func() { - g.It("Should create copies of TF_VAR_ to lowercase", func() { - // Set some initial TF_VAR_ that are uppercase - os.Setenv("TF_VAR_SOMETHING", "some value") - os.Setenv("TF_VAR_SOMETHING_ELSE", "some other value") - os.Setenv("TF_VAR_BASE64", "dGVzdA==") - - CopyTfEnv() - - // Make sure new env vars exist with proper values - g.Assert(os.Getenv("TF_VAR_something")).Equal("some value") - g.Assert(os.Getenv("TF_VAR_something_else")).Equal("some other value") - g.Assert(os.Getenv("TF_VAR_base64")).Equal("dGVzdA==") - }) - }) - - g.Describe("credsSet", func() { - tests := []struct { - name string - args map[string]string - want bool - }{ - { - "Should return true when all credentials were set", - map[string]string{"AWS_ACCESS_KEY_ID": "x", "AWS_SECRET_ACCESS_KEY": "x", "AWS_SESSION_TOKEN": "x"}, - true, - }, - { - "Should return false when access key id is missing", - map[string]string{"AWS_SECRET_ACCESS_KEY": "x", "AWS_SESSION_TOKEN": "x"}, - false, - }, - { - "Should return false when secret access key is missing", - map[string]string{"AWS_ACCESS_KEY_ID": "x", "AWS_SESSION_TOKEN": "x"}, - false, - }, - { - "Should return false when session token is missing", - map[string]string{"AWS_ACCESS_KEY_ID": "x", "AWS_SECRET_ACCESS_KEY": "x"}, - false, - }, - } - - for _, tt := range tests { - g.It(tt.name, func() { - for k, v := range tt.args { - os.Setenv(k, v) - } - g.Assert(credsSet()).Equal(tt.want) - }) - } - }) - - g.Describe("tfApply", func() { - g.It("Should return correct apply commands given the arguments", func() { - type args struct { - config Config - } - - tests := []struct { - name string - args args - want *exec.Cmd - }{ - { - "default", - args{config: Config{}}, - exec.Command("terraform", "apply", "plan.tfout"), - }, - { - "with parallelism", - args{config: Config{Parallelism: 5}}, - exec.Command("terraform", "apply", "-parallelism=5", "plan.tfout"), - }, - { - "with targets", - args{config: Config{Targets: []string{"target1", "target2"}}}, - exec.Command("terraform", "apply", "--target", "target1", "--target", "target2", "plan.tfout"), - }, - } - - for _, tt := range tests { - g.Assert(tfApply(tt.args.config).Args).Equal(tt.want.Args) - } - }) - }) - - g.Describe("tfDestroy", func() { - g.It("Should return correct destroy commands given the arguments", func() { - type args struct { - config Config - } - - tests := []struct { - name string - args args - want *exec.Cmd - }{ - { - "default", - args{config: Config{}}, - exec.Command("terraform", "destroy", "-auto-approve"), - }, - { - "with parallelism", - args{config: Config{Parallelism: 5}}, - exec.Command("terraform", "destroy", "-parallelism=5", "-auto-approve"), - }, - { - "with targets", - args{config: Config{Targets: []string{"target1", "target2"}}}, - exec.Command("terraform", "destroy", "-target=target1", "-target=target2", "-auto-approve"), - }, - { - "with vars", - args{config: Config{Vars: map[string]string{"username": "someuser", "password": "1pass"}}}, - exec.Command("terraform", "destroy", "-var", "username=someuser", "-var", "password=1pass", "-auto-approve"), - }, - { - "with var-files", - args{config: Config{VarFiles: []string{"common.tfvars", "prod.tfvars"}}}, - exec.Command("terraform", "destroy", "-var-file=common.tfvars", "-var-file=prod.tfvars", "-auto-approve"), - }, - } - - for _, tt := range tests { - g.Assert(tfDestroy(tt.args.config)).Equal(tt.want) - } - }) - }) - - g.Describe("tfValidate", func() { - g.It("Should return correct validate command", func() { - tests := []struct { - name string - want *exec.Cmd - }{ - { - "default", - exec.Command("terraform", "validate"), - }, - } - - for _, tt := range tests { - g.Assert(tfValidate()).Equal(tt.want) - } - }) - }) - - g.Describe("tfPlan", func() { - g.It("Should return correct plan commands given the arguments", func() { - type args struct { - config Config - } - - tests := []struct { - name string - args args - destroy bool - want *exec.Cmd - }{ - { - "default", - args{config: Config{}}, - false, - exec.Command("terraform", "plan", "-out=plan.tfout"), - }, - { - "destroy", - args{config: Config{}}, - true, - exec.Command("terraform", "plan", "-destroy"), - }, - { - "with vars", - args{config: Config{Vars: map[string]string{"username": "someuser", "password": "1pass"}}}, - false, - exec.Command("terraform", "plan", "-out=plan.tfout", "-var", "username=someuser", "-var", "password=1pass"), - }, - { - "with var-files", - args{config: Config{VarFiles: []string{"common.tfvars", "prod.tfvars"}}}, - false, - exec.Command("terraform", "plan", "-out=plan.tfout", "-var-file=common.tfvars", "-var-file=prod.tfvars"), - }, - } - - for _, tt := range tests { - g.Assert(tfPlan(tt.args.config, tt.destroy)).Equal(tt.want) - } - }) - }) - g.Describe("tfFmt", func() { - g.It("Should return correct fmt commands given the arguments", func() { - type args struct { - config Config - } - - affirmative := true - negative := false - - tests := []struct { - name string - args args - want *exec.Cmd - }{ - { - "default", - args{config: Config{}}, - exec.Command("terraform", "fmt"), - }, - { - "with list", - args{config: Config{FmtOptions: FmtOptions{List: &affirmative}}}, - exec.Command("terraform", "fmt", "-list=true"), - }, - { - "with write", - args{config: Config{FmtOptions: FmtOptions{Write: &affirmative}}}, - exec.Command("terraform", "fmt", "-write=true"), - }, - { - "with diff", - args{config: Config{FmtOptions: FmtOptions{Diff: &affirmative}}}, - exec.Command("terraform", "fmt", "-diff=true"), - }, - { - "with check", - args{config: Config{FmtOptions: FmtOptions{Check: &affirmative}}}, - exec.Command("terraform", "fmt", "-check=true"), - }, - { - "with combination", - args{config: Config{FmtOptions: FmtOptions{ - List: &negative, - Write: &negative, - Diff: &affirmative, - Check: &affirmative, - }}}, - exec.Command("terraform", "fmt", "-list=false", "-write=false", "-diff=true", "-check=true"), - }, - } - - for _, tt := range tests { - g.Assert(tfFmt(tt.args.config)).Equal(tt.want) - } - }) - }) - - g.Describe("tfDataDir", func() { - g.It("Should override the terraform data dir environment variable when provided", func() { - type args struct { - config Config - } - - tests := []struct { - name string - args args - want *exec.Cmd - }{ - { - "with TerraformDataDir", - args{config: Config{TerraformDataDir: ".overriden_terraform_dir"}}, - exec.Command("terraform", "apply", ".overriden_terraform_dir.plan.tfout"), - }, - { - "with TerraformDataDir value as .terraform", - args{config: Config{TerraformDataDir: ".terraform"}}, - exec.Command("terraform", "apply", "plan.tfout"), - }, - { - "without TerraformDataDir", - args{config: Config{}}, - exec.Command("terraform", "apply", "plan.tfout"), - }, - } - - for _, tt := range tests { - os.Setenv("TF_DATA_DIR", tt.args.config.TerraformDataDir) - applied := tfApply(tt.args.config) - - g.Assert(applied).Equal(tt.want) - - } - }) - }) -} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..45d1c03 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>thegeeklab/renovate-presets:golang"] +} diff --git a/terraform.go b/terraform.go deleted file mode 100644 index 519fced..0000000 --- a/terraform.go +++ /dev/null @@ -1,114 +0,0 @@ -package main - -import ( - "archive/zip" - "fmt" - "io" - "net/http" - "os" - "path/filepath" -) - -type ( - // Terraform holds input parameters for terraform - Terraform struct { - Version string - } -) - -func installTerraform(version string) error { - err := downloadTerraform(version) - if err != nil { - return nil - } - - return Unzip("/var/tmp/terraform.zip", "/bin") -} - -func downloadTerraform(version string) error { - return downloadFile("/var/tmp/terraform.zip", fmt.Sprintf("https://releases.hashicorp.com/terraform/%s/terraform_%s_linux_amd64.zip", version, version)) -} - -func downloadFile(filepath string, url string) error { - // Create the file - out, err := os.Create(filepath) - if err != nil { - return err - } - defer out.Close() - - // Get the data - resp, err := http.Get(url) - if err != nil { - return err - } - defer resp.Body.Close() - - // Writer the body to file - _, err = io.Copy(out, resp.Body) - if err != nil { - return err - } - - return nil -} - -// Unzip a file to a destination -func Unzip(src, dest string) error { - r, err := zip.OpenReader(src) - if err != nil { - return err - } - defer func() { - if err := r.Close(); err != nil { - panic(err) - } - }() - - os.MkdirAll(dest, 0755) - - // Closure to address file descriptors issue with all the deferred .Close() methods - extractAndWriteFile := func(f *zip.File) error { - rc, err := f.Open() - if err != nil { - return err - } - defer func() { - if err := rc.Close(); err != nil { - panic(err) - } - }() - - path := filepath.Join(dest, f.Name) - - if f.FileInfo().IsDir() { - os.MkdirAll(path, f.Mode()) - } else { - os.MkdirAll(filepath.Dir(path), f.Mode()) - f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - defer func() { - if err := f.Close(); err != nil { - panic(err) - } - }() - - _, err = io.Copy(f, rc) - if err != nil { - return err - } - } - return nil - } - - for _, f := range r.File { - err := extractAndWriteFile(f) - if err != nil { - return err - } - } - - return nil -}