diff --git a/.changes/unreleased/BUG FIXES-20231108-172558.yaml b/.changes/unreleased/BUG FIXES-20231108-172558.yaml new file mode 100644 index 00000000..d941ce02 --- /dev/null +++ b/.changes/unreleased/BUG FIXES-20231108-172558.yaml @@ -0,0 +1,6 @@ +kind: BUG FIXES +body: 'generate: fix `no such file or directory` error when running `generate` with + no existing rendered website directory.' +time: 2023-11-08T17:25:58.561956-05:00 +custom: + Issue: "296" diff --git a/.copywrite.hcl b/.copywrite.hcl index 76d2e55b..7169a19e 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -9,6 +9,9 @@ project { ".changes/unreleased/*.yaml", ".changie.yaml", + # examples used within documentation (prose) + "internal/provider/testdata/**", + # GitHub issue template configuration ".github/ISSUE_TEMPLATE/*.yml", diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5838777c..88a1cd85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,5 +33,5 @@ jobs: args: --timeout=3m - name: go vet run: go vet ./... - - name: Run tests - run: go test -v -cover -race ./... + - name: Run acceptance tests + run: make testacc diff --git a/.goreleaser.yml b/.goreleaser.yml index b6fab94d..b6514e75 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,7 +7,7 @@ builds: flags: - -trimpath ldflags: - - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' + - '-s -w -X github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs/build.version={{.Version}} -X github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs/build.commit={{.Commit}}' goos: - windows - linux diff --git a/GNUmakefile b/GNUmakefile index b8a61af9..76575b20 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -2,7 +2,7 @@ TEST?=./... default: build -.PHONY: build test +.PHONY: build test testacc build: go install ./cmd/tfplugindocs @@ -10,6 +10,9 @@ build: test: go test $(TEST) $(TESTARGS) -timeout=5m +testacc: + ACCTEST=1 go test -v -cover -race -timeout 120m ./... + # Generate copywrite headers generate: cd tools; go generate ./... diff --git a/README.md b/README.md index 0cfde412..2fc6c6bc 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ using the following data fields and functions: | `.ProviderName` | string | Canonical provider name (ex. `terraform-provider-random`) | | `.ProviderShortName` | string | Short version of the provider name (ex. `random`) | | `.RenderedProviderName` | string | Value provided via argument `--rendered-provider-name`, otherwise same as `.ProviderName` | +| `.SchemaMarkdown` | string | a Markdown formatted Provider Schema definition | ##### Resources / Data Source @@ -161,6 +162,7 @@ using the following data fields and functions: | `.ProviderName` | string | Canonical provider name (ex. `terraform-provider-random`) | | `.ProviderShortName` | string | Short version of the provider name (ex. `random`) | | `.RenderedProviderName` | string | Value provided via argument `--rendered-provider-name`, otherwise same as `.ProviderName` | +| `.SchemaMarkdown` | string | a Markdown formatted Resource / Data Source Schema definition | #### Functions @@ -170,6 +172,7 @@ using the following data fields and functions: | `lower` | Equivalent to [`strings.ToLower`](https://pkg.go.dev/strings#ToLower). | | `plainmarkdown` | Render Markdown content as plaintext. | | `prefixlines` | Add a prefix to all (newline-separated) lines in a string. | +| `printf` | Equivalent to [`fmt.Printf`](https://pkg.go.dev/fmt#Printf). | | `split` | Split string into sub-strings, by a given separator (ex. `split .Name "_"`). | | `title` | Equivalent to [`cases.Title`](https://pkg.go.dev/golang.org/x/text/cases#Title). | | `tffile` | A special case of the `codefile` function, designed for Terraform files (i.e. `.tf`). | @@ -190,3 +193,14 @@ Your help and patience is truly appreciated. All source code files in this repository (excluding autogenerated files like `go.mod`, prose, and files excluded in [.copywrite.hcl](.copywrite.hcl)) must have a license header at the top. This can be autogenerated by running `make generate` or running `go generate ./...` in the [/tools](/tools) directory. + +### Acceptance Tests + +This repo uses the `testscript` [package](https://pkg.go.dev/github.com/rogpeppe/go-internal/testscript) for acceptance testing. + +You can run `make testacc` to run the acceptance tests. By default, the acceptance tests will create a temporary directory in `/tmp/tftmp` for testing but you can change this location in `cmd/tfplugindocs/main_test.go` + +The test scripts are defined in the `tfplugindocs/testdata/scripts` directory. Each script includes the test, golden files, and the provider source code needed to run the test. + +Each script is a [text archive](https://pkg.go.dev/golang.org/x/tools/txtar). You can install the `txtar` CLI locally by running `go install golang.org/x/exp/cmd/txtar@latest` to extract the files in the test script for debugging. +You can also use `txtar` CLI archive files into the `.txtar` format to create new tests or modify existing ones. diff --git a/cmd/tfplugindocs/build/version.go b/cmd/tfplugindocs/build/version.go new file mode 100644 index 00000000..01ded843 --- /dev/null +++ b/cmd/tfplugindocs/build/version.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package build + +var ( + // These vars will be set by goreleaser. + version string = `dev` + commit string = `` +) + +func GetVersion() string { + version := "tfplugindocs" + " Version " + version + if commit != "" { + version += " from commit " + commit + } + return version +} diff --git a/cmd/tfplugindocs/main.go b/cmd/tfplugindocs/main.go index df6e336a..8e3c25da 100644 --- a/cmd/tfplugindocs/main.go +++ b/cmd/tfplugindocs/main.go @@ -6,24 +6,9 @@ package main import ( "os" - "github.com/mattn/go-colorable" - "github.com/hashicorp/terraform-plugin-docs/internal/cmd" ) func main() { - name := "tfplugindocs" - version := name + " Version " + version - if commit != "" { - version += " from commit " + commit - } - - os.Exit(cmd.Run( - name, - version, - os.Args[1:], - os.Stdin, - colorable.NewColorableStdout(), - colorable.NewColorableStderr(), - )) + os.Exit(cmd.Main()) } diff --git a/cmd/tfplugindocs/main_test.go b/cmd/tfplugindocs/main_test.go new file mode 100644 index 00000000..cd542aae --- /dev/null +++ b/cmd/tfplugindocs/main_test.go @@ -0,0 +1,40 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "os" + "testing" + + "github.com/rogpeppe/go-internal/testscript" + + "github.com/hashicorp/terraform-plugin-docs/internal/cmd" +) + +func TestMain(m *testing.M) { + os.Exit(testscript.RunMain(m, map[string]func() int{ + "tfplugindocs": cmd.Main, + })) +} + +func Test_GenerateAcceptanceTests(t *testing.T) { + t.Parallel() + if os.Getenv("ACCTEST") == "" { + t.Skip("ACCTEST env var not set; skipping acceptance tests.") + } + // Setting a custom temp dir instead of relying on os.TempDir() + // because Terraform providers fail to start up when $TMPDIR + // length is too long: https://github.com/hashicorp/terraform/issues/32787 + tmpDir := "/tmp/tftmp" + err := os.MkdirAll(tmpDir, 0755) + if err != nil { + t.Errorf("Error creating temp dir for testing: %s", err.Error()) + } + defer os.RemoveAll(tmpDir) + + testscript.Run(t, testscript.Params{ + Dir: "testdata/scripts/generate", + WorkdirRoot: tmpDir, + }) +} diff --git a/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_generic_templates.txtar b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_generic_templates.txtar new file mode 100644 index 00000000..5a93b719 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_generic_templates.txtar @@ -0,0 +1,931 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with "generic" template paths (i.e. templates/resources.md.tmpl) +# Templates test all implemented data fields and functions. +[!unix] skip +env GOCACHE=$WORK/gocache +env GOMODCACHE=$WORK/gomodcache +exec tfplugindocs --provider-name=terraform-provider-scaffolding +cmp stdout expected-output.txt +cmpenv docs/index.md expected-index.md +cmpenv docs/data-sources/example.md expected-datasource.md +cmpenv docs/resources/example.md expected-resource.md + +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") +copying any existing content to tmp dir +exporting schema from Terraform +compiling provider "scaffolding" +using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary +running terraform init +getting provider schema +rendering missing docs +generating missing resource content +resource "scaffolding_example" fallback template exists +generating template for "scaffolding_example" +generating missing data source content +resource "scaffolding_example" fallback template exists +generating template for "scaffolding_example" +generating missing provider content +provider "terraform-provider-scaffolding" template exists, skipping +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +rendering "data-sources/example.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/example.md.tmpl" +-- expected-datasource.md -- +# Data Fields + +Name: scaffolding_example +Type: Data Source +Description: Example data source +HasExample: true +ExampleFile: $WORK/examples/data-sources/scaffolding_example/data-source.tf +HasImport: false +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute + +### Read-Only + +- `id` (String) Example identifier + + + +# Functions + +lower: data source +plainmarkdown: Data Source +prefixlines: Prefix: Data Source +split: [scaffolding example] +title: Data Source +trimspace: Data Source +upper: DATA SOURCE + +# Conditionals and File Functions + +printf codefile: + + +printf tffile: +## Example Usage + +```terraform +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` + +codefile: + + +tffile: +## Example Usage + +```terraform +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` +-- expected-index.md -- +# Data Fields + +Description: Example provider +HasExample: true +ExampleFile: $WORK/examples/provider/provider.tf +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `endpoint` (String) Example provider attribute + + + +# Functions + +lower: terraform-provider-scaffolding +plainmarkdown: terraform-provider-scaffolding +prefixlines: Prefix: terraform-provider-scaffolding +split: [terraform provider scaffolding] +title: Terraform-Provider-Scaffolding +trimspace: terraform-provider-scaffolding +upper: TERRAFORM-PROVIDER-SCAFFOLDING + +# Conditionals and File Functions + +printf tffile: +## Example Usage + +{{tffile "$WORK/examples/provider/provider.tf"}} + +tffile: +## Example Usage + +```terraform +provider "scaffolding" { + # example configuration here +} +``` +-- expected-resource.md -- +# Data Fields + +Name: scaffolding_example +Type: Resource +Description: Example resource +HasExample: true +ExampleFile: $WORK/examples/resources/scaffolding_example/resource.tf +HasImport: true +ImportFile: $WORK/examples/resources/scaffolding_example/import.sh +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute +- `defaulted` (String) Example configurable attribute with default value + +### Read-Only + +- `id` (String) Example identifier + + + +# Functions + +lower: resource +plainmarkdown: Resource +prefixlines: Prefix: Resource +split: [scaffolding example] +title: Resource +trimspace: Resource +upper: RESOURCE + +# Conditionals and File Functions + +printf codefile: +## Import + +Import is supported using the following syntax: + +```shell +terraform import scaffolding_example.example +``` + +printf tffile: +## Example Usage + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` + +codefile: +## Import + +Import is supported using the following syntax: + +```shell +terraform import scaffolding_example.example +``` + +tffile: +## Example Usage + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` +-- templates/data-sources.md.tmpl -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +HasImport: {{.HasImport}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .Type | lower }} +plainmarkdown: {{ .Type | plainmarkdown }} +prefixlines: {{ .Type | prefixlines "Prefix: " }} +split: {{ split .Name "_" }} +title: {{ .Type | title }} +trimspace: {{ .Type | trimspace }} +upper: {{ .Type | upper }} + +# Conditionals and File Functions + +printf codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- templates/index.md.tmpl -- +# Data Fields + +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .ProviderName | lower }} +plainmarkdown: {{ .ProviderName | plainmarkdown }} +prefixlines: {{ .ProviderName | prefixlines "Prefix: " }} +split: {{ split .ProviderName "-" }} +title: {{ .ProviderName | title }} +trimspace: {{ .ProviderName | trimspace }} +upper: {{ .ProviderName | upper }} + +# Conditionals and File Functions + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- templates/resources.md.tmpl -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +HasImport: {{.HasImport}} +ImportFile: {{.ImportFile}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .Type | lower }} +plainmarkdown: {{ .Type | plainmarkdown }} +prefixlines: {{ .Type | prefixlines "Prefix: " }} +split: {{ split .Name "_" }} +title: {{ .Type | title }} +trimspace: {{ .Type | trimspace }} +upper: {{ .Type | upper }} + +# Conditionals and File Functions + +printf codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # example configuration here +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/import.sh -- +terraform import scaffolding_example.example +-- internal/provider/example_data_source.go -- +// 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)...) +} +-- internal/provider/example_resource.go -- +// 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) +} +-- internal/provider/provider.go -- +// 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/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{} + +// ScaffoldingProvider defines the provider implementation. +type ScaffoldingProvider 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"` +} + +func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "scaffolding" + resp.Version = p.version +} + +func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Example provider", + Attributes: map[string]schema.Attribute{ + "endpoint": schema.StringAttribute{ + MarkdownDescription: "Example provider attribute", + Optional: true, + }, + }, + } +} + +func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ScaffoldingProviderModel + + 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 *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewExampleDataSource, + } +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ScaffoldingProvider{ + version: version, + } + } +} +-- main.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" +) + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary. + version string = "dev" + + // 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 + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/scaffolding", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +-- GNUmakefile -- +default: testacc + +# Run acceptance tests +.PHONY: testacc +testacc: + TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m + +-- go.mod -- +module github.com/hashicorp/terraform-provider-scaffolding-framework + +go 1.19 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.4.2 + github.com/hashicorp/terraform-plugin-log v0.9.0 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.5.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/terraform-plugin-go v0.19.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.2 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) +-- go.sum -- +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k= +github.com/hashicorp/go-plugin v1.5.1/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= +github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= +github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= +github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-registry-address v0.2.2 h1:lPQBg403El8PPicg/qONZJDC6YlgCVbWDtNmmZKtBno= +github.com/hashicorp/terraform-registry-address v0.2.2/go.mod h1:LtwNbCihUoUZ3RYriyS2wF/lGPB6gF9ICLRtuDk7hSo= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_named_templates.txtar b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_named_templates.txtar new file mode 100644 index 00000000..381ba095 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_named_templates.txtar @@ -0,0 +1,919 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with "named" template paths (i.e. templates/resources/.md.tmpl) +# Templates test all implemented data fields and functions. +[!unix] skip +env GOCACHE=$WORK/gocache +env GOMODCACHE=$WORK/gomodcache +exec tfplugindocs --provider-name=terraform-provider-scaffolding +cmp stdout expected-output.txt +cmpenv docs/index.md expected-index.md +cmpenv docs/data-sources/example.md expected-datasource.md +cmpenv docs/resources/example.md expected-resource.md + +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") +copying any existing content to tmp dir +exporting schema from Terraform +compiling provider "scaffolding" +using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary +running terraform init +getting provider schema +rendering missing docs +generating missing resource content +resource "scaffolding_example" template exists, skipping +generating missing data source content +resource "scaffolding_example" template exists, skipping +generating missing provider content +provider "terraform-provider-scaffolding" template exists, skipping +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +rendering "data-sources/example.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/example.md.tmpl" +-- expected-datasource.md -- +# Data Fields + +Name: scaffolding_example +Type: Data Source +Description: Example data source +HasExample: true +ExampleFile: $WORK/examples/data-sources/scaffolding_example/data-source.tf +HasImport: false +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute + +### Read-Only + +- `id` (String) Example identifier + + + +# Functions + +lower: data source +plainmarkdown: Data Source +prefixlines: Prefix: Data Source +split: [scaffolding example] +title: Data Source +trimspace: Data Source +upper: DATA SOURCE + +# Conditionals and File Functions + +printf codefile: + + +printf tffile: +## Example Usage + +{{tffile "$WORK/examples/data-sources/scaffolding_example/data-source.tf"}} + +codefile: + + +tffile: +## Example Usage + +```terraform +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` +-- expected-index.md -- +# Data Fields + +Description: Example provider +HasExample: true +ExampleFile: $WORK/examples/provider/provider.tf +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `endpoint` (String) Example provider attribute + + + +# Functions + +lower: terraform-provider-scaffolding +plainmarkdown: terraform-provider-scaffolding +prefixlines: Prefix: terraform-provider-scaffolding +split: [terraform provider scaffolding] +title: Terraform-Provider-Scaffolding +trimspace: terraform-provider-scaffolding +upper: TERRAFORM-PROVIDER-SCAFFOLDING + +# Conditionals and File Functions + +printf tffile: +## Example Usage + +{{tffile "$WORK/examples/provider/provider.tf"}} + +tffile: +## Example Usage + +```terraform +provider "scaffolding" { + # example configuration here +} +``` +-- expected-resource.md -- +# Data Fields + +Name: scaffolding_example +Type: Resource +Description: Example resource +HasExample: true +ExampleFile: $WORK/examples/resources/scaffolding_example/resource.tf +HasImport: true +ImportFile: $WORK/examples/resources/scaffolding_example/import.sh +ProviderName: terraform-provider-scaffolding +ProviderShortName: scaffolding +RenderedProviderName: terraform-provider-scaffolding +SchemaMarkdown: +## Schema + +### Optional + +- `configurable_attribute` (String) Example configurable attribute +- `defaulted` (String) Example configurable attribute with default value + +### Read-Only + +- `id` (String) Example identifier + + + +# Functions + +lower: resource +plainmarkdown: Resource +prefixlines: Prefix: Resource +split: [scaffolding example] +title: Resource +trimspace: Resource +upper: RESOURCE + +# Conditionals and File Functions + +printf codefile: +## Import + +Import is supported using the following syntax: + +{{codefile "shell" "$WORK/examples/resources/scaffolding_example/import.sh"}} + +printf tffile: +## Example Usage + +{{tffile "$WORK/examples/resources/scaffolding_example/resource.tf"}} + +codefile: +## Import + +Import is supported using the following syntax: + +```shell +terraform import scaffolding_example.example +``` + +tffile: +## Example Usage + +```terraform +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +``` +-- templates/data-sources/example.md.tmpl -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +HasImport: {{.HasImport}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .Type | lower }} +plainmarkdown: {{ .Type | plainmarkdown }} +prefixlines: {{ .Type | prefixlines "Prefix: " }} +split: {{ split .Name "_" }} +title: {{ .Type | title }} +trimspace: {{ .Type | trimspace }} +upper: {{ .Type | upper }} + +# Conditionals and File Functions + +printf codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- templates/index.md.tmpl -- +# Data Fields + +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .ProviderName | lower }} +plainmarkdown: {{ .ProviderName | plainmarkdown }} +prefixlines: {{ .ProviderName | prefixlines "Prefix: " }} +split: {{ split .ProviderName "-" }} +title: {{ .ProviderName | title }} +trimspace: {{ .ProviderName | trimspace }} +upper: {{ .ProviderName | upper }} + +# Conditionals and File Functions + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- templates/resources/example.md.tmpl -- +# Data Fields + +Name: {{.Name}} +Type: {{.Type}} +Description: {{.Description}} +HasExample: {{.HasExample}} +ExampleFile: {{.ExampleFile}} +HasImport: {{.HasImport}} +ImportFile: {{.ImportFile}} +ProviderName: {{.ProviderName}} +ProviderShortName: {{.ProviderShortName}} +RenderedProviderName: {{.RenderedProviderName}} +SchemaMarkdown: {{.SchemaMarkdown}} + +# Functions + +lower: {{ .Type | lower }} +plainmarkdown: {{ .Type | plainmarkdown }} +prefixlines: {{ .Type | prefixlines "Prefix: " }} +split: {{ split .Name "_" }} +title: {{ .Type | title }} +trimspace: {{ .Type | trimspace }} +upper: {{ .Type | upper }} + +# Conditionals and File Functions + +printf codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} + +printf tffile: +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +codefile: +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile }} +{{- end }} + +tffile: +{{ if .HasExample -}} +## Example Usage + +{{tffile .ExampleFile }} +{{- end }} +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # example configuration here +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/resources/scaffolding_example/import.sh -- +terraform import scaffolding_example.example +-- internal/provider/example_data_source.go -- +// 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)...) +} +-- internal/provider/example_resource.go -- +// 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) +} +-- internal/provider/provider.go -- +// 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/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{} + +// ScaffoldingProvider defines the provider implementation. +type ScaffoldingProvider 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"` +} + +func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "scaffolding" + resp.Version = p.version +} + +func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Example provider", + Attributes: map[string]schema.Attribute{ + "endpoint": schema.StringAttribute{ + MarkdownDescription: "Example provider attribute", + Optional: true, + }, + }, + } +} + +func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ScaffoldingProviderModel + + 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 *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewExampleDataSource, + } +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ScaffoldingProvider{ + version: version, + } + } +} +-- main.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" +) + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary. + version string = "dev" + + // 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 + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/scaffolding", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +-- GNUmakefile -- +default: testacc + +# Run acceptance tests +.PHONY: testacc +testacc: + TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m + +-- go.mod -- +module github.com/hashicorp/terraform-provider-scaffolding-framework + +go 1.19 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.4.2 + github.com/hashicorp/terraform-plugin-log v0.9.0 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.5.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/terraform-plugin-go v0.19.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.2 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) +-- go.sum -- +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k= +github.com/hashicorp/go-plugin v1.5.1/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= +github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= +github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= +github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-registry-address v0.2.2 h1:lPQBg403El8PPicg/qONZJDC6YlgCVbWDtNmmZKtBno= +github.com/hashicorp/terraform-registry-address v0.2.2/go.mod h1:LtwNbCihUoUZ3RYriyS2wF/lGPB6gF9ICLRtuDk7hSo= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_no_templates.txtar b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_no_templates.txtar new file mode 100644 index 00000000..07ae876f --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/generate/framework_provider_success_no_templates.txtar @@ -0,0 +1,687 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a Framework provider with examples but no templates or pre-exiting docs. +[!unix] skip +env GOCACHE=$WORK/gocache +env GOMODCACHE=$WORK/gomodcache +exec tfplugindocs --provider-name=terraform-provider-scaffolding +cmp stdout expected-output.txt +cmp docs/index.md expected-index.md +cmp docs/data-sources/example.md expected-datasource.md +cmp docs/resources/example.md expected-resource.md + +-- expected-output.txt -- +rendering website for provider "terraform-provider-scaffolding" (as "terraform-provider-scaffolding") +exporting schema from Terraform +compiling provider "scaffolding" +using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary +running terraform init +getting provider schema +rendering missing docs +generating missing resource content +generating template for "scaffolding_example" +generating missing data source content +generating template for "scaffolding_example" +generating missing provider content +generating template for "terraform-provider-scaffolding" +rendering static website +cleaning rendered website dir +rendering templated website to static markdown +rendering "data-sources/example.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/example.md.tmpl" +-- expected-datasource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Data Source - terraform-provider-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 +-- expected-index.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding Provider" +subcategory: "" +description: |- + Example provider +--- + +# scaffolding Provider + +Example provider + +## Example Usage + +```terraform +provider "scaffolding" { + # example configuration here +} +``` + + +## Schema + +### Optional + +- `endpoint` (String) Example provider attribute +-- expected-resource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "scaffolding_example Resource - terraform-provider-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 +-- examples/README.md -- +# Examples + +This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. + +The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. + +* **provider/provider.tf** example file for the provider index page +* **data-sources/`full data source name`/data-source.tf** example file for the named data source page +* **resources/`full resource name`/resource.tf** example file for the named data source page +-- examples/data-sources/scaffolding_example/data-source.tf -- +data "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- examples/provider/provider.tf -- +provider "scaffolding" { + # example configuration here +} +-- examples/resources/scaffolding_example/resource.tf -- +resource "scaffolding_example" "example" { + configurable_attribute = "some-value" +} +-- internal/provider/example_data_source.go -- +// 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)...) +} +-- internal/provider/example_resource.go -- +// 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) +} +-- internal/provider/provider.go -- +// 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/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{} + +// ScaffoldingProvider defines the provider implementation. +type ScaffoldingProvider 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"` +} + +func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "scaffolding" + resp.Version = p.version +} + +func (p *ScaffoldingProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Example provider", + Attributes: map[string]schema.Attribute{ + "endpoint": schema.StringAttribute{ + MarkdownDescription: "Example provider attribute", + Optional: true, + }, + }, + } +} + +func (p *ScaffoldingProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data ScaffoldingProviderModel + + 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 *ScaffoldingProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewExampleDataSource, + } +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &ScaffoldingProvider{ + version: version, + } + } +} +-- main.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/hashicorp/terraform-provider-scaffolding-framework/internal/provider" +) + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary. + version string = "dev" + + // 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 + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/scaffolding", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} +-- GNUmakefile -- +default: testacc + +# Run acceptance tests +.PHONY: testacc +testacc: + TF_ACC=1 go test ./... -v $(TESTARGS) -timeout 120m + +-- go.mod -- +module github.com/hashicorp/terraform-provider-scaffolding-framework + +go 1.19 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.4.2 + github.com/hashicorp/terraform-plugin-log v0.9.0 +) + +require ( + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-plugin v1.5.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/terraform-plugin-go v0.19.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.2 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.57.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) +-- go.sum -- +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +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/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k= +github.com/hashicorp/go-plugin v1.5.1/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= +github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= +github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= +github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-registry-address v0.2.2 h1:lPQBg403El8PPicg/qONZJDC6YlgCVbWDtNmmZKtBno= +github.com/hashicorp/terraform-registry-address v0.2.2/go.mod h1:LtwNbCihUoUZ3RYriyS2wF/lGPB6gF9ICLRtuDk7hSo= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.1 h1:upNTNqv0ES+2ZOOqACwVtS3Il8M12/+Hz41RCPzAjQg= +google.golang.org/grpc v1.57.1/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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/cmd/tfplugindocs/testdata/scripts/generate/null_provider_success.txtar b/cmd/tfplugindocs/testdata/scripts/generate/null_provider_success.txtar new file mode 100644 index 00000000..c776a3e2 --- /dev/null +++ b/cmd/tfplugindocs/testdata/scripts/generate/null_provider_success.txtar @@ -0,0 +1,2405 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# Successful run of tfplugindocs on a stripped-down version of the Null provider with pre-exiting templates, examples, +# docs, and non-tfplugindocs managed docs (docs/cdktf subdirectory). +[!unix] skip +env GOCACHE=$WORK/gocache +env GOMODCACHE=$WORK/gomodcache +exec tfplugindocs --provider-name=terraform-provider-null +cmp stdout expected-output.txt + +-- expected-output.txt -- +rendering website for provider "terraform-provider-null" (as "terraform-provider-null") +copying any existing content to tmp dir +exporting schema from Terraform +compiling provider "null" +using Terraform CLI binary from PATH if available, otherwise downloading latest Terraform CLI binary +running terraform init +getting provider schema +rendering missing docs +generating missing resource content +resource "null_resource" fallback template exists +generating template for "null_resource" +generating missing data source content +resource "null_data_source" fallback template exists +generating template for "null_data_source" +generating missing provider content +provider "terraform-provider-null" template exists, skipping +rendering static website +cleaning rendered website dir +removing directory: "data-sources" +removing file: "index.md" +removing directory: "resources" +rendering templated website to static markdown +rendering "data-sources/data_source.md.tmpl" +rendering "index.md.tmpl" +rendering "resources/resource.md.tmpl" +-- templates/data-sources.md.tmpl -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} +-- templates/index.md.tmpl -- +--- +page_title: "Provider: Null" +description: |- + The null provider provides no-op constructs that can be useful helpers in tricky cases. +--- + +# Null Provider + +The `null` provider is a rather-unusual provider that has constructs that +intentionally do nothing. This may sound strange, and indeed these constructs +do not need to be used in most cases, but they can be useful in various +situations to help orchestrate tricky behavior or work around limitations. + +The documentation of each feature of this provider, accessible via the +navigation, gives examples of situations where these constructs may prove +useful. + +Usage of the `null` provider can make a Terraform configuration harder to +understand. While it can be useful in certain cases, it should be applied with +care and other solutions preferred when available. + +{{ .SchemaMarkdown | trimspace }} +-- templates/resources.md.tmpl -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ printf "{{tffile %q}}" .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{ printf "{{codefile \"shell\" %q}}" .ImportFile }} +{{- end }} + + +-- GNUmakefile -- +default: build + +build: + go build -v ./... + +install: build + go install -v ./... + +fmt: + gofmt -s -w -e . + +test: + go test -v -cover -timeout=120s -parallel=4 ./... + +testacc: + TF_ACC=1 go test -v -cover -timeout 120m ./... + +.PHONY: build install fmt test testacc +-- docs/cdktf/python/data-sources/data_source.md -- +--- + + +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_data_source Data Source - terraform-provider-null" +subcategory: "" +description: |- + The null_data_source data source implements the standard data source lifecycle but does not + interact with any external APIs. + Historically, the null_data_source was typically used to construct intermediate values to re-use elsewhere in configuration. The + same can now be achieved using locals https://developer.hashicorp.com/terraform/language/values/locals or the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data in Terraform 1.4 and later. +--- + +# null_data_source + +The `null_data_source` data source implements the standard data source lifecycle but does not +interact with any external APIs. + +Historically, the `null_data_source` was typically used to construct intermediate values to re-use elsewhere in configuration. The +same can now be achieved using [locals](https://developer.hashicorp.com/terraform/language/values/locals) or the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) in Terraform 1.4 and later. + +## Example Usage + +```python +# DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug +from constructs import Construct +from cdktf import Token, TerraformCount, Fn, TerraformOutput, TerraformStack +# +# Provider bindings are generated by running `cdktf get`. +# See https://cdk.tf/provider-generation for more details. +# +from imports.aws.elb import Elb +from imports.aws.instance import Instance +from imports.null.data_null_data_source import DataNullDataSource +class MyConvertedCode(TerraformStack): + def __init__(self, scope, name): + super().__init__(scope, name) + # In most cases loops should be handled in the programming language context and + # not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + # you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + # you need to keep this like it is. + blue_count = TerraformCount.of(Token.as_number("3")) + blue = Instance(self, "blue", + ami="ami-0dcc1e21636832c5d", + instance_type="m5.large", + count=blue_count + ) + # In most cases loops should be handled in the programming language context and + # not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + # you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + # you need to keep this like it is. + green_count = TerraformCount.of(Token.as_number("3")) + green = Instance(self, "green", + ami="ami-0dcc1e21636832c5d", + instance_type="m5.large", + count=green_count + ) + values = DataNullDataSource(self, "values", + inputs={ + "all_server_ids": Token.as_string( + Fn.concat([ + Fn.lookup_nested(green, ["*", "id"]), + Fn.lookup_nested(blue, ["*", "id"]) + ])), + "all_server_ips": Token.as_string( + Fn.concat([ + Fn.lookup_nested(green, ["*", "private_ip"]), + Fn.lookup_nested(blue, ["*", "private_ip"]) + ])) + } + ) + TerraformOutput(self, "all_server_ids", + value=Fn.lookup_nested(values.outputs, ["\"all_server_ids\""]) + ) + TerraformOutput(self, "all_server_ips", + value=Fn.lookup_nested(values.outputs, ["\"all_server_ips\""]) + ) + Elb(self, "main", + instances=Token.as_list( + Fn.lookup_nested(values.outputs, ["\"all_server_ids\""])), + listener=[ElbListener( + instance_port=8000, + instance_protocol="http", + lb_port=80, + lb_protocol="http" + ) + ] + ) +``` + + +## Schema + +### Optional + +- `has_computed_default` (String) If set, its literal value will be stored and returned. If not, its value defaults to `"default"`. This argument exists primarily for testing and has little practical use. +- `inputs` (Map of String) A map of arbitrary strings that is copied into the `outputs` attribute, and accessible directly for interpolation. + +### Read-Only + +- `id` (String, Deprecated) This attribute is only present for some legacy compatibility issues and should not be used. It will be removed in a future version. +- `outputs` (Map of String) After the data source is "read", a copy of the `inputs` map. +- `random` (String) A random value. This is primarily for testing and has little practical use; prefer the [hashicorp/random provider](https://registry.terraform.io/providers/hashicorp/random) for more practical random number use-cases. + + + +-- docs/cdktf/python/index.md -- +--- +page_title: "Provider: Null" +description: |- + The null provider provides no-op constructs that can be useful helpers in tricky cases. +--- + + + +# Null Provider + +The `null` provider is a rather-unusual provider that has constructs that +intentionally do nothing. This may sound strange, and indeed these constructs +do not need to be used in most cases, but they can be useful in various +situations to help orchestrate tricky behavior or work around limitations. + +The documentation of each feature of this provider, accessible via the +navigation, gives examples of situations where these constructs may prove +useful. + +Usage of the `null` provider can make a Terraform configuration harder to +understand. While it can be useful in certain cases, it should be applied with +care and other solutions preferred when available. + + +## Schema + + +-- docs/cdktf/python/resources/resource.md -- +--- + + +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_resource Resource - terraform-provider-null" +subcategory: "" +description: |- + The null_resource resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data instead. + The triggers argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. +--- + +# null_resource + +The `null_resource` resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) instead. + +The `triggers` argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. + +## Example Usage + +```python +# DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug +from cdktf import SSHProvisionerConnection, FileProvisioner +from constructs import Construct +from cdktf import Token, TerraformCount, Fn, TerraformStack +# +# Provider bindings are generated by running `cdktf get`. +# See https://cdk.tf/provider-generation for more details. +# +from imports.null.resource import Resource +from imports.aws.instance import Instance +class MyConvertedCode(TerraformStack): + def __init__(self, scope, name): + super().__init__(scope, name) + # In most cases loops should be handled in the programming language context and + # not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + # you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + # you need to keep this like it is. + cluster_count = TerraformCount.of(Token.as_number("3")) + cluster = Instance(self, "cluster", + ami="ami-0dcc1e21636832c5d", + instance_type="m5.large", + count=cluster_count + ) + null_provider_resource_cluster = Resource(self, "cluster_1", + connection=SSHProvisionerConnection( + host=Fn.element(Fn.lookup_nested(cluster, ["*", "public_ip"]), 0) + ), + triggers=[{ + "cluster_instance_ids": Fn.join(",", + Token.as_list(Fn.lookup_nested(cluster, ["*", "id"]))) + } + ], + provisioners=[FileProvisioner( + type="remote-exec", + inline=["bootstrap-cluster.sh " + + Token.as_string( + Fn.join(" ", + Token.as_list(Fn.lookup_nested(cluster, ["*", "private_ip"])))) + ] + ) + ] + ) + # This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match. + null_provider_resource_cluster.override_logical_id("cluster") +``` + + +## Schema + +### Optional + +- `triggers` (Map of String) A map of arbitrary strings that, when changed, will force the null resource to be replaced, re-running any associated provisioners. + +### Read-Only + +- `id` (String) This is set to a random value at create time. + + + +-- docs/cdktf/typescript/data-sources/data_source.md -- +--- + + +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_data_source Data Source - terraform-provider-null" +subcategory: "" +description: |- + The null_data_source data source implements the standard data source lifecycle but does not + interact with any external APIs. + Historically, the null_data_source was typically used to construct intermediate values to re-use elsewhere in configuration. The + same can now be achieved using locals https://developer.hashicorp.com/terraform/language/values/locals or the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data in Terraform 1.4 and later. +--- + +# null_data_source + +The `nullDataSource` data source implements the standard data source lifecycle but does not +interact with any external APIs. + +Historically, the `nullDataSource` was typically used to construct intermediate values to re-use elsewhere in configuration. The +same can now be achieved using [locals](https://developer.hashicorp.com/terraform/language/values/locals) or the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) in Terraform 1.4 and later. + +## Example Usage + +```typescript +// DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug +import { Construct } from "constructs"; +import { + Token, + TerraformCount, + Fn, + TerraformOutput, + TerraformStack, +} from "cdktf"; +/* + * Provider bindings are generated by running `cdktf get`. + * See https://cdk.tf/provider-generation for more details. + */ +import { Elb } from "./.gen/providers/aws/elb"; +import { Instance } from "./.gen/providers/aws/instance"; +import { DataNullDataSource } from "./.gen/providers/null/data-null-data-source"; +class MyConvertedCode extends TerraformStack { + constructor(scope: Construct, name: string) { + super(scope, name); + /*In most cases loops should be handled in the programming language context and + not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + you need to keep this like it is.*/ + const blueCount = TerraformCount.of(Token.asNumber("3")); + const blue = new Instance(this, "blue", { + ami: "ami-0dcc1e21636832c5d", + instanceType: "m5.large", + count: blueCount, + }); + /*In most cases loops should be handled in the programming language context and + not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + you need to keep this like it is.*/ + const greenCount = TerraformCount.of(Token.asNumber("3")); + const green = new Instance(this, "green", { + ami: "ami-0dcc1e21636832c5d", + instanceType: "m5.large", + count: greenCount, + }); + const values = new DataNullDataSource(this, "values", { + inputs: { + all_server_ids: Token.asString( + Fn.concat([ + Fn.lookupNested(green, ["*", "id"]), + Fn.lookupNested(blue, ["*", "id"]), + ]) + ), + all_server_ips: Token.asString( + Fn.concat([ + Fn.lookupNested(green, ["*", "private_ip"]), + Fn.lookupNested(blue, ["*", "private_ip"]), + ]) + ), + }, + }); + new TerraformOutput(this, "all_server_ids", { + value: Fn.lookupNested(values.outputs, ['"all_server_ids"']), + }); + new TerraformOutput(this, "all_server_ips", { + value: Fn.lookupNested(values.outputs, ['"all_server_ips"']), + }); + new Elb(this, "main", { + instances: Token.asList( + Fn.lookupNested(values.outputs, ['"all_server_ids"']) + ), + listener: [ + { + instancePort: 8000, + instanceProtocol: "http", + lbPort: 80, + lbProtocol: "http", + }, + ], + }); + } +} + +``` + + +## Schema + +### Optional + +- `hasComputedDefault` (String) If set, its literal value will be stored and returned. If not, its value defaults to `"default"`. This argument exists primarily for testing and has little practical use. +- `inputs` (Map of String) A map of arbitrary strings that is copied into the `outputs` attribute, and accessible directly for interpolation. + +### Read-Only + +- `id` (String, Deprecated) This attribute is only present for some legacy compatibility issues and should not be used. It will be removed in a future version. +- `outputs` (Map of String) After the data source is "read", a copy of the `inputs` map. +- `random` (String) A random value. This is primarily for testing and has little practical use; prefer the [hashicorp/random provider](https://registry.terraform.io/providers/hashicorp/random) for more practical random number use-cases. + + + +-- docs/cdktf/typescript/index.md -- +--- +page_title: "Provider: Null" +description: |- + The null provider provides no-op constructs that can be useful helpers in tricky cases. +--- + + + +# Null Provider + +The `null` provider is a rather-unusual provider that has constructs that +intentionally do nothing. This may sound strange, and indeed these constructs +do not need to be used in most cases, but they can be useful in various +situations to help orchestrate tricky behavior or work around limitations. + +The documentation of each feature of this provider, accessible via the +navigation, gives examples of situations where these constructs may prove +useful. + +Usage of the `null` provider can make a Terraform configuration harder to +understand. While it can be useful in certain cases, it should be applied with +care and other solutions preferred when available. + + +## Schema + + +-- docs/cdktf/typescript/resources/resource.md -- +--- + + +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_resource Resource - terraform-provider-null" +subcategory: "" +description: |- + The null_resource resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data instead. + The triggers argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. +--- + +# null_resource + +The `nullResource` resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) instead. + +The `triggers` argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. + +## Example Usage + +```typescript +// DO NOT EDIT. Code generated by 'cdktf convert' - Please report bugs at https://cdk.tf/bug +import { Construct } from "constructs"; +import { Token, TerraformCount, Fn, TerraformStack } from "cdktf"; +/* + * Provider bindings are generated by running `cdktf get`. + * See https://cdk.tf/provider-generation for more details. + */ +import { Resource } from "./.gen/providers/null/resource"; +import { Instance } from "./.gen/providers/aws/instance"; +class MyConvertedCode extends TerraformStack { + constructor(scope: Construct, name: string) { + super(scope, name); + /*In most cases loops should be handled in the programming language context and + not inside of the Terraform context. If you are looping over something external, e.g. a variable or a file input + you should consider using a for loop. If you are looping over something only known to Terraform, e.g. a result of a data source + you need to keep this like it is.*/ + const clusterCount = TerraformCount.of(Token.asNumber("3")); + const cluster = new Instance(this, "cluster", { + ami: "ami-0dcc1e21636832c5d", + instanceType: "m5.large", + count: clusterCount, + }); + const nullProviderResourceCluster = new Resource(this, "cluster_1", { + connection: { + host: Fn.element(Fn.lookupNested(cluster, ["*", "public_ip"]), 0), + }, + triggers: [ + { + cluster_instance_ids: Fn.join( + ",", + Token.asList(Fn.lookupNested(cluster, ["*", "id"])) + ), + }, + ], + provisioners: [ + { + type: "remote-exec", + inline: [ + "bootstrap-cluster.sh " + + Token.asString( + Fn.join( + " ", + Token.asList(Fn.lookupNested(cluster, ["*", "private_ip"])) + ) + ), + ], + }, + ], + }); + /*This allows the Terraform resource name to match the original name. You can remove the call if you don't need them to match.*/ + nullProviderResourceCluster.overrideLogicalId("cluster"); + } +} + +``` + + +## Schema + +### Optional + +- `triggers` (Map of String) A map of arbitrary strings that, when changed, will force the null resource to be replaced, re-running any associated provisioners. + +### Read-Only + +- `id` (String) This is set to a random value at create time. + + + +-- docs/data-sources/data_source.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_data_source Data Source - terraform-provider-null" +subcategory: "" +description: |- + The null_data_source data source implements the standard data source lifecycle but does not + interact with any external APIs. + Historically, the null_data_source was typically used to construct intermediate values to re-use elsewhere in configuration. The + same can now be achieved using locals https://developer.hashicorp.com/terraform/language/values/locals or the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data in Terraform 1.4 and later. +--- + +# null_data_source + +The `null_data_source` data source implements the standard data source lifecycle but does not +interact with any external APIs. + +Historically, the `null_data_source` was typically used to construct intermediate values to re-use elsewhere in configuration. The +same can now be achieved using [locals](https://developer.hashicorp.com/terraform/language/values/locals) or the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) in Terraform 1.4 and later. + +## Example Usage + +```terraform +resource "aws_instance" "green" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +resource "aws_instance" "blue" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +data "null_data_source" "values" { + inputs = { + all_server_ids = concat( + aws_instance.green[*].id, + aws_instance.blue[*].id, + ) + all_server_ips = concat( + aws_instance.green[*].private_ip, + aws_instance.blue[*].private_ip, + ) + } +} + +resource "aws_elb" "main" { + instances = data.null_data_source.values.outputs["all_server_ids"] + + # ... + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +output "all_server_ids" { + value = data.null_data_source.values.outputs["all_server_ids"] +} + +output "all_server_ips" { + value = data.null_data_source.values.outputs["all_server_ips"] +} +``` + + +## Schema + +### Optional + +- `has_computed_default` (String) If set, its literal value will be stored and returned. If not, its value defaults to `"default"`. This argument exists primarily for testing and has little practical use. +- `inputs` (Map of String) A map of arbitrary strings that is copied into the `outputs` attribute, and accessible directly for interpolation. + +### Read-Only + +- `id` (String, Deprecated) This attribute is only present for some legacy compatibility issues and should not be used. It will be removed in a future version. +- `outputs` (Map of String) After the data source is "read", a copy of the `inputs` map. +- `random` (String) A random value. This is primarily for testing and has little practical use; prefer the [hashicorp/random provider](https://registry.terraform.io/providers/hashicorp/random) for more practical random number use-cases. + +-- docs/index.md -- +--- +page_title: "Provider: Null" +description: |- + The null provider provides no-op constructs that can be useful helpers in tricky cases. +--- + +# Null Provider + +The `null` provider is a rather-unusual provider that has constructs that +intentionally do nothing. This may sound strange, and indeed these constructs +do not need to be used in most cases, but they can be useful in various +situations to help orchestrate tricky behavior or work around limitations. + +The documentation of each feature of this provider, accessible via the +navigation, gives examples of situations where these constructs may prove +useful. + +Usage of the `null` provider can make a Terraform configuration harder to +understand. While it can be useful in certain cases, it should be applied with +care and other solutions preferred when available. + + +## Schema +-- docs/resources/resource.md -- +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "null_resource Resource - terraform-provider-null" +subcategory: "" +description: |- + The null_resource resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the terraform_data resource type https://developer.hashicorp.com/terraform/language/resources/terraform-data instead. + The triggers argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. +--- + +# null_resource + +The `null_resource` resource implements the standard resource lifecycle but takes no further action. On Terraform 1.4 and later, use the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) instead. + +The `triggers` argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced. + +## Example Usage + +```terraform +resource "aws_instance" "cluster" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +# The primary use-case for the null resource is as a do-nothing container +# for arbitrary actions taken by a provisioner. +# +# In this example, three EC2 instances are created and then a +# null_resource instance is used to gather data about all three +# and execute a single action that affects them all. Due to the triggers +# map, the null_resource will be replaced each time the instance ids +# change, and thus the remote-exec provisioner will be re-run. +resource "null_resource" "cluster" { + # Changes to any instance of the cluster requires re-provisioning + triggers = { + cluster_instance_ids = join(",", aws_instance.cluster[*].id) + } + + # Bootstrap script can run on any instance of the cluster + # So we just choose the first in this case + connection { + host = element(aws_instance.cluster[*].public_ip, 0) + } + + provisioner "remote-exec" { + # Bootstrap script called with private_ip of each node in the cluster + inline = [ + "bootstrap-cluster.sh ${join(" ", + aws_instance.cluster[*].private_ip)}", + ] + } +} +``` + + +## Schema + +### Optional + +- `triggers` (Map of String) A map of arbitrary strings that, when changed, will force the null resource to be replaced, re-running any associated provisioners. + +### Read-Only + +- `id` (String) This is set to a random value at create time. + +-- examples/data-sources/null_data_source/data-source.tf -- +resource "aws_instance" "green" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +resource "aws_instance" "blue" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +data "null_data_source" "values" { + inputs = { + all_server_ids = concat( + aws_instance.green[*].id, + aws_instance.blue[*].id, + ) + all_server_ips = concat( + aws_instance.green[*].private_ip, + aws_instance.blue[*].private_ip, + ) + } +} + +resource "aws_elb" "main" { + instances = data.null_data_source.values.outputs["all_server_ids"] + + # ... + listener { + instance_port = 8000 + instance_protocol = "http" + lb_port = 80 + lb_protocol = "http" + } +} + +output "all_server_ids" { + value = data.null_data_source.values.outputs["all_server_ids"] +} + +output "all_server_ips" { + value = data.null_data_source.values.outputs["all_server_ips"] +} +-- examples/resources/null_resource/resource.tf -- +resource "aws_instance" "cluster" { + count = 3 + ami = "ami-0dcc1e21636832c5d" + instance_type = "m5.large" + + # ... +} + +# The primary use-case for the null resource is as a do-nothing container +# for arbitrary actions taken by a provisioner. +# +# In this example, three EC2 instances are created and then a +# null_resource instance is used to gather data about all three +# and execute a single action that affects them all. Due to the triggers +# map, the null_resource will be replaced each time the instance ids +# change, and thus the remote-exec provisioner will be re-run. +resource "null_resource" "cluster" { + # Changes to any instance of the cluster requires re-provisioning + triggers = { + cluster_instance_ids = join(",", aws_instance.cluster[*].id) + } + + # Bootstrap script can run on any instance of the cluster + # So we just choose the first in this case + connection { + host = element(aws_instance.cluster[*].public_ip, 0) + } + + provisioner "remote-exec" { + # Bootstrap script called with private_ip of each node in the cluster + inline = [ + "bootstrap-cluster.sh ${join(" ", + aws_instance.cluster[*].private_ip)}", + ] + } +} +-- go.mod -- +module github.com/terraform-providers/terraform-provider-null + +go 1.20 + +require ( + github.com/hashicorp/terraform-plugin-framework v1.4.2 + github.com/hashicorp/terraform-plugin-go v0.19.0 + github.com/hashicorp/terraform-plugin-testing v1.5.1 +) + +require ( + github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 // indirect + github.com/agext/levenshtein v1.2.2 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.5.1 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hc-install v0.6.0 // indirect + github.com/hashicorp/hcl/v2 v2.18.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.19.0 // indirect + github.com/hashicorp/terraform-json v0.17.1 // indirect + github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.2 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.14.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 // indirect + google.golang.org/grpc v1.57.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) +-- go.sum -- +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95 h1:KLq8BE0KwCL+mmXnjLWEAOYO+2l2AE4YMmqG1ZpZHBs= +github.com/ProtonMail/go-crypto v0.0.0-20230717121422-5aa5874ade95/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= +github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-git/v5 v5.8.1 h1:Zo79E4p7TRk0xoRgMq0RShiTHGKcKI4+DI6BfJc/Q+A= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= +github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k= +github.com/hashicorp/go-plugin v1.5.1/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.6.0 h1:fDHnU7JNFNSQebVKYhHZ0va1bC6SrPQ8fpebsvNr2w4= +github.com/hashicorp/hc-install v0.6.0/go.mod h1:10I912u3nntx9Umo1VAeYPUUuehk0aRQJYpMwbX5wQA= +github.com/hashicorp/hcl/v2 v2.18.0 h1:wYnG7Lt31t2zYkcquwgKo6MWXzRUDIeIVU5naZwHLl8= +github.com/hashicorp/hcl/v2 v2.18.0/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.19.0 h1:FpqZ6n50Tk95mItTSS9BjeOVUb4eg81SpgVtZNNtFSM= +github.com/hashicorp/terraform-exec v0.19.0/go.mod h1:tbxUpe3JKruE9Cuf65mycSIT8KiNPZ0FkuTE3H4urQg= +github.com/hashicorp/terraform-json v0.17.1 h1:eMfvh/uWggKmY7Pmb3T85u86E2EQg6EQHgyRwf3RkyA= +github.com/hashicorp/terraform-json v0.17.1/go.mod h1:Huy6zt6euxaY9knPAFKjUITn8QxUFIe9VuSzb4zn/0o= +github.com/hashicorp/terraform-plugin-framework v1.4.2 h1:P7a7VP1GZbjc4rv921Xy5OckzhoiO3ig6SGxwelD2sI= +github.com/hashicorp/terraform-plugin-framework v1.4.2/go.mod h1:GWl3InPFZi2wVQmdVnINPKys09s9mLmTZr95/ngLnbY= +github.com/hashicorp/terraform-plugin-go v0.19.0 h1:BuZx/6Cp+lkmiG0cOBk6Zps0Cb2tmqQpDM3iAtnhDQU= +github.com/hashicorp/terraform-plugin-go v0.19.0/go.mod h1:EhRSkEPNoylLQntYsk5KrDHTZJh9HQoumZXbOGOXmec= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 h1:wcOKYwPI9IorAJEBLzgclh3xVolO7ZorYd6U1vnok14= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0/go.mod h1:qH/34G25Ugdj5FcM95cSoXzUgIbgfhVLXCcEcYaMwq8= +github.com/hashicorp/terraform-plugin-testing v1.5.1 h1:T4aQh9JAhmWo4+t1A7x+rnxAJHCDIYW9kXyo4sVO92c= +github.com/hashicorp/terraform-plugin-testing v1.5.1/go.mod h1:dg8clO6K59rZ8w9EshBmDp1CxTIPu3yA4iaDpX1h5u0= +github.com/hashicorp/terraform-registry-address v0.2.2 h1:lPQBg403El8PPicg/qONZJDC6YlgCVbWDtNmmZKtBno= +github.com/hashicorp/terraform-registry-address v0.2.2/go.mod h1:LtwNbCihUoUZ3RYriyS2wF/lGPB6gF9ICLRtuDk7hSo= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +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.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.14.0 h1:/Xrd39K7DXbHzlisFP9c4pHao4yyf+/Ug9LEz+Y/yhc= +github.com/zclconf/go-cty v1.14.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +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.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= +golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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/sync v0.1.0/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +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/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19 h1:0nDDozoAU19Qb2HwhXadU8OcsiO/09cnTqhUtq2MEOM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= +google.golang.org/grpc v1.57.0 h1:kfzNeI/klCGD2YPMUlaGNT3pxvYfga7smW3Vth8Zsiw= +google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt16Mo= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +-- internal/planmodifiers/attribute.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package planmodifiers + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +func RequiresReplaceIfValuesNotNull() planmodifier.Map { + return requiresReplaceIfValuesNotNullModifier{} +} + +type requiresReplaceIfValuesNotNullModifier struct{} + +func (r requiresReplaceIfValuesNotNullModifier) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) { + if req.State.Raw.IsNull() { + // if we're creating the resource, no need to delete and + // recreate it + return + } + + if req.Plan.Raw.IsNull() { + // if we're deleting the resource, no need to delete and + // recreate it + return + } + + // If there are no differences, do not mark the resource for replacement + // and ensure the plan matches the configuration. + if req.ConfigValue.Equal(req.StateValue) { + return + } + + if req.StateValue.IsNull() { + // terraform-plugin-sdk would store maps as null if all keys had null + // values. To prevent unintentional replacement plans when migrating + // to terraform-plugin-framework, only trigger replacement when the + // prior state (map) is null and when there are not null map values. + allNullValues := true + + for _, configValue := range req.ConfigValue.Elements() { + if !configValue.IsNull() { + allNullValues = false + } + } + + if allNullValues { + return + } + } else { + // terraform-plugin-sdk would completely omit storing map keys with + // null values, so this also must prevent unintentional replacement + // in that case as well. + allNewNullValues := true + + configMap := req.ConfigValue + stateMap := req.StateValue + + for configKey, configValue := range configMap.Elements() { + stateValue, ok := stateMap.Elements()[configKey] + + // If the key doesn't exist in state and the config value is + // null, do not trigger replacement. + if !ok && configValue.IsNull() { + continue + } + + // If the state value exists and it is equal to the config value, + // do not trigger replacement. + if configValue.Equal(stateValue) { + continue + } + + allNewNullValues = false + break + } + + for stateKey := range stateMap.Elements() { + _, ok := configMap.Elements()[stateKey] + + // If the key doesn't exist in the config, but there is a state + // value, trigger replacement. + if !ok { + allNewNullValues = false + break + } + } + + if allNewNullValues { + return + } + } + + resp.RequiresReplace = true +} + +// Description returns a human-readable description of the plan modifier. +func (r requiresReplaceIfValuesNotNullModifier) Description(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (r requiresReplaceIfValuesNotNullModifier) MarkdownDescription(ctx context.Context) string { + return "If the value of this attribute changes, Terraform will destroy and recreate the resource." +} +-- internal/provider/data_source.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "math/rand" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = (*nullDataSource)(nil) +) + +func NewNullDataSource() datasource.DataSource { + return &nullDataSource{} +} + +type nullDataSource struct{} + +func (n *nullDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_data_source" +} + +func (n *nullDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + DeprecationMessage: "The null_data_source was historically used to construct intermediate values to re-use elsewhere " + + "in configuration, the same can now be achieved using locals or the terraform_data resource type in Terraform 1.4 and later.", + Description: `The ` + "`null_data_source`" + ` data source implements the standard data source lifecycle but does not +interact with any external APIs. + +Historically, the ` + "`null_data_source`" + ` was typically used to construct intermediate values to re-use elsewhere in configuration. The +same can now be achieved using [locals](https://developer.hashicorp.com/terraform/language/values/locals) ` + + `or the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) in Terraform 1.4 and later.`, + Attributes: map[string]schema.Attribute{ + "inputs": schema.MapAttribute{ + Description: "A map of arbitrary strings that is copied into the `outputs` attribute, and accessible directly for interpolation.", + ElementType: types.StringType, + Optional: true, + }, + "outputs": schema.MapAttribute{ + Description: "After the data source is \"read\", a copy of the `inputs` map.", + ElementType: types.StringType, + Computed: true, + }, + "random": schema.StringAttribute{ + Description: "A random value. This is primarily for testing and has little practical use; prefer the [hashicorp/random provider](https://registry.terraform.io/providers/hashicorp/random) for more practical random number use-cases.", + Computed: true, + }, + "has_computed_default": schema.StringAttribute{ + Description: "If set, its literal value will be stored and returned. If not, its value defaults to `\"default\"`. This argument exists primarily for testing and has little practical use.", + Optional: true, + Computed: true, + }, + + "id": schema.StringAttribute{ + Description: "This attribute is only present for some legacy compatibility issues and should not be used. It will be removed in a future version.", + Computed: true, + DeprecationMessage: "This attribute is only present for some legacy compatibility issues and should not be used. It will be removed in a future version.", + }, + }, + } +} + +func (n *nullDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var config nullDataSourceModelV0 + + diags := req.Config.Get(ctx, &config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + config.Outputs = config.Inputs + config.Random = types.StringValue(fmt.Sprintf("%d", rand.Int())) + + if config.HasComputedDefault.IsNull() { + config.HasComputedDefault = types.StringValue("default") + } + config.ID = types.StringValue("static") + diags = resp.State.Set(ctx, config) + resp.Diagnostics.Append(diags...) +} + +type nullDataSourceModelV0 struct { + Inputs types.Map `tfsdk:"inputs"` + Outputs types.Map `tfsdk:"outputs"` + Random types.String `tfsdk:"random"` + HasComputedDefault types.String `tfsdk:"has_computed_default"` + ID types.String `tfsdk:"id"` +} +-- internal/provider/data_source_test.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccDataSource_basic(t *testing.T) { + dsn := "data.null_data_source.test" + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dsn, "random"), + resource.TestCheckResourceAttr(dsn, "has_computed_default", "default"), + ), + }, + }, + }) +} + +func TestAccDataSource_inputs(t *testing.T) { + dsn := "data.null_data_source.test" + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccDataSourceConfig_inputs, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dsn, "random"), + resource.TestCheckResourceAttr(dsn, "outputs.foo", "bar"), + ), + }, + }, + }) +} + +const testAccDataSourceConfig_basic = ` +data "null_data_source" "test" { +} +` + +const testAccDataSourceConfig_inputs = ` +data "null_data_source" "test" { + inputs = { + foo = "bar" + } +}` +-- internal/provider/provider.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +var _ provider.Provider = (*nullProvider)(nil) + +func New() provider.Provider { + return &nullProvider{} +} + +type nullProvider struct{} + +func (p *nullProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "null" +} + +func (p *nullProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + +} + +func (p *nullProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewNullDataSource, + } +} + +func (p *nullProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewNullResource, + } +} + +func (p *nullProvider) Schema(context.Context, provider.SchemaRequest, *provider.SchemaResponse) { +} +-- internal/provider/provider_test.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func protoV5ProviderFactories() map[string]func() (tfprotov5.ProviderServer, error) { + return map[string]func() (tfprotov5.ProviderServer, error){ + "null": providerserver.NewProtocol5WithError(New()), + } +} + +func providerVersion311() map[string]resource.ExternalProvider { + return map[string]resource.ExternalProvider{ + "null": { + VersionConstraint: "3.1.1", + Source: "hashicorp/null", + }, + } +} + +func testCheckAttributeValuesDiffer(i *string, j *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if testStringValue(i) == testStringValue(j) { + return fmt.Errorf("attribute values are the same") + } + + return nil + } +} + +func testCheckAttributeValuesEqual(i *string, j *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + if testStringValue(i) != testStringValue(j) { + return fmt.Errorf("attribute values are different") + } + + return nil + } +} + +//nolint:unparam +func testExtractResourceAttr(resourceName string, attributeName string, attributeValue *string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + + if !ok { + return fmt.Errorf("resource name %s not found in state", resourceName) + } + + attrValue, ok := rs.Primary.Attributes[attributeName] + + if !ok { + return fmt.Errorf("attribute %s not found in resource %s state", attributeName, resourceName) + } + + *attributeValue = attrValue + + return nil + } +} + +func testStringValue(sPtr *string) string { + if sPtr == nil { + return "" + } + + return *sPtr +} +-- internal/provider/resource.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "fmt" + "math/rand" + + "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/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/terraform-providers/terraform-provider-null/internal/planmodifiers" +) + +var ( + _ resource.Resource = (*nullResource)(nil) +) + +func NewNullResource() resource.Resource { + return &nullResource{} +} + +type nullResource struct{} + +func (n *nullResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_resource" +} + +func (n *nullResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: `The ` + "`null_resource`" + ` resource implements the standard resource lifecycle but takes no further action. ` + + `On Terraform 1.4 and later, use the [terraform_data resource type](https://developer.hashicorp.com/terraform/language/resources/terraform-data) instead. + +The ` + "`triggers`" + ` argument allows specifying an arbitrary set of values that, when changed, will cause the resource to be replaced.`, + Attributes: map[string]schema.Attribute{ + "triggers": schema.MapAttribute{ + Description: "A map of arbitrary strings that, when changed, will force the null resource to be replaced, re-running any associated provisioners.", + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + planmodifiers.RequiresReplaceIfValuesNotNull(), + }, + }, + + "id": schema.StringAttribute{ + Description: "This is set to a random value at create time.", + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +func (n *nullResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var model nullModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) + + diags := resp.State.SetAttribute(ctx, path.Root("id"), fmt.Sprintf("%d", rand.Int())) + resp.Diagnostics.Append(diags...) +} + +func (n *nullResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + +} + +func (n *nullResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var model nullModelV0 + + resp.Diagnostics.Append(req.Plan.Get(ctx, &model)...) + + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &model)...) +} + +func (n *nullResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + +} + +type nullModelV0 struct { + Triggers types.Map `tfsdk:"triggers"` + ID types.String `tfsdk:"id"` +} +-- internal/provider/resource_test.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccResource_basic(t *testing.T) { + dsn := "null_resource.test" + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Steps: []resource.TestStep{ + { + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dsn, "id"), + ), + }, + }, + }) +} + +func TestAccResource_basic_FrameworkMigration(t *testing.T) { + dsn := "null_resource.test" + + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dsn, "id"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + PlanOnly: true, + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(dsn, "id"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_EmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_EmptyMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_NullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_NullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_NullValues(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_Value(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Keep_Values(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_EmptyMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_NullValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_ValueToEmptyMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = {} + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_ValueToNullMap(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: testAccResourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_ValueToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_Replace_ValueToNewValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapToNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key" = "123" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapToMultipleNullValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapToMultipleValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = null + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "0"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesEqual(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +func TestAccResource_Triggers_FrameworkMigration_NullMapValueToValue(t *testing.T) { + var id1, id2 string + + resource.ParallelTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + ExternalProviders: providerVersion311(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = null + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id1), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "1"), + ), + }, + { + ProtoV5ProviderFactories: protoV5ProviderFactories(), + Config: `resource "null_resource" "test" { + triggers = { + "key1" = "123" + "key2" = "456" + } + }`, + Check: resource.ComposeTestCheckFunc( + testExtractResourceAttr("null_resource.test", "id", &id2), + testCheckAttributeValuesDiffer(&id1, &id2), + resource.TestCheckResourceAttr("null_resource.test", "triggers.%", "2"), + ), + }, + }, + }) +} + +const testAccResourceConfig_basic = ` +resource "null_resource" "test" { +} +` +-- main.go -- +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/terraform-providers/terraform-provider-null/internal/provider" +) + +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + err := providerserver.Serve(context.Background(), provider.New, providerserver.ServeOpts{ + Address: "registry.terraform.io/hashicorp/null", + Debug: debug, + ProtocolVersion: 5, + }) + if err != nil { + log.Fatal(err) + } +} \ No newline at end of file diff --git a/cmd/tfplugindocs/version.go b/cmd/tfplugindocs/version.go deleted file mode 100644 index 68dc6cee..00000000 --- a/cmd/tfplugindocs/version.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package main - -var ( - // These vars will be set by goreleaser. - version string = `dev` - commit string = `` -) diff --git a/go.mod b/go.mod index 6eee75d9..a33126f1 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/hashicorp/terraform-json v0.18.0 github.com/mattn/go-colorable v0.1.13 github.com/mitchellh/cli v1.1.5 + github.com/rogpeppe/go-internal v1.11.0 github.com/russross/blackfriday v1.6.0 github.com/zclconf/go-cty v1.14.1 golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df @@ -43,4 +44,5 @@ require ( golang.org/x/crypto v0.13.0 // indirect golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.12.0 // indirect + golang.org/x/tools v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 2a4e2ffd..0e990701 100644 --- a/go.sum +++ b/go.sum @@ -94,7 +94,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= @@ -174,6 +175,7 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 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/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= diff --git a/internal/cmd/run.go b/internal/cmd/run.go index 82f47cde..bf0266b4 100644 --- a/internal/cmd/run.go +++ b/internal/cmd/run.go @@ -8,7 +8,10 @@ import ( "io" "os" + "github.com/mattn/go-colorable" "github.com/mitchellh/cli" + + "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs/build" ) type commonCmd struct { @@ -100,3 +103,16 @@ func Run(name, version string, args []string, stdin io.Reader, stdout, stderr io } return exitCode } + +// Main has the required function signature for use with testscript +func Main() int { + + return Run( + "tfplugindocs", + build.GetVersion(), + os.Args[1:], + os.Stdin, + colorable.NewColorableStdout(), + colorable.NewColorableStderr(), + ) +} diff --git a/internal/provider/generate.go b/internal/provider/generate.go index b4c3293f..d6aafe25 100644 --- a/internal/provider/generate.go +++ b/internal/provider/generate.go @@ -429,7 +429,7 @@ func (g *generator) renderMissingDocs(providerName string, providerSchema *tfjso func (g *generator) renderStaticWebsite(providerName string, providerSchema *tfjson.ProviderSchema) error { g.infof("cleaning rendered website dir") dirEntry, err := os.ReadDir(g.ProviderDocsDir()) - if err != nil { + if err != nil && !os.IsNotExist(err) { return err } diff --git a/internal/provider/template_test.go b/internal/provider/template_test.go index 349f6892..82bcc3b5 100644 --- a/internal/provider/template_test.go +++ b/internal/provider/template_test.go @@ -4,9 +4,11 @@ package provider import ( + "strings" "testing" "github.com/google/go-cmp/cmp" + tfjson "github.com/hashicorp/terraform-json" ) func TestRenderStringTemplate(t *testing.T) { @@ -21,6 +23,8 @@ Upper: {{ lower .Text }} Title: {{ title .Text }} Prefixlines: {{ prefixlines " " .MultiLineTest }} +Printf tffile: {{ printf "{{tffile %q}}" .Code }} +tffile: {{ tffile .Code }} ` expectedString := ` @@ -33,20 +37,109 @@ Title: My Odly Cased String Prefixlines: This text used multiple lines +Printf tffile: {{tffile "provider.tf"}} +tffile: terraform +provider "scaffolding" { + # example configuration here +} + ` - result, err := renderStringTemplate("test-provider-dir", "testTemplate", template, struct { + result, err := renderStringTemplate("testdata/test-provider-dir", "testTemplate", template, struct { Text string MultiLineTest string + Code string }{ Text: "my Odly cAsed striNg", MultiLineTest: `This text used multiple lines`, + Code: "provider.tf", }) if err != nil { t.Error(err) } - if !cmp.Equal(expectedString, result) { - t.Errorf("expected: %+v, got: %+v", expectedString, result) + cleanedResult := strings.ReplaceAll(result, "```", "") + if !cmp.Equal(expectedString, cleanedResult) { + t.Errorf("expected: %+v, got: %+v", expectedString, cleanedResult) + } +} + +func TestResourceTemplate_Render(t *testing.T) { + t.Parallel() + + template := ` +Printf tffile: {{ printf "{{tffile %q}}" .ExampleFile }} +tffile: {{ tffile .ExampleFile }} +` + expectedString := ` +Printf tffile: {{tffile "provider.tf"}} +tffile: terraform +provider "scaffolding" { + # example configuration here +} + +` + + tpl := resourceTemplate(template) + + schema := tfjson.Schema{ + Version: 3, + Block: &tfjson.SchemaBlock{ + Attributes: nil, + NestedBlocks: nil, + Description: "", + DescriptionKind: "", + Deprecated: false, + }, + } + + result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "test-provider", "Resource", "provider.tf", "provider.tf", &schema) + if err != nil { + t.Error(err) + } + + cleanedResult := strings.ReplaceAll(result, "```", "") + if !cmp.Equal(expectedString, cleanedResult) { + t.Errorf("expected: %+v, got: %+v", expectedString, cleanedResult) + } +} + +func TestProviderTemplate_Render(t *testing.T) { + t.Parallel() + + template := ` +Printf tffile: {{ printf "{{tffile %q}}" .ExampleFile }} +tffile: {{ tffile .ExampleFile }} +` + expectedString := ` +Printf tffile: {{tffile "provider.tf"}} +tffile: terraform +provider "scaffolding" { + # example configuration here +} + +` + + tpl := providerTemplate(template) + + schema := tfjson.Schema{ + Version: 3, + Block: &tfjson.SchemaBlock{ + Attributes: nil, + NestedBlocks: nil, + Description: "", + DescriptionKind: "", + Deprecated: false, + }, + } + + result, err := tpl.Render("testdata/test-provider-dir", "testTemplate", "test-provider", "provider.tf", &schema) + if err != nil { + t.Error(err) + } + + cleanedResult := strings.ReplaceAll(result, "```", "") + if !cmp.Equal(expectedString, cleanedResult) { + t.Errorf("expected: %+v, got: %+v", expectedString, cleanedResult) } } diff --git a/internal/provider/testdata/test-provider-dir/provider.tf b/internal/provider/testdata/test-provider-dir/provider.tf new file mode 100644 index 00000000..942db457 --- /dev/null +++ b/internal/provider/testdata/test-provider-dir/provider.tf @@ -0,0 +1,3 @@ +provider "scaffolding" { + # example configuration here +}