feat: add data sources and organize code (#148)
* feat: add data sources and organize code * fix: ignore unused data source example * fix: add ignore to docs * fix: update CI test to new location * fix: update CI to run acceptance tests * fix: run tests in nix * fix: switch terraform version based on matrix * fix: remove equal sign --------- Signed-off-by: matttrach <matt.trachier@suse.com>
This commit is contained in:
parent
6a43cc60fc
commit
b0b84c1409
|
|
@ -98,12 +98,23 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
# list whatever Terraform versions here you would like to support
|
# list whatever Terraform versions here you would like to support
|
||||||
terraform:
|
terraform:
|
||||||
- '1.8.*'
|
# the latest patch of each version will be used
|
||||||
- '1.9.*'
|
- '1.5.0'
|
||||||
- '1.10.*'
|
- '1.6.0'
|
||||||
- '1.11.*'
|
- '1.7.0'
|
||||||
- '1.12.*'
|
- '1.8.0'
|
||||||
|
- '1.9.0'
|
||||||
|
- '1.10.0'
|
||||||
|
- '1.11.0'
|
||||||
|
- '1.12.0'
|
||||||
|
- '1.13.0'
|
||||||
steps:
|
steps:
|
||||||
|
- name: install-nix
|
||||||
|
run: |
|
||||||
|
curl -L https://nixos.org/nix/install | sh
|
||||||
|
source /home/runner/.nix-profile/etc/profile.d/nix.sh
|
||||||
|
nix --version
|
||||||
|
which nix
|
||||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 https://github.com/actions/checkout
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 https://github.com/actions/checkout
|
||||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 https://github.com/actions/setup-go
|
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 https://github.com/actions/setup-go
|
||||||
with:
|
with:
|
||||||
|
|
@ -113,9 +124,12 @@ jobs:
|
||||||
with:
|
with:
|
||||||
terraform_version: ${{ matrix.terraform }}
|
terraform_version: ${{ matrix.terraform }}
|
||||||
terraform_wrapper: false
|
terraform_wrapper: false
|
||||||
- run: go mod download
|
- name: run tests
|
||||||
- run: go install gotest.tools/gotestsum@ddd0b05a6878e2e8257a2abe6e7df66cebc53d0e # v1.12.3
|
shell: /home/runner/.nix-profile/bin/nix develop --ignore-environment --extra-experimental-features nix-command --extra-experimental-features flakes --keep HOME --keep NIX_SSL_CERT_FILE --keep NIX_ENV_LOADED --keep TERM --command bash -e {0}
|
||||||
- run: make test
|
run: |
|
||||||
- run: terraform fmt -check -recursive
|
go mod download
|
||||||
- run: pushd ./examples/use-cases/basic; terraform init -input=false; popd
|
go install gotest.tools/gotestsum@ddd0b05a6878e2e8257a2abe6e7df66cebc53d0e # v1.12.3
|
||||||
- run: pushd ./examples/use-cases/basic; terraform validate -no-color; popd
|
terraform fmt -check -recursive
|
||||||
|
tfswitch -s ${{ matrix.terraform }}
|
||||||
|
make test
|
||||||
|
make testacc
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
version: "2"
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# https://goreleaser.com for documentation
|
# https://goreleaser.com for documentation
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,12 @@ test:
|
||||||
|
|
||||||
testacc: build
|
testacc: build
|
||||||
export REPO_ROOT="../../../."; \
|
export REPO_ROOT="../../../."; \
|
||||||
export TF_CLI_CONFIG_FILE="../../../test/.terraformrc"; \
|
|
||||||
pushd ./test; \
|
pushd ./test; \
|
||||||
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
||||||
popd;
|
popd;
|
||||||
|
|
||||||
debug: build
|
debug: build
|
||||||
export REPO_ROOT="../../../."; \
|
export REPO_ROOT="../../../."; \
|
||||||
export TF_CLI_CONFIG_FILE="../../../test/.terraformrc"; \
|
|
||||||
export TF_LOG=DEBUG; \
|
export TF_LOG=DEBUG; \
|
||||||
pushd ./test; \
|
pushd ./test; \
|
||||||
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
gotestsum --format standard-verbose --jsonfile report.json --post-run-command "./summarize.sh" -- ./... -v -p=1 -timeout=300s; \
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
---
|
||||||
|
# generated by https://github.com/hashicorp/terraform-plugin-docs
|
||||||
|
page_title: "file_local Data Source - file"
|
||||||
|
subcategory: ""
|
||||||
|
description: |-
|
||||||
|
Local File DataSource
|
||||||
|
---
|
||||||
|
|
||||||
|
# file_local (Data Source)
|
||||||
|
|
||||||
|
Local File DataSource
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```terraform
|
||||||
|
# tflint-ignore: terraform_unused_declarations
|
||||||
|
data "file_local" "basic_example" {
|
||||||
|
name = "example.txt"
|
||||||
|
directory = "."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- schema generated by tfplugindocs -->
|
||||||
|
## Schema
|
||||||
|
|
||||||
|
### Required
|
||||||
|
|
||||||
|
- `directory` (String) The directory where the file exists.
|
||||||
|
- `name` (String) File name, required.
|
||||||
|
|
||||||
|
### Optional
|
||||||
|
|
||||||
|
- `hmac_secret_key` (String, Sensitive) A string used to generate the file identifier, you can pass this value in the environment variable `TF_FILE_HMAC_SECRET_KEY`.
|
||||||
|
|
||||||
|
### Read-Only
|
||||||
|
|
||||||
|
- `contents` (String) The file contents.
|
||||||
|
- `id` (String) Identifier derived from sha256+HMAC hash of file contents.
|
||||||
|
- `permissions` (String) The file permissions.
|
||||||
|
|
@ -12,8 +12,6 @@ description: |-
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# this provider has no configuration currently
|
# this provider has no configuration currently
|
||||||
provider "file" {}
|
provider "file" {}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,6 @@ Local File resource
|
||||||
## Example Usage
|
## Example Usage
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
resource "file_local" "basic_example" {
|
resource "file_local" "basic_example" {
|
||||||
name = "example.txt"
|
name = "example.txt"
|
||||||
contents = "An example implementation writing a local file."
|
contents = "An example implementation writing a local file."
|
||||||
|
|
@ -59,8 +57,6 @@ Import is supported using the following syntax:
|
||||||
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# echo "Test data" > data.txt
|
# echo "Test data" > data.txt
|
||||||
# FILEPATH="./data.txt"
|
# FILEPATH="./data.txt"
|
||||||
# TF_FILE_HMAC_SECRET_KEY="super-secret-key"
|
# TF_FILE_HMAC_SECRET_KEY="super-secret-key"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
data "file_local" "example" {
|
|
||||||
configurable_attribute = "some-value"
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
# tflint-ignore: terraform_unused_declarations
|
||||||
|
data "file_local" "basic_example" {
|
||||||
|
name = "example.txt"
|
||||||
|
directory = "."
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_version = ">= 1.5.0"
|
required_version = ">= 1.5.0"
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# this provider has no configuration currently
|
# this provider has no configuration currently
|
||||||
provider "file" {}
|
provider "file" {}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_version = ">= 1.5.0"
|
required_version = ">= 1.5.0"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# echo "Test data" > data.txt
|
# echo "Test data" > data.txt
|
||||||
# FILEPATH="./data.txt"
|
# FILEPATH="./data.txt"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
resource "file_local" "basic_example" {
|
resource "file_local" "basic_example" {
|
||||||
name = "example.txt"
|
name = "example.txt"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_version = ">= 1.5.0"
|
required_version = ">= 1.5.0"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
provider_installation {
|
provider_installation {
|
||||||
dev_overrides {
|
dev_overrides {
|
||||||
"rancher/file" = "../../../bin"
|
"rancher/file" = "../../../../bin"
|
||||||
}
|
}
|
||||||
direct {
|
direct {
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
provider "file" {}
|
provider "file" {}
|
||||||
|
|
||||||
|
|
@ -12,3 +12,8 @@ resource "file_local" "basic" {
|
||||||
directory = local.directory
|
directory = local.directory
|
||||||
contents = "An example of the \"most basic\" implementation writing a local file."
|
contents = "An example of the \"most basic\" implementation writing a local file."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "file_local" "basic" {
|
||||||
|
name = file_local.basic.name
|
||||||
|
directory = file_local.basic.directory
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
|
||||||
|
output "file_resource" {
|
||||||
|
value = jsonencode(file_local.basic)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "file_data_source" {
|
||||||
|
value = jsonencode(data.file_local.basic)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
variable "directory" {
|
variable "directory" {
|
||||||
type = string
|
type = string
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
required_version = ">= 1.5.0"
|
required_version = ">= 1.5.0"
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
provider_installation {
|
provider_installation {
|
||||||
dev_overrides {
|
dev_overrides {
|
||||||
"rancher/file" = "../../../bin"
|
"rancher/file" = "../../../../bin"
|
||||||
}
|
}
|
||||||
direct {
|
direct {
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
|
|
||||||
terraform {
|
terraform {
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
provider "file" {}
|
provider "file" {}
|
||||||
|
|
||||||
|
|
@ -26,3 +26,8 @@ resource "file_local" "protected_env" {
|
||||||
directory = local.directory
|
directory = local.directory
|
||||||
contents = "An example implementation of a protected file."
|
contents = "An example implementation of a protected file."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data "file_local" "protected" {
|
||||||
|
name = file_local.protected.name
|
||||||
|
directory = file_local.protected.directory
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
|
||||||
|
output "file_resource" {
|
||||||
|
value = jsonencode(file_local.protected)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "file_resource_env" {
|
||||||
|
value = jsonencode(file_local.protected_env)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
output "file_data_source" {
|
||||||
|
value = jsonencode(data.file_local.protected)
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
variable "directory" {
|
variable "directory" {
|
||||||
type = string
|
type = string
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
|
||||||
|
|
||||||
|
terraform {
|
||||||
|
required_version = ">= 1.5.0"
|
||||||
|
required_providers {
|
||||||
|
file = {
|
||||||
|
source = "rancher/file"
|
||||||
|
version = ">= 0.0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -20,11 +20,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1756128520,
|
"lastModified": 1756819007,
|
||||||
"narHash": "sha256-R94HxJBi+RK1iCm8Y4Q9pdrHZl0GZoDPIaYwjxRNPh4=",
|
"narHash": "sha256-12V64nKG/O/guxSYnr5/nq1EfqwJCdD2+cIGmhz3nrE=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c53baa6685261e5253a1c355a1b322f82674a824",
|
"rev": "aaff8c16d7fc04991cac6245bee1baa31f72b1e1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
|
||||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ensure provider defined types fully satisfy framework interfaces.
|
|
||||||
var _ datasource.DataSource = &ExampleDataSource{}
|
|
||||||
|
|
||||||
func NewExampleDataSource() datasource.DataSource {
|
|
||||||
return &ExampleDataSource{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExampleDataSource defines the data source implementation.
|
|
||||||
type ExampleDataSource struct {
|
|
||||||
client *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExampleDataSourceModel describes the data source data model.
|
|
||||||
type ExampleDataSourceModel struct {
|
|
||||||
ConfigurableAttribute types.String `tfsdk:"configurable_attribute"`
|
|
||||||
Id types.String `tfsdk:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ExampleDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
|
||||||
resp.TypeName = req.ProviderTypeName + "_example"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ExampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
|
||||||
resp.Schema = schema.Schema{
|
|
||||||
// This description is used by the documentation generator and the language server.
|
|
||||||
MarkdownDescription: "Example data source",
|
|
||||||
|
|
||||||
Attributes: map[string]schema.Attribute{
|
|
||||||
"configurable_attribute": schema.StringAttribute{
|
|
||||||
MarkdownDescription: "Example configurable attribute",
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
"id": schema.StringAttribute{
|
|
||||||
MarkdownDescription: "Example identifier",
|
|
||||||
Computed: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ExampleDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
|
||||||
// Prevent panic if the provider has not been configured.
|
|
||||||
if req.ProviderData == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
client, ok := req.ProviderData.(*http.Client)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
resp.Diagnostics.AddError(
|
|
||||||
"Unexpected Data Source Configure Type",
|
|
||||||
fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
|
||||||
)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
d.client = client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *ExampleDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
|
||||||
var data ExampleDataSourceModel
|
|
||||||
|
|
||||||
// Read Terraform configuration data into the model
|
|
||||||
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
|
|
||||||
|
|
||||||
if resp.Diagnostics.HasError() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If applicable, this is a great opportunity to initialize any necessary
|
|
||||||
// provider client data and make a call using it.
|
|
||||||
// httpResp, err := d.client.Do(httpReq)
|
|
||||||
// if err != nil {
|
|
||||||
// resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read example, got error: %s", err))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// For the purposes of this example code, hardcoding a response value to
|
|
||||||
// save into the Terraform state.
|
|
||||||
data.Id = types.StringValue("example-id")
|
|
||||||
|
|
||||||
// Write logs using the tflog package
|
|
||||||
// Documentation: https://terraform.io/plugin/log
|
|
||||||
tflog.Trace(ctx, "read a data source")
|
|
||||||
|
|
||||||
// Save data into Terraform state
|
|
||||||
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
|
||||||
|
|
||||||
package provider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/statecheck"
|
|
||||||
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAccExampleDataSource(t *testing.T) {
|
|
||||||
resource.Test(t, resource.TestCase{
|
|
||||||
PreCheck: func() { testAccPreCheck(t) },
|
|
||||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
|
||||||
Steps: []resource.TestStep{
|
|
||||||
// Read testing
|
|
||||||
{
|
|
||||||
Config: testAccExampleDataSourceConfig,
|
|
||||||
ConfigStateChecks: []statecheck.StateCheck{
|
|
||||||
statecheck.ExpectKnownValue(
|
|
||||||
"data.file_example.test",
|
|
||||||
tfjsonpath.New("id"),
|
|
||||||
knownvalue.StringExact("example-id"),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const testAccExampleDataSourceConfig = `
|
|
||||||
data "file_example" "test" {
|
|
||||||
configurable_attribute = "example"
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The `var _` is a special Go construct that results in an unusable variable.
|
||||||
|
// The purpose of these lines is to make sure our LocalFileResource correctly implements the `resource.Resource“ interface.
|
||||||
|
// These will fail at compilation time if the implementation is not satisfied.
|
||||||
|
var _ datasource.DataSource = &LocalDataSource{}
|
||||||
|
|
||||||
|
func NewLocalDataSource() datasource.DataSource {
|
||||||
|
return &LocalDataSource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalDataSource struct {
|
||||||
|
client fileClient
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocalDataSourceModel struct {
|
||||||
|
Id types.String `tfsdk:"id"`
|
||||||
|
Name types.String `tfsdk:"name"`
|
||||||
|
Directory types.String `tfsdk:"directory"`
|
||||||
|
Contents types.String `tfsdk:"contents"`
|
||||||
|
Permissions types.String `tfsdk:"permissions"`
|
||||||
|
HmacSecretKey types.String `tfsdk:"hmac_secret_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LocalDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||||
|
resp.TypeName = req.ProviderTypeName + "_local" // file_local datasource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LocalDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||||
|
resp.Schema = schema.Schema{
|
||||||
|
MarkdownDescription: "Local File DataSource",
|
||||||
|
|
||||||
|
Attributes: map[string]schema.Attribute{
|
||||||
|
"name": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "File name, required.",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"directory": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "The directory where the file exists.",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"hmac_secret_key": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "A string used to generate the file identifier, " +
|
||||||
|
"you can pass this value in the environment variable `TF_FILE_HMAC_SECRET_KEY`. ",
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
Sensitive: true,
|
||||||
|
},
|
||||||
|
"contents": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "The file contents.",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"permissions": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "The file permissions.",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
"id": schema.StringAttribute{
|
||||||
|
MarkdownDescription: "Identifier derived from sha256+HMAC hash of file contents. ",
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *LocalDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||||
|
// Prevent panic if the provider has not been configured.
|
||||||
|
if req.ProviderData == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read runs before all other resources are run, datasources only get the Read function.
|
||||||
|
func (r *LocalDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||||
|
tflog.Debug(ctx, fmt.Sprintf("Request Object: %#v", req))
|
||||||
|
|
||||||
|
// Allow the ability to inject a file client, but use the osFileClient by default.
|
||||||
|
if r.client == nil {
|
||||||
|
tflog.Debug(ctx, "Configuring client with default osFileClient.")
|
||||||
|
r.client = &osFileClient{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var config LocalDataSourceModel
|
||||||
|
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
|
||||||
|
if resp.Diagnostics.HasError() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cName := config.Name.ValueString()
|
||||||
|
cDirectory := config.Directory.ValueString()
|
||||||
|
cPerm := config.Permissions.ValueString()
|
||||||
|
cHmacSecretKey := config.HmacSecretKey.ValueString()
|
||||||
|
|
||||||
|
cKey := cHmacSecretKey
|
||||||
|
if cKey == "" {
|
||||||
|
tflog.Debug(ctx, "Checking for secret key in environment variable TF_FILE_HMAC_SECRET_KEY.")
|
||||||
|
cKey = os.Getenv("TF_FILE_HMAC_SECRET_KEY")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cKey == "" {
|
||||||
|
tflog.Debug(ctx, "Key not found, attempting to use constant.")
|
||||||
|
cKey = unprotectedHmacSecret // this is a constant defined in file_local_resource.go
|
||||||
|
}
|
||||||
|
|
||||||
|
perm, contents, err := r.client.Read(cDirectory, cName)
|
||||||
|
if err != nil && err.Error() == "File not found." {
|
||||||
|
resp.State.RemoveResource(ctx)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error reading file: ", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update state with actual contents
|
||||||
|
config.Contents = types.StringValue(contents)
|
||||||
|
id, err := calculateId(contents, cKey)
|
||||||
|
if err != nil {
|
||||||
|
resp.Diagnostics.AddError("Error reading file: ", "Problem calculating id from key: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
config.Id = types.StringValue(id)
|
||||||
|
|
||||||
|
if perm != cPerm {
|
||||||
|
// update the state with the actual mode
|
||||||
|
config.Permissions = types.StringValue(perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
|
||||||
|
tflog.Debug(ctx, fmt.Sprintf("Response Object: %#v", *resp))
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
|
||||||
|
"github.com/hashicorp/terraform-plugin-go/tftypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLocalDataSourceMetadata(t *testing.T) {
|
||||||
|
t.Run("Metadata function", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
fit LocalDataSource
|
||||||
|
want datasource.MetadataResponse
|
||||||
|
}{
|
||||||
|
{"Basic test", LocalDataSource{}, datasource.MetadataResponse{TypeName: "file_local"}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
res := datasource.MetadataResponse{}
|
||||||
|
tc.fit.Metadata(context.Background(), datasource.MetadataRequest{ProviderTypeName: "file"}, &res)
|
||||||
|
got := res
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("%#v.Metadata() is %v; want %v", tc.fit, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLocalDataSourceRead(t *testing.T) {
|
||||||
|
t.Run("Read function", func(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
fit LocalDataSource
|
||||||
|
have datasource.ReadRequest
|
||||||
|
want datasource.ReadResponse
|
||||||
|
setup map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Unprotected",
|
||||||
|
LocalDataSource{client: &memoryFileClient{}},
|
||||||
|
// have
|
||||||
|
getDataSourceReadRequest(t, map[string]string{
|
||||||
|
"id": "60cef95046105ff4522c0c1f1aeeeba43d0d729dbcabdd8846c317c98cac60a2",
|
||||||
|
"name": "read.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is an unprotected read test",
|
||||||
|
"hmac_secret_key": defaultHmacSecretKey,
|
||||||
|
}),
|
||||||
|
// want
|
||||||
|
getDataSourceReadResponse(t, map[string]string{
|
||||||
|
"id": "60cef95046105ff4522c0c1f1aeeeba43d0d729dbcabdd8846c317c98cac60a2",
|
||||||
|
"name": "read.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is an unprotected read test",
|
||||||
|
"hmac_secret_key": defaultHmacSecretKey,
|
||||||
|
}),
|
||||||
|
map[string]string{
|
||||||
|
"mode": defaultPerm,
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"name": "read.tmp",
|
||||||
|
"contents": "this is an unprotected read test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Protected",
|
||||||
|
LocalDataSource{client: &memoryFileClient{}},
|
||||||
|
// have
|
||||||
|
getDataSourceReadRequest(t, map[string]string{
|
||||||
|
"id": "ec4407ba53b2c40ac2ac18ff7372a6fe6e4f7f8aa04f340503aefc7d9a5fa4e1",
|
||||||
|
"name": "read_protected.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// want
|
||||||
|
getDataSourceReadResponse(t, map[string]string{
|
||||||
|
"id": "ec4407ba53b2c40ac2ac18ff7372a6fe6e4f7f8aa04f340503aefc7d9a5fa4e1",
|
||||||
|
"name": "read_protected.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// reality
|
||||||
|
map[string]string{
|
||||||
|
"mode": defaultPerm,
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"name": "read_protected.tmp",
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Protected with content update",
|
||||||
|
LocalDataSource{client: &memoryFileClient{}},
|
||||||
|
// have
|
||||||
|
getDataSourceReadRequest(t, map[string]string{
|
||||||
|
"id": "ec4407ba53b2c40ac2ac18ff7372a6fe6e4f7f8aa04f340503aefc7d9a5fa4e1",
|
||||||
|
"name": "read_protected_content.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// want
|
||||||
|
getDataSourceReadResponse(t, map[string]string{
|
||||||
|
"id": "84326116e261654e44ca3cb73fa026580853794062d472bc817b7ec2c82ff648",
|
||||||
|
"name": "read_protected_content.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is a change in contents in the real file",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// reality
|
||||||
|
map[string]string{
|
||||||
|
"mode": defaultPerm,
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"name": "read_protected_content.tmp",
|
||||||
|
"contents": "this is a change in contents in the real file",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Protected with mode update",
|
||||||
|
LocalDataSource{client: &memoryFileClient{}},
|
||||||
|
// have
|
||||||
|
getDataSourceReadRequest(t, map[string]string{
|
||||||
|
"id": "ec4407ba53b2c40ac2ac18ff7372a6fe6e4f7f8aa04f340503aefc7d9a5fa4e1",
|
||||||
|
"name": "read_protected_mode.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": defaultPerm,
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// want
|
||||||
|
getDataSourceReadResponse(t, map[string]string{
|
||||||
|
"id": "ec4407ba53b2c40ac2ac18ff7372a6fe6e4f7f8aa04f340503aefc7d9a5fa4e1",
|
||||||
|
"name": "read_protected_mode.tmp",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"permissions": "0755",
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
"hmac_secret_key": "this-is-a-test-key",
|
||||||
|
}),
|
||||||
|
// reality
|
||||||
|
map[string]string{
|
||||||
|
"mode": "0755",
|
||||||
|
"directory": defaultDirectory,
|
||||||
|
"name": "read_protected_mode.tmp",
|
||||||
|
"contents": "this is a protected read test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if err := tc.fit.client.Create(tc.setup["directory"], tc.setup["name"], tc.setup["contents"], tc.setup["mode"]); err != nil {
|
||||||
|
t.Errorf("Error setting up: %v", err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := tc.fit.client.Delete(tc.setup["directory"], tc.setup["name"]); err != nil {
|
||||||
|
t.Errorf("Error tearing down: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
r := getDataSourceReadResponseContainer()
|
||||||
|
tc.fit.Read(context.Background(), tc.have, &r)
|
||||||
|
got := r
|
||||||
|
if diff := cmp.Diff(tc.want, got); diff != "" {
|
||||||
|
t.Errorf("Read() mismatch (-want +got):\n%s", diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** Test Helper Functions *** //
|
||||||
|
|
||||||
|
func getDataSourceReadRequest(t *testing.T, data map[string]string) datasource.ReadRequest {
|
||||||
|
stateMap := make(map[string]tftypes.Value)
|
||||||
|
for key, value := range data {
|
||||||
|
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||||
|
}
|
||||||
|
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||||
|
} else {
|
||||||
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stateValue := tftypes.NewValue(getDataSourceObjectAttributeTypes(), stateMap)
|
||||||
|
return datasource.ReadRequest{
|
||||||
|
Config: tfsdk.Config{
|
||||||
|
Raw: stateValue,
|
||||||
|
Schema: getLocalDataSourceSchema().Schema,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataSourceReadResponseContainer() datasource.ReadResponse {
|
||||||
|
return datasource.ReadResponse{
|
||||||
|
State: tfsdk.State{Schema: getLocalDataSourceSchema().Schema},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataSourceReadResponse(t *testing.T, data map[string]string) datasource.ReadResponse {
|
||||||
|
stateMap := make(map[string]tftypes.Value)
|
||||||
|
for key, value := range data {
|
||||||
|
if slices.Contains(booleanFields, key) { // booleanFields is a constant
|
||||||
|
v, err := strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error converting %s to bool %s: ", value, err.Error())
|
||||||
|
}
|
||||||
|
stateMap[key] = tftypes.NewValue(tftypes.Bool, v)
|
||||||
|
} else {
|
||||||
|
stateMap[key] = tftypes.NewValue(tftypes.String, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stateValue := tftypes.NewValue(getDataSourceObjectAttributeTypes(), stateMap)
|
||||||
|
return datasource.ReadResponse{
|
||||||
|
State: tfsdk.State{
|
||||||
|
Raw: stateValue,
|
||||||
|
Schema: getLocalDataSourceSchema().Schema,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDataSourceObjectAttributeTypes() tftypes.Object {
|
||||||
|
return tftypes.Object{
|
||||||
|
AttributeTypes: map[string]tftypes.Type{
|
||||||
|
"id": tftypes.String,
|
||||||
|
"name": tftypes.String,
|
||||||
|
"directory": tftypes.String,
|
||||||
|
"permissions": tftypes.String,
|
||||||
|
"contents": tftypes.String,
|
||||||
|
"hmac_secret_key": tftypes.String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalDataSourceSchema() *datasource.SchemaResponse {
|
||||||
|
var testResource LocalDataSource
|
||||||
|
r := &datasource.SchemaResponse{}
|
||||||
|
testResource.Schema(context.Background(), datasource.SchemaRequest{}, r)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package provider
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -10,8 +10,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator"
|
"github.com/hashicorp/terraform-plugin-framework-validators/boolvalidator"
|
||||||
|
|
@ -34,6 +32,8 @@ import (
|
||||||
var _ resource.Resource = &LocalResource{}
|
var _ resource.Resource = &LocalResource{}
|
||||||
var _ resource.ResourceWithImportState = &LocalResource{}
|
var _ resource.ResourceWithImportState = &LocalResource{}
|
||||||
|
|
||||||
|
const unprotectedHmacSecret = "this-is-the-hmac-secret-key-that-will-be-used-to-calculate-the-hash-of-unprotected-files"
|
||||||
|
|
||||||
// An interface for defining custom file managers.
|
// An interface for defining custom file managers.
|
||||||
type fileClient interface {
|
type fileClient interface {
|
||||||
Create(directory string, name string, data string, permissions string) error
|
Create(directory string, name string, data string, permissions string) error
|
||||||
|
|
@ -43,58 +43,6 @@ type fileClient interface {
|
||||||
Delete(directory string, name string) error
|
Delete(directory string, name string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// The default fileClient, using the os package.
|
|
||||||
type osFileClient struct{}
|
|
||||||
|
|
||||||
var _ fileClient = &osFileClient{} // make sure the osFileClient implements the fileClient
|
|
||||||
|
|
||||||
func (c *osFileClient) Create(directory string, name string, data string, permissions string) error {
|
|
||||||
path := filepath.Join(directory, name)
|
|
||||||
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.WriteFile(path, []byte(data), os.FileMode(modeInt))
|
|
||||||
}
|
|
||||||
func (c *osFileClient) Read(directory string, name string) (string, string, error) {
|
|
||||||
path := filepath.Join(directory, name)
|
|
||||||
info, err := os.Stat(path)
|
|
||||||
if err != nil && os.IsNotExist(err) {
|
|
||||||
return "", "", fmt.Errorf("file not found")
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
mode := fmt.Sprintf("%#o", info.Mode().Perm())
|
|
||||||
contents, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", err
|
|
||||||
}
|
|
||||||
return mode, string(contents), nil
|
|
||||||
}
|
|
||||||
func (c *osFileClient) Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error {
|
|
||||||
currentPath := filepath.Join(currentDirectory, currentName)
|
|
||||||
newPath := filepath.Join(newDirectory, newName)
|
|
||||||
if currentPath != newPath {
|
|
||||||
err := os.Rename(currentPath, newPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = os.WriteFile(newPath, []byte(data), os.FileMode(modeInt)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (c *osFileClient) Delete(directory string, name string) error {
|
|
||||||
path := filepath.Join(directory, name)
|
|
||||||
return os.Remove(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLocalResource() resource.Resource {
|
func NewLocalResource() resource.Resource {
|
||||||
return &LocalResource{}
|
return &LocalResource{}
|
||||||
}
|
}
|
||||||
|
|
@ -212,6 +160,7 @@ func (r *LocalResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Allow the ability to inject a file client, but use the osFileClient by default.
|
// Allow the ability to inject a file client, but use the osFileClient by default.
|
||||||
|
// see file_os_client.go
|
||||||
if r.client == nil {
|
if r.client == nil {
|
||||||
tflog.Debug(ctx, "Configuring client with default osFileClient.")
|
tflog.Debug(ctx, "Configuring client with default osFileClient.")
|
||||||
r.client = &osFileClient{}
|
r.client = &osFileClient{}
|
||||||
|
|
@ -245,7 +194,7 @@ func (r *LocalResource) Create(ctx context.Context, req resource.CreateRequest,
|
||||||
return
|
return
|
||||||
} // at this point we have an id, key, contents, protected is true, and our calculated id matches what was provided
|
} // at this point we have an id, key, contents, protected is true, and our calculated id matches what was provided
|
||||||
} else {
|
} else {
|
||||||
id, err = calculateId(contents, "this-is-the-hmac-secret-key-that-will-be-used-to-calculate-the-hash-of-unprotected-files")
|
id, err = calculateId(contents, unprotectedHmacSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error creating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
resp.Diagnostics.AddError("Error creating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -369,7 +318,7 @@ func (r *LocalResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
id, err := calculateId(cContents, "this-is-the-hmac-secret-key-that-will-be-used-to-calculate-the-hash-of-unprotected-files")
|
id, err := calculateId(cContents, unprotectedHmacSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error updating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
resp.Diagnostics.AddError("Error updating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -405,7 +354,7 @@ func (r *LocalResource) Update(ctx context.Context, req resource.UpdateRequest,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := calculateId(rContents, "this-is-the-hmac-secret-key-that-will-be-used-to-calculate-the-hash-of-unprotected-files")
|
_, err := calculateId(rContents, unprotectedHmacSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Diagnostics.AddError("Error updating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
resp.Diagnostics.AddError("Error updating file: ", "Problem calculating id from hard coded key: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
package provider
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -709,43 +708,3 @@ func getLocalResourceSchema() *resource.SchemaResponse {
|
||||||
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
testResource.Schema(context.Background(), resource.SchemaRequest{}, r)
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// type fileClient interface {
|
|
||||||
// Create(directory string, name string, data string, permissions string) error
|
|
||||||
// // If file isn't found the error message must have err.Error() == "File not found."
|
|
||||||
// Read(directory string, name string) (string, string, error)// permissions, contents, error
|
|
||||||
// Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error
|
|
||||||
// Delete(directory string, name string) error
|
|
||||||
// }
|
|
||||||
|
|
||||||
type memoryFileClient struct {
|
|
||||||
file map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ fileClient = &memoryFileClient{} // make sure the memoryFileClient implements the fileClient
|
|
||||||
func (c *memoryFileClient) Create(directory string, name string, data string, permissions string) error {
|
|
||||||
|
|
||||||
c.file = make(map[string]string)
|
|
||||||
c.file["directory"] = directory
|
|
||||||
c.file["name"] = name
|
|
||||||
c.file["contents"] = data
|
|
||||||
c.file["permissions"] = permissions
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (c *memoryFileClient) Read(directory string, name string) (string, string, error) {
|
|
||||||
if c.file["directory"] == "" || c.file["name"] == "" {
|
|
||||||
return "", "", fmt.Errorf("file not found")
|
|
||||||
}
|
|
||||||
return c.file["permissions"], c.file["contents"], nil
|
|
||||||
}
|
|
||||||
func (c *memoryFileClient) Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error {
|
|
||||||
c.file["directory"] = newDirectory
|
|
||||||
c.file["name"] = newName
|
|
||||||
c.file["contents"] = data
|
|
||||||
c.file["permissions"] = permissions
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (c *memoryFileClient) Delete(directory string, name string) error {
|
|
||||||
c.file = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type memoryFileClient struct {
|
||||||
|
file map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fileClient = &memoryFileClient{} // make sure the memoryFileClient implements the fileClient
|
||||||
|
|
||||||
|
func (c *memoryFileClient) Create(directory string, name string, data string, permissions string) error {
|
||||||
|
|
||||||
|
c.file = make(map[string]string)
|
||||||
|
c.file["directory"] = directory
|
||||||
|
c.file["name"] = name
|
||||||
|
c.file["contents"] = data
|
||||||
|
c.file["permissions"] = permissions
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memoryFileClient) Read(directory string, name string) (string, string, error) {
|
||||||
|
if c.file["directory"] == "" || c.file["name"] == "" {
|
||||||
|
return "", "", fmt.Errorf("file not found")
|
||||||
|
}
|
||||||
|
return c.file["permissions"], c.file["contents"], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memoryFileClient) Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error {
|
||||||
|
c.file["directory"] = newDirectory
|
||||||
|
c.file["name"] = newName
|
||||||
|
c.file["contents"] = data
|
||||||
|
c.file["permissions"] = permissions
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *memoryFileClient) Delete(directory string, name string) error {
|
||||||
|
c.file = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The default fileClient, using the os package.
|
||||||
|
type osFileClient struct{}
|
||||||
|
|
||||||
|
var _ fileClient = &osFileClient{} // make sure the osFileClient implements the fileClient
|
||||||
|
|
||||||
|
func (c *osFileClient) Create(directory string, name string, data string, permissions string) error {
|
||||||
|
path := filepath.Join(directory, name)
|
||||||
|
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.WriteFile(path, []byte(data), os.FileMode(modeInt))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *osFileClient) Read(directory string, name string) (string, string, error) {
|
||||||
|
path := filepath.Join(directory, name)
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil && os.IsNotExist(err) {
|
||||||
|
return "", "", fmt.Errorf("file not found")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
mode := fmt.Sprintf("%#o", info.Mode().Perm())
|
||||||
|
contents, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return mode, string(contents), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *osFileClient) Update(currentDirectory string, currentName string, newDirectory string, newName string, data string, permissions string) error {
|
||||||
|
currentPath := filepath.Join(currentDirectory, currentName)
|
||||||
|
newPath := filepath.Join(newDirectory, newName)
|
||||||
|
if currentPath != newPath {
|
||||||
|
err := os.Rename(currentPath, newPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
modeInt, err := strconv.ParseUint(permissions, 8, 32)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(newPath, []byte(data), os.FileMode(modeInt)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *osFileClient) Delete(directory string, name string) error {
|
||||||
|
path := filepath.Join(directory, name)
|
||||||
|
return os.Remove(path)
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/hashicorp/terraform-plugin-framework/provider"
|
"github.com/hashicorp/terraform-plugin-framework/provider"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
||||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||||
|
"github.com/rancher/terraform-provider-file/internal/provider/local"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The `var _` is a special Go construct that results in an unusable variable.
|
// The `var _` is a special Go construct that results in an unusable variable.
|
||||||
|
|
@ -48,13 +49,13 @@ func (p *FileProvider) Configure(ctx context.Context, req provider.ConfigureRequ
|
||||||
|
|
||||||
func (p *FileProvider) Resources(ctx context.Context) []func() resource.Resource {
|
func (p *FileProvider) Resources(ctx context.Context) []func() resource.Resource {
|
||||||
return []func() resource.Resource{
|
return []func() resource.Resource{
|
||||||
NewLocalResource,
|
local.NewLocalResource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FileProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
func (p *FileProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
|
||||||
return []func() datasource.DataSource{
|
return []func() datasource.DataSource{
|
||||||
// NewExampleDataSource,
|
local.NewLocalDataSource,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -1,5 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
provider_installation {
|
provider_installation {
|
||||||
dev_overrides {
|
dev_overrides {
|
||||||
"rancher/file" = "../../../bin"
|
"rancher/file" = "../../../../bin"
|
||||||
}
|
}
|
||||||
direct {
|
direct {
|
||||||
exclude = []
|
exclude = []
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
package basic
|
package basic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -14,7 +12,7 @@ func TestBasic(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "basic"
|
directory := "local/basic"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
|
|
@ -40,6 +38,7 @@ func TestBasic(t *testing.T) {
|
||||||
},
|
},
|
||||||
EnvVars: map[string]string{
|
EnvVars: map[string]string{
|
||||||
"TF_DATA_DIR": testDir,
|
"TF_DATA_DIR": testDir,
|
||||||
|
"TF_CLI_CONFIG_FILE": filepath.Join(repoRoot, "test", ".terraformrc"),
|
||||||
"TF_IN_AUTOMATION": "1",
|
"TF_IN_AUTOMATION": "1",
|
||||||
"TF_CLI_ARGS_init": "-no-color",
|
"TF_CLI_ARGS_init": "-no-color",
|
||||||
"TF_CLI_ARGS_plan": "-no-color",
|
"TF_CLI_ARGS_plan": "-no-color",
|
||||||
|
|
@ -50,6 +49,7 @@ func TestBasic(t *testing.T) {
|
||||||
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
||||||
NoColor: true,
|
NoColor: true,
|
||||||
Upgrade: true,
|
Upgrade: true,
|
||||||
|
// ExtraArgs: terraform.ExtraArgs{ Output: []string{"-json"} },
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
||||||
|
|
@ -58,6 +58,10 @@ func TestBasic(t *testing.T) {
|
||||||
util.TearDown(t, testDir, terraformOptions)
|
util.TearDown(t, testDir, terraformOptions)
|
||||||
t.Fatalf("Error creating file: %s", err)
|
t.Fatalf("Error creating file: %s", err)
|
||||||
}
|
}
|
||||||
|
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Output failed, moving along...")
|
||||||
|
}
|
||||||
|
|
||||||
fileExists, err := util.CheckFileExists(filepath.Join(testDir, "basic_test.txt"))
|
fileExists, err := util.CheckFileExists(filepath.Join(testDir, "basic_test.txt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
package protected
|
package protected
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -13,7 +11,7 @@ import (
|
||||||
func TestProtectedBasic(t *testing.T) {
|
func TestProtectedBasic(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "protected"
|
directory := "local/protected"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
|
|
@ -41,6 +39,7 @@ func TestProtectedBasic(t *testing.T) {
|
||||||
EnvVars: map[string]string{
|
EnvVars: map[string]string{
|
||||||
"TF_DATA_DIR": testDir,
|
"TF_DATA_DIR": testDir,
|
||||||
"TF_FILE_HMAC_SECRET_KEY": "thisisasupersecretkey",
|
"TF_FILE_HMAC_SECRET_KEY": "thisisasupersecretkey",
|
||||||
|
"TF_CLI_CONFIG_FILE": filepath.Join(repoRoot, "test", ".terraformrc"),
|
||||||
"TF_IN_AUTOMATION": "1",
|
"TF_IN_AUTOMATION": "1",
|
||||||
"TF_CLI_ARGS_init": "-no-color",
|
"TF_CLI_ARGS_init": "-no-color",
|
||||||
"TF_CLI_ARGS_plan": "-no-color",
|
"TF_CLI_ARGS_plan": "-no-color",
|
||||||
|
|
@ -51,6 +50,7 @@ func TestProtectedBasic(t *testing.T) {
|
||||||
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
||||||
NoColor: true,
|
NoColor: true,
|
||||||
Upgrade: true,
|
Upgrade: true,
|
||||||
|
// ExtraArgs: terraform.ExtraArgs{ Output: []string{"-json"} },
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
||||||
|
|
@ -59,6 +59,10 @@ func TestProtectedBasic(t *testing.T) {
|
||||||
util.TearDown(t, testDir, terraformOptions)
|
util.TearDown(t, testDir, terraformOptions)
|
||||||
t.Fatalf("Error creating file: %s", err)
|
t.Fatalf("Error creating file: %s", err)
|
||||||
}
|
}
|
||||||
|
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Output failed, moving along...")
|
||||||
|
}
|
||||||
|
|
||||||
fileAExists, err := util.CheckFileExists(filepath.Join(testDir, "a_protected_test.txt"))
|
fileAExists, err := util.CheckFileExists(filepath.Join(testDir, "a_protected_test.txt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
package protected
|
package protected
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -13,7 +11,7 @@ import (
|
||||||
func TestProtectedProtects(t *testing.T) {
|
func TestProtectedProtects(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
id := util.GetId()
|
id := util.GetId()
|
||||||
directory := "protected"
|
directory := "local/protected"
|
||||||
repoRoot, err := util.GetRepoRoot(t)
|
repoRoot, err := util.GetRepoRoot(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error getting git root directory: %v", err)
|
t.Fatalf("Error getting git root directory: %v", err)
|
||||||
|
|
@ -41,6 +39,7 @@ func TestProtectedProtects(t *testing.T) {
|
||||||
EnvVars: map[string]string{
|
EnvVars: map[string]string{
|
||||||
"TF_DATA_DIR": testDir,
|
"TF_DATA_DIR": testDir,
|
||||||
"TF_FILE_HMAC_SECRET_KEY": "thisisasupersecretkey",
|
"TF_FILE_HMAC_SECRET_KEY": "thisisasupersecretkey",
|
||||||
|
"TF_CLI_CONFIG_FILE": filepath.Join(repoRoot, "test", ".terraformrc"),
|
||||||
"TF_IN_AUTOMATION": "1",
|
"TF_IN_AUTOMATION": "1",
|
||||||
"TF_CLI_ARGS_init": "-no-color",
|
"TF_CLI_ARGS_init": "-no-color",
|
||||||
"TF_CLI_ARGS_plan": "-no-color",
|
"TF_CLI_ARGS_plan": "-no-color",
|
||||||
|
|
@ -51,6 +50,7 @@ func TestProtectedProtects(t *testing.T) {
|
||||||
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
RetryableTerraformErrors: util.GetRetryableTerraformErrors(),
|
||||||
NoColor: true,
|
NoColor: true,
|
||||||
Upgrade: true,
|
Upgrade: true,
|
||||||
|
// ExtraArgs: terraform.ExtraArgs{ Output: []string{"-json"} },
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
_, err = terraform.InitAndApplyE(t, terraformOptions)
|
||||||
|
|
@ -59,6 +59,10 @@ func TestProtectedProtects(t *testing.T) {
|
||||||
util.TearDown(t, testDir, terraformOptions)
|
util.TearDown(t, testDir, terraformOptions)
|
||||||
t.Fatalf("Error creating file: %s", err)
|
t.Fatalf("Error creating file: %s", err)
|
||||||
}
|
}
|
||||||
|
_, err = terraform.OutputAllE(t, terraformOptions)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Output failed, moving along...")
|
||||||
|
}
|
||||||
|
|
||||||
fileAExists, err := util.CheckFileExists(filepath.Join(testDir, "a_protected_test.txt"))
|
fileAExists, err := util.CheckFileExists(filepath.Join(testDir, "a_protected_test.txt"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
# Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
# summarize.sh - reads report.json and prints a summary
|
# summarize.sh - reads report.json and prints a summary
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
|
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// Copyright (c) HashiCorp, Inc.
|
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
//go:build generate
|
//go:build generate
|
||||||
|
|
@ -6,13 +5,9 @@
|
||||||
package tools
|
package tools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "github.com/hashicorp/copywrite"
|
|
||||||
_ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
|
_ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Generate copyright headers
|
|
||||||
//go:generate go run github.com/hashicorp/copywrite headers -d .. --config ../.copywrite.hcl
|
|
||||||
|
|
||||||
// Format Terraform code for use in documentation.
|
// Format Terraform code for use in documentation.
|
||||||
// If you do not have Terraform installed, you can remove the formatting command, but it is suggested
|
// If you do not have Terraform installed, you can remove the formatting command, but it is suggested
|
||||||
// to ensure the documentation is formatted properly.
|
// to ensure the documentation is formatted properly.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue