diff --git a/.copywrite.hcl b/.copywrite.hcl deleted file mode 100644 index bdf3892..0000000 --- a/.copywrite.hcl +++ /dev/null @@ -1,21 +0,0 @@ -# NOTE: This file is for HashiCorp specific licensing automation and can be deleted after creating a new repo with this template. -schema_version = 1 - -project { - license = "MPL-2.0" - copyright_year = 2021 - - header_ignore = [ - # examples used within documentation (prose) - "examples/**", - - # GitHub issue template configuration - ".github/ISSUE_TEMPLATE/*.yml", - - # golangci-lint tooling configuration - ".golangci.yml", - - # GoReleaser tooling configuration - ".goreleaser.yml", - ] -} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..cdcd4a8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 160 + +[*.tf] +max_line_length = off +indent_size = 2 + +[Makefile] +indent_style = tab + +[*.go] +indent_size = tab +indent_style = tab +ij_go_group_stdlib_imports = true +ij_go_import_sorting = goimports +ij_go_move_all_imports_in_one_declaration = true +ij_go_move_all_stdlib_imports_in_one_group = true +ij_go_remove_redundant_import_aliases = true +ij_go_run_go_fmt_on_reformat = true + +[*.{yaml,yml}] +indent_size = 2 \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 922ee27..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @hashicorp/terraform-devex diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index 0c8b092..0000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,5 +0,0 @@ -# Code of Conduct - -HashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code. - -Please read the full text at https://www.hashicorp.com/community-guidelines diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 73bb4d3..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,15 +0,0 @@ -# See GitHub's documentation for more information on this file: -# https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates -version: 2 -updates: - - package-ecosystem: "gomod" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - # TODO: Dependabot only updates hashicorp GHAs in the template repository, the following lines can be removed for consumers of this template - allow: - - dependency-name: "hashicorp/*" diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..143e44f --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,28 @@ +changelog: + categories: + - title: 🚨 Breaking Changes + labels: + - Kind/Breaking + - title: ✨ Features + labels: + - Kind/Feature + - title: 🐛 Bug Fixes and Security + labels: + - Kind/Bug + - Kind/Security + - title: 🌟 Improvements + labels: + - Kind/Enhancement + - title: 📦 Dependencies + labels: + - Kind/Dependency + - title: 📚 Miscellaneous + labels: + - "*" + exclude_labels: + - Kind/Breaking + - Kind/Feature + - Kind/Bug + - Kind/Security + - Kind/Enhancement + - Kind/Dependency diff --git a/.github/workflows/issue-comment-triage.yml b/.github/workflows/issue-comment-triage.yml deleted file mode 100644 index 00017cd..0000000 --- a/.github/workflows/issue-comment-triage.yml +++ /dev/null @@ -1,21 +0,0 @@ -# DO NOT EDIT - This GitHub Workflow is managed by automation -# https://github.com/hashicorp/terraform-devex-repos -name: Issue Comment Triage - -on: - issue_comment: - types: [created] - -jobs: - issue_comment_triage: - runs-on: ubuntu-latest - env: - # issue_comment events are triggered by comments on issues and pull requests. Checking the - # value of github.event.issue.pull_request tells us whether the issue is an issue or is - # actually a pull request, allowing us to dynamically set the gh subcommand: - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only - COMMAND: ${{ github.event.issue.pull_request && 'pr' || 'issue' }} - GH_TOKEN: ${{ github.token }} - steps: - - name: 'Remove waiting-response on comment' - run: gh ${{ env.COMMAND }} edit ${{ github.event.issue.html_url }} --remove-label waiting-response diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml deleted file mode 100644 index 358aed1..0000000 --- a/.github/workflows/lock.yml +++ /dev/null @@ -1,21 +0,0 @@ -# DO NOT EDIT - This GitHub Workflow is managed by automation -# https://github.com/hashicorp/terraform-devex-repos -name: 'Lock Threads' - -on: - schedule: - - cron: '43 20 * * *' - -jobs: - lock: - runs-on: ubuntu-latest - steps: - # NOTE: When TSCCR updates the GitHub action version, update the template workflow file to avoid drift: - # https://github.com/hashicorp/terraform-devex-repos/blob/main/modules/repo/workflows/lock.tftpl - - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 - with: - github-token: ${{ github.token }} - issue-inactive-days: '30' - issue-lock-reason: resolved - pr-inactive-days: '30' - pr-lock-reason: resolved diff --git a/.github/workflows/release.yml b/.github/workflows/release.yaml similarity index 89% rename from .github/workflows/release.yml rename to .github/workflows/release.yaml index f823f07..ce31027 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yaml @@ -32,8 +32,10 @@ jobs: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} passphrase: ${{ secrets.PASSPHRASE }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 with: + distribution: goreleaser + version: "~> v2" args: release --clean env: # GitHub sets the GITHUB_TOKEN secret automatically. diff --git a/.github/workflows/spelling.yaml b/.github/workflows/spelling.yaml new file mode 100644 index 0000000..f8f9207 --- /dev/null +++ b/.github/workflows/spelling.yaml @@ -0,0 +1,23 @@ +name: Spell checking + +# Trigger on pull requests, and pushes to master branch. +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4 + - uses: codespell-project/actions-codespell@master + with: + check_filenames: true + # When using this Action in other repos, the --skip option below can be removed + skip: ./.git,go.mod,go.sum + ignore_words_list: AtLeast,AtMost diff --git a/.github/workflows/test.yml b/.github/workflows/test.yaml similarity index 85% rename from .github/workflows/test.yml rename to .github/workflows/test.yaml index 32a6c7e..c4998b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yaml @@ -4,10 +4,15 @@ name: Tests # This GitHub action runs your tests for each pull request and push. # Optionally, you can turn it on using a schedule for regular testing. on: - pull_request: + workflow_dispatch: + + push: + branches: + - main paths-ignore: - 'README.md' - push: + + pull_request: paths-ignore: - 'README.md' @@ -29,11 +34,14 @@ jobs: cache: true - run: go mod download - run: go build -v . + - run: go test -v ./... - name: Run linters uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 with: version: latest + - run: go run github.com/bflad/tfproviderlint/cmd/tfproviderlintx@latest ./... + generate: runs-on: ubuntu-latest steps: @@ -42,22 +50,25 @@ jobs: with: go-version-file: 'go.mod' cache: true - # Temporarily download Terraform 1.8 prerelease for function documentation support. - # When Terraform 1.8.0 final is released, this can be removed. - uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1 with: - terraform_version: '1.8.0-alpha20240216' terraform_wrapper: false + - run: go mod tidy + - name: git diff + run: | + git diff --compact-summary --exit-code || \ + (echo; echo "Run 'go mod tidy' command and commit."; git diff; exit 1) - run: go generate ./... - name: git diff run: | git diff --compact-summary --exit-code || \ - (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) + (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; git diff; exit 1) # Run acceptance tests in a matrix with Terraform CLI versions test: name: Terraform Provider Acceptance Tests needs: build + concurrency: acctests runs-on: ubuntu-latest timeout-minutes: 15 strategy: @@ -65,11 +76,7 @@ jobs: matrix: # list whatever Terraform versions here you would like to support terraform: - - '1.0.*' - - '1.1.*' - - '1.2.*' - - '1.3.*' - - '1.4.*' + - '1.8.*' steps: - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 diff --git a/.gitignore b/.gitignore index fd3ad8e..a14b43e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ *.dll *.exe .DS_Store -example.tf terraform.tfplan terraform.tfstate bin/ @@ -23,7 +22,6 @@ website/node_modules .idea *.iml *.test -*.iml website/vendor diff --git a/.golangci.yml b/.golangci.yml index 223cf95..904b175 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,27 +1,74 @@ +# yaml-language-server: $schema: https://golangci-lint.run/jsonschema/golangci.jsonschema.json + # Visit https://golangci-lint.run/ for usage documentation # and information on other useful linters issues: - max-per-linter: 0 + max-issues-per-linter: 0 max-same-issues: 0 + exclude-rules: + - path: _test\.go + linters: + - funlen + - dupword linters: disable-all: true enable: + - asciicheck + - contextcheck - durationcheck + - dupword - errcheck - exportloopref - forcetypeassert + - forbidigo + - gci + - gochecknoglobals + - gocheckcompilerdirectives + - gochecknoinits + - gocognit + - gocritic - godot - gofmt + - gofumpt + - goimports + - gomoddirectives - gosimple + - gosec + - govet - ineffassign + - lll - makezero - misspell + - mirror - nilerr + - nlreturn + - noctx + - nolintlint + - nonamedreturns + - prealloc + - perfsprint - predeclared - staticcheck + - tagalign + - tagliatelle - tenv + - testifylint - unconvert - unparam - unused - - vet \ No newline at end of file + - wastedassign + - wsl + - wrapcheck + +linters-settings: + lll: + line-length: 160 + tab-width: 4 + + tagliatelle: + case: + use-field-name: true + rules: + json: snake + tfsdk: snake diff --git a/.goreleaser.yml b/.goreleaser.yml index 7178be5..88b5fb7 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,37 +1,40 @@ -# Visit https://goreleaser.com for documentation on how to customize this -# behavior. +# yaml-language-server: $schema: https://goreleaser.com/static/schema.json + +# Visit https://goreleaser.com for documentation on how to customize this behavior. +version: 2 + before: hooks: # this is just an example and not a requirement for provider building/publishing - go mod tidy builds: -- env: - # goreleaser does not work with CGO, it could also complicate - # usage by users in CI/CD systems like HCP Terraform where - # they are unable to install libraries. - - CGO_ENABLED=0 - mod_timestamp: '{{ .CommitTimestamp }}' - flags: - - -trimpath - ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' - goos: - - freebsd - - windows - - linux - - darwin - goarch: - - amd64 - - '386' - - arm - - arm64 - ignore: - - goos: darwin - goarch: '386' - binary: '{{ .ProjectName }}_v{{ .Version }}' + - env: + # goreleaser does not work with CGO, it could also complicate + # usage by users in CI/CD systems like Terraform Cloud where + # they are unable to install libraries. + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + - goos: darwin + goarch: '386' + binary: '{{ .ProjectName }}_v{{ .Version }}' archives: -- format: zip - name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' checksum: extra_files: - glob: 'terraform-registry-manifest.json' @@ -41,7 +44,7 @@ checksum: signs: - artifacts: checksum args: - # if you are using this in a GitHub action or some other automated pipeline, you + # if you are using this in a GitHub action or some other automated pipeline, you # need to pass the batch flag to indicate its not interactive. - "--batch" - "--local-user" @@ -57,4 +60,4 @@ release: # If you want to manually examine the release before its live, uncomment this line: # draft: true changelog: - skip: true + disable: true diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index b76e247..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.1.0 (Unreleased) - -FEATURES: diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 7771cd6..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,6 +0,0 @@ -default: testacc - -# Run acceptance tests -.PHONY: testacc -testacc: - TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m diff --git a/LICENSE b/LICENSE index 15eba9d..a612ad9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,3 @@ -Copyright (c) 2021 HashiCorp, Inc. - Mozilla Public License Version 2.0 ================================== diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73b9bad --- /dev/null +++ b/Makefile @@ -0,0 +1,30 @@ +BINARY_DIR=bin +BINARY_NAME=terraform-provider-dotenv + +.PHONY: build testacc test lint generate fmt + +build: + mkdir -p $(BINARY_DIR) + go build -o $(BINARY_DIR)/$(BINARY_NAME) + +testacc: + TF_ACC=1 go test -v ./internal/provider -timeout 5m + +test: + go test -v ./... -timeout 5m + +lint: + golangci-lint run ./... + go run github.com/bflad/tfproviderlint/cmd/tfproviderlintx@latest ./... + +generate: + go generate ./... + +fmt: + go fmt ./... + -go run mvdan.cc/gofumpt@latest -l -w . + -go run golang.org/x/tools/cmd/goimports@latest -l -w . + -go run github.com/bombsimon/wsl/v4/cmd...@latest -strict-append -test=true -fix ./... + -go run github.com/catenacyber/perfsprint@latest -fix ./... + -go run github.com/bflad/tfproviderlint/cmd/tfproviderlintx@latest -fix ./... + diff --git a/README.md b/README.md index 9759acd..63900d0 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,51 @@ -# Terraform Provider Scaffolding (Terraform Plugin Framework) +# Terraform Provider DotEnv -_This template repository is built on the [Terraform Plugin Framework](https://github.com/hashicorp/terraform-plugin-framework). The template repository built on the [Terraform Plugin SDK](https://github.com/hashicorp/terraform-plugin-sdk) can be found at [terraform-provider-scaffolding](https://github.com/hashicorp/terraform-provider-scaffolding). See [Which SDK Should I Use?](https://developer.hashicorp.com/terraform/plugin/framework-benefits) in the Terraform documentation for additional information._ +[![Terraform](https://img.shields.io/badge/Terraform-844FBA.svg?style=for-the-badge&logo=Terraform&logoColor=white)](https://registry.terraform.io/providers/germanbrew/dotenv/latest) +[![OpenTofu](https://img.shields.io/badge/OpenTofu-FFDA18.svg?style=for-the-badge&logo=OpenTofu&logoColor=black)](https://github.com/opentofu/registry/blob/main/providers/g/germanbrew/dotenv.json) +[![GitHub Release](https://img.shields.io/github/v/release/germanbrew/terraform-provider-dotenv?sort=date&display_name=release&style=for-the-badge&logo=github&link=https%3A%2F%2Fgithub.com%2Fgermanbrew%2Fterraform-provider-dotenv%2Freleases%2Flatest)](https://github.com/germanbrew/terraform-provider-dotenv/releases/latest) +[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/germanbrew/terraform-provider-dotenv/test.yaml?branch=main&style=for-the-badge&logo=github&label=Tests&link=https%3A%2F%2Fgithub.com%2Fgermanbrew%2Fterraform-provider-dotenv%2Factions%2Fworkflows%2Ftest.yaml)](https://github.com/germanbrew/terraform-provider-dotenv/actions/workflows/test.yaml) -This repository is a *template* for a [Terraform](https://www.terraform.io) provider. It is intended as a starting point for creating Terraform providers, containing: +A utility Terraform provider for dotfiles -- A resource and a data source (`internal/provider/`), -- Examples (`examples/`) and generated documentation (`docs/`), -- Miscellaneous meta files. +## Requirements -These files contain boilerplate code that you will need to edit to create your own Terraform provider. Tutorials for creating Terraform providers can be found on the [HashiCorp Developer](https://developer.hashicorp.com/terraform/tutorials/providers-plugin-framework) platform. _Terraform Plugin Framework specific guides are titled accordingly._ +- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.0 -Please see the [GitHub template repository documentation](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for how to create a new repository from this template on GitHub. +## Installing and Using this Plugin -Once you've written your provider, you'll want to [publish it on the Terraform Registry](https://developer.hashicorp.com/terraform/registry/providers/publishing) so that others can use it. +You most likely want to download the provider from [Terraform Registry](https://registry.terraform.io/providers/germanbrew/dotenv/latest/docs). +The provider is also published in the [OpenTofu Registry](https://github.com/opentofu/registry/tree/main/providers/g/germanbrew). -## Requirements +Using Provider from Terraform Registry (TF >= 1.0) +This provider is published and available there. If you want to use it, just add the following to your terraform.tf: -- [Terraform](https://developer.hashicorp.com/terraform/downloads) >= 1.0 -- [Go](https://golang.org/doc/install) >= 1.21 +```terraform +terraform { + required_providers { + dotenv = { + source = "germanbrew/dotenv" + version = "1.0.0" # Adjust to latest version + } + } + required_version = ">= 1.0" +} +``` + +Then run terraform init to download the provider. + +## Development + +### Requirements + +- [Go](https://golang.org/) 1.21 (to build the provider plugin) +- [golangci-lint](https://github.com/golangci/golangci-lint) (to lint code) +- [terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) (to generate registry documentation) + +### Makefile Commands + +Check the subcommands in our [Makefile](Makefile) for useful dev tools and scripts. -## Building The Provider +### Building The Provider 1. Clone the repository 1. Enter the repository directory @@ -29,7 +55,7 @@ Once you've written your provider, you'll want to [publish it on the Terraform R go install ``` -## Adding Dependencies +### Adding Dependencies This provider uses [Go modules](https://github.com/golang/go/wiki/Modules). Please see the Go documentation for the most up to date information about using Go modules. @@ -43,11 +69,7 @@ go mod tidy Then commit the changes to `go.mod` and `go.sum`. -## Using the provider - -Fill this in for each provider - -## Developing the Provider +### Developing the Provider If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above). @@ -62,3 +84,39 @@ In order to run the full suite of Acceptance tests, run `make testacc`. ```shell make testacc ``` + +### Testing the provider locally + +To test the provider locally: + +1. Build the provider binary with `make build` +2. Create a new file `~/.terraform.rc` and point the provider to the absolute **directory** path of the binary file: + ```hcl + provider_installation { + dev_overrides { + "germanbrew/dotenv" = "/path/to/your/terraform-provider-dotenv/bin/" + } + direct {} + } + ``` +3. - Set the variable before running terraform commands: + +```sh +TF_CLI_CONFIG_FILE=~/.terraform.rc terraform plan +``` + + - Or set the env variable `TF_CLI_CONFIG_FILE` and point it to `~/.terraform.rc`: e.g. + +```sh +export TF_CLI_CONFIG_FILE=~/.terraform.rc +``` + +4. Now you can just use terraform normally. A warning will appear, that notifies you that you are using an provider override + ``` + Warning: Provider development overrides are in effect + ... + ``` +5. Unset the env variable if you don't want to use the local provider anymore: + ```sh + unset TF_CLI_CONFIG_FILE + ``` diff --git a/docs/data-sources/dotenv.md b/docs/data-sources/dotenv.md new file mode 100644 index 0000000..dd1e584 --- /dev/null +++ b/docs/data-sources/dotenv.md @@ -0,0 +1,24 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "dotenv Data Source - dotenv" +subcategory: "" +description: |- + Reads and provides all entries of a dotenv file. If you only need a specific value and/or do not want to store the contents of the file in the state, you can use the get_by_key provider function. +--- + +# dotenv (Data Source) + +Reads and provides all entries of a dotenv file. If you only need a specific value and/or do not want to store the contents of the file in the state, you can use the `get_by_key` provider function. + + + + +## Schema + +### Optional + +- `filename` (String) `Default: .env` Path to the dotenv file + +### Read-Only + +- `entries` (Map of String) Key-Value entries of the dotenv file. The values are by default considered nonsensitive. If you want to hide the values in any output as sensitive contents, you can use the [`sensitive()`](https://developer.hashicorp.com/terraform/language/functions/sensitive) function. diff --git a/docs/data-sources/example.md b/docs/data-sources/example.md deleted file mode 100644 index b19c8a0..0000000 --- a/docs/data-sources/example.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Data Source - scaffolding" -subcategory: "" -description: |- - Example data source ---- - -# scaffolding_example (Data Source) - -Example data source - -## Example Usage - -```terraform -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` - - -## Schema - -### Optional - -- `configurable_attribute` (String) Example configurable attribute - -### Read-Only - -- `id` (String) Example identifier diff --git a/docs/functions/example.md b/docs/functions/example.md deleted file mode 100644 index c65087d..0000000 --- a/docs/functions/example.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "example function - scaffolding" -subcategory: "" -description: |- - Example function ---- - -# function: example - -Echoes given argument as result - - - -## Signature - - -```text -example(input string) string -``` - -## Arguments - - -1. `input` (String) String to echo - diff --git a/docs/functions/get_by_key.md b/docs/functions/get_by_key.md new file mode 100644 index 0000000..b13f746 --- /dev/null +++ b/docs/functions/get_by_key.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "get_by_key function - dotenv" +subcategory: "" +description: |- + Get by key function +--- + +# function: get_by_key + +Reads and provides a single entry of a dotfile by its key + +## Example Usage + +```terraform +resource "some_ressource" "example" { + value = provider::dotenv::get_by_key("SECRET_KEY", ".env") +} + +output "addition" { + value = provider::dotenv::get_by_key("EXAMPLE_INT", "./testdata/test.env") + 50 +} +``` + +## Signature + + +```text +get_by_key(key string, filename string) string +``` + +## Arguments + + +1. `key` (String) Name of the key +1. `filename` (String) Path to the dotfile + diff --git a/docs/index.md b/docs/index.md index 7458d6d..bee7e0a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,26 +1,28 @@ --- # generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding Provider" +page_title: "dotenv Provider" subcategory: "" description: |- - + A utility Terraform provider for dotfiles. --- -# scaffolding Provider - +# dotenv Provider +A utility Terraform provider for dotfiles. ## Example Usage ```terraform -provider "scaffolding" { - # example configuration here +provider "dotenv" {} + +data "dotenv" "app" { + filename = "./application/app.env" +} + +provider "some_provider" { + token = data.dotenv.app.entries.SECRET_KEY } ``` ## Schema - -### Optional - -- `endpoint` (String) Example provider attribute diff --git a/docs/resources/example.md b/docs/resources/example.md deleted file mode 100644 index 5f3d5ca..0000000 --- a/docs/resources/example.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "scaffolding_example Resource - scaffolding" -subcategory: "" -description: |- - Example resource ---- - -# scaffolding_example (Resource) - -Example resource - -## Example Usage - -```terraform -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} -``` - - -## Schema - -### Optional - -- `configurable_attribute` (String) Example configurable attribute -- `defaulted` (String) Example configurable attribute with default value - -### Read-Only - -- `id` (String) Example identifier diff --git a/examples/data-sources/dotenv_file/data-source.tf b/examples/data-sources/dotenv_file/data-source.tf new file mode 100644 index 0000000..92efce5 --- /dev/null +++ b/examples/data-sources/dotenv_file/data-source.tf @@ -0,0 +1,16 @@ +# With specific dotfile path and without storing the contents to the TF state +data "dotenv" "database" { + filename = "./database.env" + keep_local = true +} + +output "foo" { + value = data.dotenv.database.PASSWORD +} + +# Use the default .env in current directory +data "dotenv" "local" {} + +output "local" { + value = data.dotenv.local.EXAMPLE_KEY +} diff --git a/examples/data-sources/scaffolding_example/data-source.tf b/examples/data-sources/scaffolding_example/data-source.tf deleted file mode 100644 index a852489..0000000 --- a/examples/data-sources/scaffolding_example/data-source.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/examples/functions/get_by_key/function.tf b/examples/functions/get_by_key/function.tf new file mode 100644 index 0000000..2625e0a --- /dev/null +++ b/examples/functions/get_by_key/function.tf @@ -0,0 +1,7 @@ +resource "some_ressource" "example" { + value = provider::dotenv::get_by_key("SECRET_KEY", ".env") +} + +output "addition" { + value = provider::dotenv::get_by_key("EXAMPLE_INT", "./testdata/test.env") + 50 +} diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 942db45..177bf83 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,9 @@ -provider "scaffolding" { - # example configuration here +provider "dotenv" {} + +data "dotenv" "app" { + filename = "./application/app.env" +} + +provider "some_provider" { + token = data.dotenv.app.entries.SECRET_KEY } diff --git a/examples/resources/scaffolding_example/resource.tf b/examples/resources/scaffolding_example/resource.tf deleted file mode 100644 index 9ae3f57..0000000 --- a/examples/resources/scaffolding_example/resource.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/go.mod b/go.mod index 490da16..8086ca2 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,19 @@ -module github.com/hashicorp/terraform-provider-scaffolding-framework +module github.com/germanbrew/terraform-provider-dotenv go 1.21 require ( + github.com/direnv/direnv/v2 v2.34.0 github.com/hashicorp/terraform-plugin-docs v0.19.4 github.com/hashicorp/terraform-plugin-framework v1.9.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.8.0 ) require ( - github.com/BurntSushi/toml v1.2.1 // indirect + github.com/BurntSushi/toml v1.3.2 // indirect github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect diff --git a/go.sum b/go.sum index cc7625f..299d4f3 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -34,6 +34,8 @@ github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxG 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/direnv/direnv/v2 v2.34.0 h1:ptc4r+qVnBwG0MPCP/G/YYEoUZ88bIrZX5izKYliAwM= +github.com/direnv/direnv/v2 v2.34.0/go.mod h1:4kFfMgIQpxwJSoF4QENbcCZQeUO0gRegOu3aD+yyrRA= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= @@ -101,6 +103,8 @@ github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSey github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= github.com/hashicorp/terraform-plugin-framework v1.9.0 h1:caLcDoxiRucNi2hk8+j3kJwkKfvHznubyFsJMWfZqKU= github.com/hashicorp/terraform-plugin-framework v1.9.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 h1:HOjBuMbOEzl7snOdOoUfE2Jgeto6JOjLVQ39Ls2nksc= +github.com/hashicorp/terraform-plugin-framework-validators v0.12.0/go.mod h1:jfHGE/gzjxYz6XoUwi/aYiiKrJDeutQNUtGQXkaHklg= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= diff --git a/internal/provider/example_data_source.go b/internal/provider/example_data_source.go deleted file mode 100644 index 585b9d2..0000000 --- a/internal/provider/example_data_source.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "fmt" - "net/http" - - "github.com/hashicorp/terraform-plugin-framework/datasource" - "github.com/hashicorp/terraform-plugin-framework/datasource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -// Ensure provider defined types fully satisfy framework interfaces. -var _ datasource.DataSource = &ExampleDataSource{} - -func NewExampleDataSource() datasource.DataSource { - return &ExampleDataSource{} -} - -// ExampleDataSource defines the data source implementation. -type ExampleDataSource struct { - client *http.Client -} - -// ExampleDataSourceModel describes the data source data model. -type ExampleDataSourceModel struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Id types.String `tfsdk:"id"` -} - -func (d *ExampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_example" -} - -func (d *ExampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Example data source", - - Attributes: map[string]schema.Attribute{ - "configurable_attribute": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute", - Optional: true, - }, - "id": schema.StringAttribute{ - MarkdownDescription: "Example identifier", - Computed: true, - }, - }, - } -} - -func (d *ExampleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*http.Client) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - d.client = client -} - -func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { - var data ExampleDataSourceModel - - // Read Terraform configuration data into the model - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := d.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) - // return - // } - - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "read a data source") - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} diff --git a/internal/provider/example_function.go b/internal/provider/example_function.go deleted file mode 100644 index 1106e7d..0000000 --- a/internal/provider/example_function.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - - "github.com/hashicorp/terraform-plugin-framework/function" -) - -var ( - _ function.Function = ExampleFunction{} -) - -func NewExampleFunction() function.Function { - return ExampleFunction{} -} - -type ExampleFunction struct{} - -func (r ExampleFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "example" -} - -func (r ExampleFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Summary: "Example function", - MarkdownDescription: "Echoes given argument as result", - Parameters: []function.Parameter{ - function.StringParameter{ - Name: "input", - MarkdownDescription: "String to echo", - }, - }, - Return: function.StringReturn{}, - } -} - -func (r ExampleFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { - var data string - - resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &data)) - - if resp.Error != nil { - return - } - - resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, data)) -} diff --git a/internal/provider/example_resource.go b/internal/provider/example_resource.go deleted file mode 100644 index 70e961a..0000000 --- a/internal/provider/example_resource.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "context" - "fmt" - "net/http" - - "github.com/hashicorp/terraform-plugin-framework/path" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-log/tflog" -) - -// Ensure provider defined types fully satisfy framework interfaces. -var _ resource.Resource = &ExampleResource{} -var _ resource.ResourceWithImportState = &ExampleResource{} - -func NewExampleResource() resource.Resource { - return &ExampleResource{} -} - -// ExampleResource defines the resource implementation. -type ExampleResource struct { - client *http.Client -} - -// ExampleResourceModel describes the resource data model. -type ExampleResourceModel struct { - ConfigurableAttribute types.String `tfsdk:"configurable_attribute"` - Defaulted types.String `tfsdk:"defaulted"` - Id types.String `tfsdk:"id"` -} - -func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_example" -} - -func (r *ExampleResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - // This description is used by the documentation generator and the language server. - MarkdownDescription: "Example resource", - - Attributes: map[string]schema.Attribute{ - "configurable_attribute": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute", - Optional: true, - }, - "defaulted": schema.StringAttribute{ - MarkdownDescription: "Example configurable attribute with default value", - Optional: true, - Computed: true, - Default: stringdefault.StaticString("example value when not configured"), - }, - "id": schema.StringAttribute{ - Computed: true, - MarkdownDescription: "Example identifier", - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - }, - } -} - -func (r *ExampleResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Prevent panic if the provider has not been configured. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*http.Client) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Resource Configure Type", - fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - - return - } - - r.client = client -} - -func (r *ExampleResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var data ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create example, got error: %s", err)) - // return - // } - - // For the purposes of this example code, hardcoding a response value to - // save into the Terraform state. - data.Id = types.StringValue("example-id") - - // Write logs using the tflog package - // Documentation: https://terraform.io/plugin/log - tflog.Trace(ctx, "created a resource") - - // Save data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var data ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var data ExampleResourceModel - - // Read Terraform plan data into the model - resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update example, got error: %s", err)) - // return - // } - - // Save updated data into Terraform state - resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) -} - -func (r *ExampleResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var data ExampleResourceModel - - // Read Terraform prior state data into the model - resp.Diagnostics.Append(req.State.Get(ctx, &data)...) - - if resp.Diagnostics.HasError() { - return - } - - // If applicable, this is a great opportunity to initialize any necessary - // provider client data and make a call using it. - // httpResp, err := r.client.Do(httpReq) - // if err != nil { - // resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete example, got error: %s", err)) - // return - // } -} - -func (r *ExampleResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { - resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) -} diff --git a/internal/provider/example_resource_test.go b/internal/provider/example_resource_test.go deleted file mode 100644 index c5464d0..0000000 --- a/internal/provider/example_resource_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package provider - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccExampleResource(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, - Steps: []resource.TestStep{ - // Create and Read testing - { - Config: testAccExampleResourceConfig("one"), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "one"), - resource.TestCheckResourceAttr("scaffolding_example.test", "defaulted", "example value when not configured"), - resource.TestCheckResourceAttr("scaffolding_example.test", "id", "example-id"), - ), - }, - // ImportState testing - { - ResourceName: "scaffolding_example.test", - ImportState: true, - ImportStateVerify: true, - // This is not normally necessary, but is here because this - // example code does not have an actual upstream service. - // Once the Read method is able to refresh information from - // the upstream service, this can be removed. - ImportStateVerifyIgnore: []string{"configurable_attribute", "defaulted"}, - }, - // Update and Read testing - { - Config: testAccExampleResourceConfig("two"), - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("scaffolding_example.test", "configurable_attribute", "two"), - ), - }, - // Delete testing automatically occurs in TestCase - }, - }) -} - -func testAccExampleResourceConfig(configurableAttribute string) string { - return fmt.Sprintf(` -resource "scaffolding_example" "test" { - configurable_attribute = %[1]q -} -`, configurableAttribute) -} diff --git a/internal/provider/file_data_source.go b/internal/provider/file_data_source.go new file mode 100644 index 0000000..7ca7fab --- /dev/null +++ b/internal/provider/file_data_source.go @@ -0,0 +1,112 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/germanbrew/terraform-provider-dotenv/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ datasource.DataSource = &fileDotEnvDataSource{} + +func NewFileDotEnvDataSource() datasource.DataSource { + return &fileDotEnvDataSource{} +} + +// fileDotEnvDataSource defines the data source implementation. +type fileDotEnvDataSource struct{} + +// fileDotEnvDataSourceModel describes the data source data model. +type fileDotEnvDataSourceModel struct { + Filename types.String `tfsdk:"filename"` + Entries types.Map `tfsdk:"entries"` +} + +func (d *fileDotEnvDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName // + "_file" +} + +func (d *fileDotEnvDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + // This description is used by the documentation generator and the language server. + MarkdownDescription: "Reads and provides all entries of a dotenv file. " + + "If you only need a specific value and/or do not want to store the contents of the file in the state, " + + "you can use the `get_by_key` provider function.", + + Attributes: map[string]schema.Attribute{ + "filename": schema.StringAttribute{ + MarkdownDescription: "`Default: .env` Path to the dotenv file", + Optional: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "entries": schema.MapAttribute{ + MarkdownDescription: "Key-Value entries of the dotenv file. The values are by default considered nonsensitive. " + + "If you want to hide the values in any output as sensitive contents, you can use the " + + "[`sensitive()`](https://developer.hashicorp.com/terraform/language/functions/sensitive) function.", + Computed: true, + ElementType: types.StringType, + }, + }, + } +} + +func (d *fileDotEnvDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } +} + +func (d *fileDotEnvDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data fileDotEnvDataSourceModel + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + filename := data.Filename.ValueString() + + if filename == "" { + filename = ".env" + tflog.Info(ctx, "No file name specified, so the default is used: "+filename) + } + + parsedEntries, err := utils.ParseDotEnvFile(filename) + if err != nil { + resp.Diagnostics.AddError("Parse Error", fmt.Sprintf("Parsing contents of file %s failed: %s", filename, err)) + + return + } + + entries := make(map[string]attr.Value, len(parsedEntries)) + for key, value := range parsedEntries { + entries[key] = types.StringValue(value) + + if err != nil { + resp.Diagnostics.AddError("Conversion Error", fmt.Sprintf("Failed to convert key %s value %s: %s", key, value, err)) + + return + } + } + + tflog.Debug(ctx, "Parsing the file was successful") + + data.Filename = types.StringValue(filename) + data.Entries, _ = types.MapValue(types.StringType, entries) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/example_data_source_test.go b/internal/provider/file_data_source_test.go similarity index 55% rename from internal/provider/example_data_source_test.go rename to internal/provider/file_data_source_test.go index 6f9aa7d..3c884e5 100644 --- a/internal/provider/example_data_source_test.go +++ b/internal/provider/file_data_source_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package provider import ( @@ -9,7 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestAccExampleDataSource(t *testing.T) { +func TestAccDataSource_DotEnvFile(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, @@ -18,7 +15,9 @@ func TestAccExampleDataSource(t *testing.T) { { Config: testAccExampleDataSourceConfig, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.scaffolding_example.test", "id", "example-id"), + resource.TestCheckResourceAttr("data.dotenv.test", "entries.EXAMPLE_STRING", "Example v@lue!"), + resource.TestCheckResourceAttr("data.dotenv.test", "entries.EXAMPLE_INT", "100"), + resource.TestCheckResourceAttr("data.dotenv.test", "entries.EXAMPLE_FLOAT", "1.23"), ), }, }, @@ -26,7 +25,7 @@ func TestAccExampleDataSource(t *testing.T) { } const testAccExampleDataSourceConfig = ` -data "scaffolding_example" "test" { - configurable_attribute = "example" +data "dotenv" "test" { + filename = "./testdata/test.env" } ` diff --git a/internal/provider/get_by_key_function.go b/internal/provider/get_by_key_function.go new file mode 100644 index 0000000..e1da7ab --- /dev/null +++ b/internal/provider/get_by_key_function.go @@ -0,0 +1,74 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/germanbrew/terraform-provider-dotenv/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/function" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ function.Function = GetByKeyFunction{} + +func NewGetByKeyFunction() function.Function { + return GetByKeyFunction{} +} + +type GetByKeyFunction struct{} + +func (r GetByKeyFunction) Metadata(_ context.Context, req function.MetadataRequest, resp *function.MetadataResponse) { + resp.Name = "get_by_key" +} + +func (r GetByKeyFunction) Definition(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { + resp.Definition = function.Definition{ + Summary: "Get by key function", + MarkdownDescription: "Reads and provides a single entry of a dotfile by its key", + Parameters: []function.Parameter{ + function.StringParameter{ + Name: "key", + MarkdownDescription: "Name of the key", + }, + function.StringParameter{ + Name: "filename", + MarkdownDescription: "Path to the dotfile", + }, + }, + Return: function.StringReturn{}, + } +} + +func (r GetByKeyFunction) Run(ctx context.Context, req function.RunRequest, resp *function.RunResponse) { + var ( + key string + filename string + ) + + resp.Error = function.ConcatFuncErrors(req.Arguments.Get(ctx, &key, &filename)) + + if resp.Error != nil { + return + } + + if filename == "" { + filename = ".env" + tflog.Info(ctx, "No file name specified, so the default is used: "+filename) + } + + entries, err := utils.ParseDotEnvFile(filename) + if err != nil { + resp.Error = function.NewFuncError(err.Error()) + + return + } + + value, exists := entries[key] + if !exists { + resp.Error = function.NewFuncError(fmt.Sprintf("Could not find key '%s' in file '%s'", key, filename)) + + return + } + + resp.Error = function.ConcatFuncErrors(resp.Result.Set(ctx, value)) +} diff --git a/internal/provider/example_function_test.go b/internal/provider/get_by_key_function_test.go similarity index 53% rename from internal/provider/example_function_test.go rename to internal/provider/get_by_key_function_test.go index f7b5bdd..b106a5d 100644 --- a/internal/provider/example_function_test.go +++ b/internal/provider/get_by_key_function_test.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package provider import ( @@ -8,10 +5,12 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) -func TestExampleFunction_Known(t *testing.T) { +func TestGetByKeyFunction_Known(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), @@ -21,18 +20,23 @@ func TestExampleFunction_Known(t *testing.T) { { Config: ` output "test" { - value = provider::scaffolding::example("testvalue") + value = provider::dotenv::get_by_key("EXAMPLE_STRING", "./testdata/test.env") + } + + output "addition" { + value = provider::dotenv::get_by_key("EXAMPLE_INT", "./testdata/test.env") + 50 } `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckOutput("test", "testvalue"), - ), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownOutputValue("test", knownvalue.StringExact("Example v@lue!")), + statecheck.ExpectKnownOutputValue("addition", knownvalue.Int64Exact(150)), + }, }, }, }) } -func TestExampleFunction_Null(t *testing.T) { +func TestGetByKeyFunction_UnknownKey(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), @@ -42,17 +46,16 @@ func TestExampleFunction_Null(t *testing.T) { { Config: ` output "test" { - value = provider::scaffolding::example(null) + value = provider::dotenv::get_by_key("DOES_NOT_EXIST", "./testdata/test.env") } `, - // The parameter does not enable AllowNullValue - ExpectError: regexp.MustCompile(`argument must not be null`), + ExpectError: regexp.MustCompile(`Could not find key`), }, }, }) } -func TestExampleFunction_Unknown(t *testing.T) { +func TestGetByKeyFunction_UnknownFileName(t *testing.T) { resource.UnitTest(t, resource.TestCase{ TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version1_8_0), @@ -61,17 +64,11 @@ func TestExampleFunction_Unknown(t *testing.T) { Steps: []resource.TestStep{ { Config: ` - resource "terraform_data" "test" { - input = "testvalue" - } - output "test" { - value = provider::scaffolding::example(terraform_data.test.output) + value = provider::dotenv::get_by_key("EXAMPLE_STRING", "./testdata/unknown.env") } `, - Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckOutput("test", "testvalue"), - ), + ExpectError: regexp.MustCompile(`no such file or directory`), }, }, }) diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 2471df6..ce41327 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,92 +1,72 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package provider import ( "context" - "net/http" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/types" ) -// Ensure ScaffoldingProvider satisfies various provider interfaces. -var _ provider.Provider = &ScaffoldingProvider{} -var _ provider.ProviderWithFunctions = &ScaffoldingProvider{} +// Ensure DotenvProvider satisfies various provider interfaces. +var ( + _ provider.Provider = &DotenvProvider{} + _ provider.ProviderWithFunctions = &DotenvProvider{} +) -// ScaffoldingProvider defines the provider implementation. -type ScaffoldingProvider struct { +// DotenvProvider defines the provider implementation. +type DotenvProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance // testing. version string } -// ScaffoldingProviderModel describes the provider data model. -type ScaffoldingProviderModel struct { - Endpoint types.String `tfsdk:"endpoint"` -} +// DotenvProviderModel describes the provider data model. +type DotenvProviderModel struct{} -func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "scaffolding" +func (p *DotenvProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "dotenv" resp.Version = p.version } -func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { +func (p *DotenvProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "endpoint": schema.StringAttribute{ - MarkdownDescription: "Example provider attribute", - Optional: true, - }, - }, + Description: "A utility Terraform provider for dotfiles.", } } -func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - var data ScaffoldingProviderModel +func (p *DotenvProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data DotenvProviderModel resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } - - // Configuration values are now available. - // if data.Endpoint.IsNull() { /* ... */ } - - // Example client configuration for data sources and resources - client := http.DefaultClient - resp.DataSourceData = client - resp.ResourceData = client } -func (p *ScaffoldingProvider) Resources(ctx context.Context) []func() resource.Resource { - return []func() resource.Resource{ - NewExampleResource, - } +func (p *DotenvProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{} } -func (p *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { +func (p *DotenvProvider) DataSources(ctx context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ - NewExampleDataSource, + NewFileDotEnvDataSource, } } -func (p *ScaffoldingProvider) Functions(ctx context.Context) []func() function.Function { +func (p *DotenvProvider) Functions(ctx context.Context) []func() function.Function { return []func() function.Function{ - NewExampleFunction, + NewGetByKeyFunction, } } func New(version string) func() provider.Provider { return func() provider.Provider { - return &ScaffoldingProvider{ + return &DotenvProvider{ version: version, } } diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index ef6599b..b7431bc 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -14,8 +14,10 @@ import ( // acceptance testing. The factory function will be invoked for every Terraform // CLI command executed to create a provider server to which the CLI can // reattach. +// +//nolint:gochecknoglobals var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ - "scaffolding": providerserver.NewProtocol6WithError(New("test")()), + "dotenv": providerserver.NewProtocol6WithError(New("test")()), } func testAccPreCheck(t *testing.T) { diff --git a/internal/provider/testdata/test.env b/internal/provider/testdata/test.env new file mode 100644 index 0000000..f176eb5 --- /dev/null +++ b/internal/provider/testdata/test.env @@ -0,0 +1,3 @@ +EXAMPLE_STRING='Example v@lue!' +EXAMPLE_INT=100 +EXAMPLE_FLOAT=1.23 diff --git a/internal/utils/file.go b/internal/utils/file.go new file mode 100644 index 0000000..b58edd7 --- /dev/null +++ b/internal/utils/file.go @@ -0,0 +1,22 @@ +package utils + +import ( + "fmt" + "os" + + "github.com/direnv/direnv/v2/pkg/dotenv" +) + +func ParseDotEnvFile(filename string) (map[string]string, error) { + contents, err := os.ReadFile(filename) + if err != nil { + return nil, fmt.Errorf("failed to read dotenv file: %w", err) + } + + parsedEntries, err := dotenv.Parse(string(contents)) + if err != nil { + return nil, fmt.Errorf("failed to parse dotenv file: %w", err) + } + + return parsedEntries, nil +} diff --git a/main.go b/main.go index a95e85c..d2a00be 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,3 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - package main import ( @@ -8,8 +5,8 @@ import ( "flag" "log" + "github.com/germanbrew/terraform-provider-dotenv/internal/provider" "github.com/hashicorp/terraform-plugin-framework/providerserver" - "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" ) // Run "go generate" to format example terraform files and generate the docs for the registry/website @@ -20,16 +17,14 @@ import ( // Run the docs generation tool, check its repository for more information on how it works and how docs // can be customized. -//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate -provider-name scaffolding +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate -provider-name dotenv -var ( - // these will be set by the goreleaser configuration - // to appropriate values for the compiled binary. - version string = "dev" +// these will be set by the goreleaser configuration +// to appropriate values for the compiled binary. +var version string = "dev" - // goreleaser can pass other information to the main package, such as the specific commit - // https://goreleaser.com/cookbooks/using-main.version/ -) +// goreleaser can pass other information to the main package, such as the specific commit +// https://goreleaser.com/cookbooks/using-main.version/ func main() { var debug bool @@ -38,15 +33,11 @@ func main() { flag.Parse() opts := providerserver.ServeOpts{ - // TODO: Update this string with the published name of your provider. - // Also update the tfplugindocs generate command to either remove the - // -provider-name flag or set its value to the updated provider name. - Address: "registry.terraform.io/hashicorp/scaffolding", + Address: "registry.terraform.io/germanbrew/dotenv", Debug: debug, } err := providerserver.Serve(context.Background(), provider.New(version), opts) - if err != nil { log.Fatal(err.Error()) } diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b3e5232 --- /dev/null +++ b/renovate.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices" + ], + "dependencyDashboardLabels": ["Kind/Dependency"], + "labels": ["Kind/Dependency"], + "packageRules": [ + { + "matchUpdateTypes": ["major"], + "minimumReleaseAge": "2 days" + }, + { + "matchUpdateTypes": ["minor"], + "minimumReleaseAge": "1 days" + }, + { + "matchUpdateTypes": ["patch", "pin"], + "minimumReleaseAge": "1 days", + "automerge": true + } + ] +}