Migrate to terraform plugin framework (#1379)

Co-authored-by: Mauricio Alvarez Leon <65101411+BBBmau@users.noreply.github.com>
Co-authored-by: John Houston <jhouston@hashicorp.com>
Co-authored-by: Brandy Jackson <90709515+iBrandyJackson@users.noreply.github.com>
This commit is contained in:
Jaylon McShan 2025-01-16 13:08:36 -06:00 committed by GitHub
parent afc64eda21
commit b328cfa7d1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 5151 additions and 4186 deletions

3
.changelog/1379.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:dependency
Provider project ported from `terraform-plugin-sdk/v2` to `terraform-plugin-framework`
```

View file

@ -1,42 +0,0 @@
name: "Documentation Updates"
on:
pull_request:
paths:
- 'docs/**'
types: [opened, synchronize, labeled]
push:
branches:
- main
jobs:
check-docs:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.pull_request.labels.*.name, 'no-documentation') }}
steps:
- name: Checkout repository
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
- name: Set up Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version-file: 'go.mod'
- name: Install tfplugindocs command
run: go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest
- name: Run tfplugindocs command
run: tfplugindocs generate
- name: Check for changes
run: |
git diff --exit-code
- name: Undocumented changes
run: |
echo 'Documentation is not up to date. Please refer to the `Making Changes` in the Contribution Guide on how to properly update documentation.'
exit 1
if: failure()

View file

@ -3,11 +3,9 @@ on:
push:
branches:
- main
- helm-framework
pull_request:
branches:
- main
- helm-framework
env:
KUBECONFIG: ${{ github.workspace }}/.kube/config
@ -59,6 +57,7 @@ jobs:
env:
KUBE_CONFIG_PATH: ${{ env.KUBECONFIG }}
TF_ACC_TERRAFORM_VERSION: ${{ matrix.terraform_version }}
TF_ACC_TEMP_DIR: ${{ runner.temp }}
TESTARGS: "-parallel 1"
run: |
make testacc

View file

@ -15,7 +15,6 @@ ifneq ($(origin TRAVIS_TAG), undefined)
BRANCH := $(TRAVIS_TAG)
VERSION := $(TRAVIS_TAG)
endif
# For changelog generation, default the last release to the last tag on
# any branch, and this release to just be the current branch we're on.
LAST_RELEASE?=$$(git describe --tags $$(git rev-list --tags --max-count=1))

View file

@ -140,17 +140,22 @@ data "helm_template" "mariadb_instance" {
chart = "mariadb"
version = "7.1.0"
set {
name = "service.port"
value = "13306"
}
set = [
{
name = "service.port"
value = "13306"
}
]
set_sensitive {
name = "rootUser.password"
value = "s3cr3t!"
}
set_sensitive = [
{
name = "rootUser.password"
value = "s3cr3t!"
}
]
}
resource "local_file" "mariadb_manifests" {
for_each = data.helm_template.mariadb_instance.manifests
@ -188,18 +193,23 @@ data "helm_template" "mariadb_instance" {
"templates/master-statefulset.yaml",
"templates/master-svc.yaml",
]
set {
name = "service.port"
value = "13306"
}
set_sensitive {
name = "rootUser.password"
value = "s3cr3t!"
}
set = [
{
name = "service.port"
value = "13306"
}
]
set_sensitive = [
{
name = "rootUser.password"
value = "s3cr3t!"
}
]
}
resource "local_file" "mariadb_manifests" {
for_each = data.helm_template.mariadb_instance.manifests
@ -219,3 +229,4 @@ output "mariadb_instance_notes" {
value = data.helm_template.mariadb_instance.notes
}
```

View file

@ -0,0 +1,246 @@
---
layout: "helm"
page_title: "Helm: Upgrade Guide for Helm Provider v3.0.0"
description: |-
This guide covers the changes introduced in v3.0.0 of the Helm provider and what you may need to do to upgrade your configuration.
---
# Upgrading to v3.0.0 of the Helm provider
This guide covers the changes introduced in v3.0.0 of the Helm provider and what you may need to do to upgrade your configuration.
## Changes in v3.0.0
### Adoption of the Terraform Plugin Framework
The Helm provider has been migrated from the legacy [Terraform Plugin SDKv2](https://github.com/hashicorp/terraform-plugin-sdk) to the [Terraform Plugin Framework](https://github.com/hashicorp/terraform-plugin-framework). This migration introduces structural changes to the schema, affecting nested blocks, attribute names, and how configurations are represented. Users must update their configurations to align with the new framework. Key changes include:
- **Blocks to Nested Objects**: Blocks like `kubernetes`, `registry`, and `experiments` are now represented as nested objects.
- **List Syntax for Nested Attributes**: Attributes like `set`, `set_list`, and `set_sensitive` in `helm_release` and `helm_template` are now lists of nested objects instead of blocks.
### Terraform Version Compatability
The new framework code uses [Terraform Plugin Protocol Version 6](https://developer.hashicorp.com/terraform/plugin/terraform-plugin-protocol#protocol-version-6) which is compatible with Terraform versions 1.0 and aboove. Users of earlier versions of Terraform can continue to use the Helm provider by pinning their configuration to the 2.x version.
---
### Changes to Provider Attributes
#### Kubernetes Configuration (`kubernetes`)
The `kubernetes` block has been updated to a single nested object.
**Old SDKv2 Configuration:**
```hcl
provider "helm" {
kubernetes {
config_path = "~/.kube/config"
}
registry {
url = "oci://localhost:5000"
username = "username"
password = "password"
}
registry {
url = "oci://private.registry"
username = "username"
password = "password"
}
}
```
**New Plugin Framework Configuration:**
```hcl
provider "helm" {
kubernetes = {
config_path = "~/.kube/config"
}
registries = [
{
url = "oci://localhost:5000"
username = "username"
password = "password"
},
{
url = "oci://private.registry"
username = "username"
password = "password"
}
]
}
```
**What Changed?**
- `kubernetes` is now a single nested object attribute using `{ ... }`.
- `registry` blocks have been replaced by a `registries` list attribute.
#### Experiments Configuration (experiments)
The `experiments` block has been updated to a list of nested objects.
**Old SDKv2 Configuration:**
```hcl
provider "helm" {
experiments {
manifest = true
}
}
```
**New Plugin Framework Configuration:**
```hcl
provider "helm" {
experiments = {
manifest = true
}
}
```
**What Changed?**
- `experiments` is now a single nested object attribute using `{ ... }`.
### Changes to helm_release Resource
#### `set`, `set_list`, and `set_sensitive` Configuration
Attributes `set`, `set_list`, and `set_sensitive` are now represented as lists of nested objects instead of individual blocks.
**Old SDKv2 Configuration:**
```hcl
resource "helm_release" "nginx_ingress" {
name = "nginx-ingress-controller"
repository = "https://charts.bitnami.com/bitnami"
chart = "nginx-ingress-controller"
set {
name = "service.type"
value = "ClusterIP"
}
set_list {
name = "allowed.hosts"
value = ["host1", "host2"]
}
set_sensitive {
name = "api.key"
value = "super-secret-key"
}
}
```
**New Plugin Framework Configuration:**
```hcl
resource "helm_release" "nginx_ingress" {
name = "nginx-ingress-controller"
repository = "https://charts.bitnami.com/bitnami"
chart = "nginx-ingress-controller"
set = [
{
name = "service.type"
value = "ClusterIP"
}
]
set_list = [
{
name = "allowed.hosts"
value = ["host1", "host2"]
}
]
set_sensitive = [
{
name = "api.key"
value = "super-secret-key"
}
]
}
```
**What Changed?**
- `set`, `set_list`, and `set_sensitive` is now a list of nested objects using `[ { ... } ]`.
### Changes to helm_template Data Source
#### `set`, `set_list`, and `set_sensitive` Configuration
Attributes `set`, `set_list`, and `set_sensitive` are now represented as lists of nested objects instead of individual blocks.
**Old SDKv2 Configuration:**
```hcl
data "helm_template" "example" {
name = "my-release"
chart = "my-chart"
namespace = "my-namespace"
values = ["custom-values.yaml"]
set {
name = "image.tag"
value = "1.2.3"
}
set_list {
name = "allowed.hosts"
value = ["host1", "host2"]
}
set_sensitive {
name = "api.key"
value = "super-secret-key"
}
}
```
**New Plugin Framework Configuration:**
```hcl
data "helm_template" "example" {
name = "my-release"
chart = "my-chart"
namespace = "my-namespace"
values = ["custom-values.yaml"]
set = [
{
name = "image.tag"
value = "1.2.3"
}
]
set_list = [
{
name = "allowed.hosts"
value = ["host1", "host2"]
}
]
set_sensitive = [
{
name = "api.key"
value = "super-secret-key"
}
]
}
```
**What Changed?**
- `set`, `set_list`, and `set_sensitive` is now a list of nested objects using `[ { ... } ]`.

View file

@ -23,35 +23,35 @@ Try the [hands-on tutorial](https://learn.hashicorp.com/tutorials/terraform/helm
```terraform
provider "helm" {
kubernetes {
kubernetes = {
config_path = "~/.kube/config"
}
# localhost registry with password protection
registry {
url = "oci://localhost:5000"
username = "username"
password = "password"
}
# private registry
registry {
url = "oci://private.registry"
username = "username"
password = "password"
}
registries = [
{
url = "oci://localhost:5000"
username = "username"
password = "password"
},
{
url = "oci://private.registry"
username = "username"
password = "password"
}
]
}
resource "helm_release" "nginx_ingress" {
name = "nginx-ingress-controller"
repository = "https://charts.bitnami.com/bitnami"
chart = "nginx-ingress-controller"
set {
name = "service.type"
value = "ClusterIP"
}
set = [
{
name = "service.type"
value = "ClusterIP"
}
]
}
```
@ -80,7 +80,7 @@ The easiest way is to supply a path to your kubeconfig file using the `config_pa
```terraform
provider "helm" {
kubernetes {
kubernetes = {
config_path = "~/.kube/config"
}
}
@ -90,7 +90,7 @@ The provider also supports multiple paths in the same way that kubectl does usin
```terraform
provider "helm" {
kubernetes {
kubernetes = {
config_paths = [
"/path/to/config_a.yaml",
"/path/to/config_b.yaml"
@ -104,7 +104,7 @@ provider "helm" {
You can also configure the host, basic auth credentials, and client certificate authentication explicitly or through environment variables.
```terraform
provider "helm" {
provider "helm" = {
kubernetes {
host = "https://cluster_endpoint:port"
@ -127,7 +127,7 @@ Some cloud providers have short-lived authentication tokens that can expire rela
```terraform
provider "helm" {
kubernetes {
kubernetes = {
host = var.cluster_endpoint
cluster_ca_certificate = base64decode(var.cluster_ca_cert)
exec {
@ -143,7 +143,7 @@ For example, to [authenticate with GKE](https://registry.terraform.io/providers/
```terraform
provider "helm" {
kubernetes{
kubernetes = {
host = "https://${data.google_container_cluster.my_cluster.endpoint}"
token = data.google_client_config.provider.access_token
cluster_ca_certificate = base64decode(
@ -168,7 +168,7 @@ The following arguments are supported:
* `helm_driver` - (Optional) "The backend storage driver. Valid values are: `configmap`, `secret`, `memory`, `sql`. Defaults to `secret`. Note: Regarding the sql driver, as of helm v3.2.0 SQL support exists only for the postgres dialect. The connection string can be configured by setting the `HELM_DRIVER_SQL_CONNECTION_STRING` environment variable e.g. `HELM_DRIVER_SQL_CONNECTION_STRING=postgres://username:password@host/dbname` more info [here](https://pkg.go.dev/github.com/lib/pq).
* `burst_limit` - (Optional) The helm burst limit to use. Set this value higher if your cluster has many CRDs. Default: `100`
* `kubernetes` - Kubernetes configuration block.
* `registry` - Private OCI registry configuration block. Can be specified multiple times.
* `registries` - Private OCI registry configuration block. Can be specified multiple times.
The `kubernetes` block supports:
@ -191,7 +191,7 @@ The `kubernetes` block supports:
* `args` - (Optional) List of arguments to pass when executing the plugin.
* `env` - (Optional) Map of environment variables to set when executing the plugin.
The `registry` block has options:
The `registries` block has options:
* `url` - (Required) url to the registry in format `oci://host:port`
* `username` - (Required) username to registry
@ -202,3 +202,11 @@ The `registry` block has options:
The provider takes an `experiments` block that allows you enable experimental features by setting them to `true`.
* `manifest` - Enable storing of the rendered manifest for `helm_release` so the full diff of what is changing can been seen in the plan.
```terraform
provider "helm" {
experiments = {
manifest = true
}
}
```

View file

@ -144,25 +144,24 @@ resource "helm_release" "example" {
chart = "redis"
version = "6.0.1"
values = [
"${file("values.yaml")}"
set = [
{
name = "cluster.enabled"
value = "true"
},
{
name = "metrics.enabled"
value = "true"
}
]
set {
name = "cluster.enabled"
value = "true"
}
set {
name = "metrics.enabled"
value = "true"
}
set {
name = "service.annotations.prometheus\\.io/port"
value = "9127"
type = "string"
}
set = [
{
name = "service.annotations.prometheus\\.io/port"
value = "9127"
type = "string"
}
]
}
```
@ -194,16 +193,17 @@ Provider supports grabbing charts from an OCI repository:
```terraform
provider "helm" {
kubernetes {
kubernetes = {
config_path = "~/.kube/config"
}
# localhost registry with password protection
registry {
url = "oci://localhost:5000"
username = "username"
password = "password"
}
registries = [
{
url = "oci://localhost:5000"
username = "username"
password = "password"
}
]
}
resource "helm_release" "example" {
@ -295,17 +295,21 @@ The `set`, `set_list`, and `set_sensitive` blocks support:
Since Terraform Utilizes HCL as well as Helm using the Helm Template Language, it's necessary to escape the `{}`, `[]`, `.`, and `,` characters twice in order for it to be parsed. `name` should also be set to the `value path`, and `value` is the desired value that will be set.
```terraform
set {
name = "grafana.ingress.annotations.alb\\.ingress\\.kubernetes\\.io/group\\.name"
value = "shared-ingress"
}
set = [
{
name = "grafana.ingress.annotations.alb\\.ingress\\.kubernetes\\.io/group\\.name"
value = "shared-ingress"
}
]
```
```terraform
set_list {
name = "hashicorp"
value = ["terraform", "nomad", "vault"]
}
set_list = [
{
name = "hashicorp"
value = ["terraform", "nomad", "vault"]
}
]
```
```terraform
@ -316,10 +320,13 @@ controller:
```
```terraform
set {
set = [
{
name = "controller.pod.annotations.status\\.kubernetes\\.io/restart-on-failure"
value = "\\{\"timeout\": \"30s\"\\}"
}
}
]
```
The `postrender` block supports two attributes:

45
go.mod
View file

@ -2,14 +2,17 @@ module github.com/hashicorp/terraform-provider-helm
go 1.22.0
toolchain go1.22.5
toolchain go1.22.3
require (
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-framework v1.11.0
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
github.com/hashicorp/terraform-plugin-testing v1.8.0
github.com/hashicorp/terraform-plugin-testing v1.10.0
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
@ -18,15 +21,22 @@ require (
k8s.io/api v0.30.3
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
k8s.io/helm v2.17.0+incompatible
k8s.io/klog v1.0.0
sigs.k8s.io/yaml v1.4.0
)
require (
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
)
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
github.com/MakeNowJust/heredoc v1.0.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
@ -34,13 +44,12 @@ require (
github.com/Masterminds/squirrel v1.5.4 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
@ -60,6 +69,7 @@ require (
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
@ -80,21 +90,20 @@ require (
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/hashicorp/cli v1.1.6 // 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-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hc-install v0.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.20.1 // indirect
github.com/hashicorp/hc-install v0.8.0 // indirect
github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
github.com/hashicorp/terraform-json v0.22.1 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
@ -112,7 +121,7 @@ require (
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
@ -153,22 +162,22 @@ require (
github.com/xlab/treeprint v1.2.0 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.14.4 // indirect
github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/otel v1.21.0 // indirect
go.opentelemetry.io/otel/metric v1.21.0 // indirect
go.opentelemetry.io/otel/trace v1.21.0 // indirect
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/term v0.27.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect

68
go.sum
View file

@ -31,8 +31,8 @@ github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
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/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
@ -42,8 +42,8 @@ github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY=
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -129,6 +129,8 @@ github.com/foxcpp/go-mockdns v1.0.0 h1:7jBqxd3WDWwi/6WhDvacvH1XsN3rOLXyHM1uhvIx6
github.com/foxcpp/go-mockdns v1.0.0/go.mod h1:lgRN6+KxQBawyIghpnl5CezHFGS9VLzvtVlwxvzXTQ4=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@ -246,6 +248,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
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=
@ -253,10 +257,10 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk=
github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc=
github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4=
github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI=
github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
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.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
@ -265,14 +269,18 @@ github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c=
github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA=
github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E=
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo=
github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co=
github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
github.com/hashicorp/terraform-plugin-testing v1.8.0 h1:wdYIgwDk4iO933gC4S8KbKdnMQShu6BXuZQPScmHvpk=
github.com/hashicorp/terraform-plugin-testing v1.8.0/go.mod h1:o2kOgf18ADUaZGhtOl0YCkfIxg01MAiMATT2EtIHlZk=
github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
@ -344,8 +352,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -434,6 +442,8 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
@ -504,22 +514,22 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMzt
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI=
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 h1:x8Z78aZx8cOF0+Kkazoc7lwUNMGy0LrzEMxTm4BbTxg=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0/go.mod h1:62CPTSry9QZtOaSsE3tOzhx6LzDhHnXJ6xHeMNNiM6Q=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -531,16 +541,16 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -605,8 +615,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -686,6 +696,8 @@ k8s.io/client-go v0.30.3 h1:bHrJu3xQZNXIi8/MoxYtZBBWQQXwy16zqJwloXXfD3k=
k8s.io/client-go v0.30.3/go.mod h1:8d4pf8vYu665/kUbsxWAQ/JDBNWqfFeZnvFiVdmx89U=
k8s.io/component-base v0.30.0 h1:cj6bp38g0ainlfYtaOQuRELh5KSYjhKxM+io7AUIk4o=
k8s.io/component-base v0.30.0/go.mod h1:V9x/0ePFNaKeKYA3bOvIbrNoluTSG+fSJKjLdjOoeXQ=
k8s.io/helm v2.17.0+incompatible h1:Bpn6o1wKLYqKM3+Osh8e+1/K2g/GsQJ4F4yNF2+deao=
k8s.io/helm v2.17.0+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw=

1061
helm/data_helm_template.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ import (
"regexp"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestAccDataTemplate_basic(t *testing.T) {
@ -18,8 +18,7 @@ func TestAccDataTemplate_basic(t *testing.T) {
datasourceAddress := fmt.Sprintf("data.helm_template.%s", testResourceName)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateConfigBasic(testResourceName, namespace, name, "1.2.3"),
Check: resource.ComposeAggregateTestCheckFunc(
@ -43,8 +42,7 @@ func TestAccDataTemplate_crds(t *testing.T) {
datasourceAddress := fmt.Sprintf("data.helm_template.%s", testResourceName)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateCRDs(testResourceName, namespace, name, "1.2.3"),
Check: resource.ComposeAggregateTestCheckFunc(
@ -181,8 +179,7 @@ data:
`, name)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateConfigTemplates(testResourceName, namespace, name, "1.2.3"),
Check: resource.ComposeAggregateTestCheckFunc(
@ -201,40 +198,32 @@ func TestAccDataTemplate_kubeVersion(t *testing.T) {
datasourceAddress := fmt.Sprintf("data.helm_template.%s", testResourceName)
// No kube version set, will fail as v1.20.0.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateKubeVersionNoVersionSet(testResourceName, namespace, name, "1.2.3"),
ExpectError: regexp.MustCompile("chart requires kubeVersion.*"),
}},
})
// Kube Version set but for a to low version.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateKubeVersion(testResourceName, namespace, name, "1.2.3", "1.18.0"),
ExpectError: regexp.MustCompile("chart requires kubeVersion.*"),
}},
})
// Kube Version set but not parsable.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateKubeVersion(testResourceName, namespace, name, "1.2.3", "abcdef"),
ExpectError: regexp.MustCompile(`couldn't parse string "abcdef" into kube-version`),
}},
})
// Kube Version set and above the min version.
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
ProtoV6ProviderFactories: protoV6ProviderFactories(),
Steps: []resource.TestStep{{
Config: testAccDataHelmTemplateKubeVersion(testResourceName, namespace, name, "1.2.3", "1.22.0"),
Check: resource.ComposeAggregateTestCheckFunc(
@ -249,7 +238,8 @@ func TestAccDataTemplate_kubeVersion(t *testing.T) {
func testAccDataHelmTemplateConfigBasic(resource, ns, name, version string) string {
return fmt.Sprintf(`
data "helm_template" "%s" {
show_only = [""]
show_only = [""]
name = %q
namespace = %q
description = "Test"
@ -257,15 +247,16 @@ func testAccDataHelmTemplateConfigBasic(resource, ns, name, version string) stri
chart = "test-chart"
version = %q
set {
name = "foo"
value = "bar"
}
set {
name = "fizz"
value = 1337
}
set = [
{
name = "foo"
value = "bar"
},
{
name = "fizz"
value = 1337
}
]
}
`, resource, name, ns, testRepositoryURL, version)
}
@ -280,15 +271,16 @@ func testAccDataHelmTemplateConfigTemplates(resource, ns, name, version string)
chart = "test-chart"
version = %q
set {
name = "foo"
value = "bar"
}
set {
name = "fizz"
value = 1337
}
set = [
{
name = "foo"
value = "bar"
},
{
name = "fizz"
value = 1337
}
]
show_only = [
"templates/configmaps.yaml",

View file

@ -1,645 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package helm
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chart/loader"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
"helm.sh/helm/v3/pkg/releaseutil"
)
// defaultTemplateAttributes template attribute values
var defaultTemplateAttributes = map[string]interface{}{
"validate": false,
"include_crds": false,
"is_upgrade": false,
"skip_tests": false,
}
func dataTemplate() *schema.Resource {
return &schema.Resource{
ReadContext: dataTemplateRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Release name.",
},
"repository": {
Type: schema.TypeString,
Optional: true,
Description: "Repository where to locate the requested chart. If is a URL the chart is installed without installing the repository.",
},
"repository_key_file": {
Type: schema.TypeString,
Optional: true,
Description: "The repositories cert key file",
},
"repository_cert_file": {
Type: schema.TypeString,
Optional: true,
Description: "The repositories cert file",
},
"repository_ca_file": {
Type: schema.TypeString,
Optional: true,
Description: "The Repositories CA File",
},
"repository_username": {
Type: schema.TypeString,
Optional: true,
Description: "Username for HTTP basic authentication",
},
"repository_password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
Description: "Password for HTTP basic authentication",
},
"pass_credentials": {
Type: schema.TypeBool,
Optional: true,
Description: "Pass credentials to all domains. Defaults to `false`.",
},
"chart": {
Type: schema.TypeString,
Required: true,
Description: "Chart name to be installed. A path may be used.",
},
"version": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Specify the exact chart version to install. If this is not specified, the latest version is installed.",
},
"devel": {
Type: schema.TypeBool,
Optional: true,
Description: "Use chart development versions, too. Equivalent to version '>0.0.0-0'. If `version` is set, this is ignored",
// Suppress changes of this attribute if `version` is set
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return d.Get("version").(string) != ""
},
},
"values": {
Type: schema.TypeList,
Optional: true,
Description: "List of values in raw yaml format to pass to helm.",
Elem: &schema.Schema{Type: schema.TypeString},
},
"set": {
Type: schema.TypeSet,
Optional: true,
Description: "Custom values to be merged with the values.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeString,
Required: true,
},
"type": {
Type: schema.TypeString,
Optional: true,
// TODO: use ValidateDiagFunc once an SDK v2 version of StringInSlice exists.
// https://github.com/hashicorp/terraform-plugin-sdk/issues/534
ValidateFunc: validation.StringInSlice([]string{
"auto", "string",
}, false),
},
},
},
},
"set_list": {
Type: schema.TypeList,
Optional: true,
Description: "Custom list values to be merged with the values.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
},
},
},
"set_sensitive": {
Type: schema.TypeSet,
Optional: true,
Description: "Custom sensitive values to be merged with the values.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
},
"type": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validation.StringInSlice([]string{
"auto", "string",
}, false),
},
},
},
},
"set_string": {
Type: schema.TypeSet,
Optional: true,
Description: "Custom string values to be merged with the values.",
Deprecated: "This argument is deprecated and will be removed in the next major" +
" version. Use `set` argument with `type` equals to `string`",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
},
"value": {
Type: schema.TypeString,
Required: true,
},
},
},
},
"namespace": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "Namespace to install the release into. Defaults to `default`.",
DefaultFunc: schema.EnvDefaultFunc("HELM_NAMESPACE", "default"),
},
"verify": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["verify"],
Description: "Verify the package before installing it.Defaults to `false`.",
},
"keyring": {
Type: schema.TypeString,
Optional: true,
Default: os.ExpandEnv("$HOME/.gnupg/pubring.gpg"),
Description: "Location of public keys used for verification. Used only if `verify` is true. Defaults to `/.gnupg/pubring.gpg` in the location set by `home`.",
// Suppress changes of this attribute if `verify` is false
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return !d.Get("verify").(bool)
},
},
"timeout": {
Type: schema.TypeInt,
Optional: true,
Default: defaultAttributes["timeout"],
Description: "Time in seconds to wait for any individual kubernetes operation. Defaults to `300` seconds.",
},
"disable_webhooks": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["disable_webhooks"],
Description: "Prevent hooks from running.Defaults to `300` seconds.",
},
"reuse_values": {
Type: schema.TypeBool,
Optional: true,
Description: "When upgrading, reuse the last release's values and merge in any overrides. If 'reset_values' is specified, this is ignored. Defaults to `false`. ",
Default: defaultAttributes["reuse_values"],
},
"reset_values": {
Type: schema.TypeBool,
Optional: true,
Description: "When upgrading, reset the values to the ones built into the chart.Defaults to `false`. ",
Default: defaultAttributes["reset_values"],
},
"atomic": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["atomic"],
Description: "If set, installation process purges chart on fail. The wait flag will be set automatically if atomic is used. Defaults to `false`.",
},
"skip_crds": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["skip_crds"],
Description: "If set, no CRDs will be installed. By default, CRDs are installed if not already present. Defaults to `false`.",
},
"skip_tests": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["skip_tests"],
Description: "If set, tests will not be rendered. By default, tests are rendered. Defaults to `false`.",
},
"render_subchart_notes": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["render_subchart_notes"],
Description: "If set, render subchart notes along with the parent. Defaults to `true`.",
},
"disable_openapi_validation": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["disable_openapi_validation"],
Description: "If set, the installation process will not validate rendered templates against the Kubernetes OpenAPI Schema.Defaults to `false`.",
},
"wait": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["wait"],
Description: "Will wait until all resources are in a ready state before marking the release as successful.Defaults to `true`.",
},
"dependency_update": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["dependency_update"],
Description: "Run helm dependency update before installing the chart. Defaults to `false`.",
},
"replace": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["replace"],
Description: "Re-use the given name, even if that name is already used. This is unsafe in production. Defaults to `false`.",
},
"description": {
Type: schema.TypeString,
Optional: true,
Description: "Add a custom description",
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
return new == ""
},
},
"create_namespace": {
Type: schema.TypeBool,
Optional: true,
Default: defaultAttributes["create_namespace"],
Description: "Create the namespace if it does not exist. Defaults to `false`.",
},
"postrender": {
Type: schema.TypeList,
MaxItems: 1,
Optional: true,
Description: "Postrender command configuration.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"binary_path": {
Type: schema.TypeString,
Required: true,
Description: "The command binary path.",
},
},
},
},
"api_versions": {
Type: schema.TypeList,
Optional: true,
Description: "Kubernetes api versions used for Capabilities.APIVersions",
Elem: &schema.Schema{Type: schema.TypeString},
},
"include_crds": {
Type: schema.TypeBool,
Optional: true,
Default: defaultTemplateAttributes["include_crds"],
Description: "Include CRDs in the templated output",
},
"is_upgrade": {
Type: schema.TypeBool,
Optional: true,
Default: defaultTemplateAttributes["is_upgrade"],
Description: "Set .Release.IsUpgrade instead of .Release.IsInstall",
},
"show_only": {
Type: schema.TypeList,
Optional: true,
Description: "Only show manifests rendered from the given templates",
Elem: &schema.Schema{Type: schema.TypeString},
},
"validate": {
Type: schema.TypeBool,
Optional: true,
Default: defaultTemplateAttributes["validate"],
Description: "Validate your manifests against the Kubernetes cluster you are currently pointing at. This is the same validation performed on an install",
},
"manifests": {
Type: schema.TypeMap,
Optional: true,
Computed: true,
Description: "Map of rendered chart templates indexed by the template name.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"crds": {
Type: schema.TypeList,
Optional: true,
Computed: true,
Description: "List of rendered CRDs from the chart.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"manifest": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Concatenated rendered chart templates. This corresponds to the output of the `helm template` command.",
},
"notes": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "Rendered notes if the chart contains a `NOTES.txt`.",
},
"kube_version": {
Type: schema.TypeString,
Optional: true,
Description: "Kubernetes version used for Capabilities.KubeVersion",
},
},
}
}
func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
logID := fmt.Sprintf("[dataTemplateRead: %s]", d.Get("name").(string))
debug("%s Started", logID)
m := meta.(*Meta)
name := d.Get("name").(string)
n := d.Get("namespace").(string)
var apiVersions []string
if apiVersionsAttr, ok := d.GetOk("api_versions"); ok {
apiVersionsValues := apiVersionsAttr.([]interface{})
for _, apiVersion := range apiVersionsValues {
apiVersions = append(apiVersions, apiVersion.(string))
}
}
var showFiles []string
if showOnlyAttr, ok := d.GetOk("show_only"); ok {
showOnlyAttrValue := showOnlyAttr.([]interface{})
for _, showFile := range showOnlyAttrValue {
if s, ok := showFile.(string); ok && len(s) > 0 {
showFiles = append(showFiles, s)
}
}
}
debug("%s Getting Config", logID)
actionConfig, err := m.GetHelmConfiguration(n)
if err != nil {
return diag.FromErr(err)
}
err = OCIRegistryLogin(actionConfig, d, m)
if err != nil {
return diag.FromErr(err)
}
client := action.NewInstall(actionConfig)
cpo, chartName, err := chartPathOptions(d, m, &client.ChartPathOptions)
if err != nil {
return diag.FromErr(err)
}
debug("%s Getting chart", logID)
c, path, err := getChart(d, m, chartName, cpo)
if err != nil {
return diag.FromErr(err)
}
// check and update the chart's dependencies if needed
updated, err := checkChartDependencies(d, c, path, m)
if err != nil {
return diag.FromErr(err)
} else if updated {
// load the chart again if its dependencies have been updated
c, err = loader.Load(path)
if err != nil {
return diag.FromErr(err)
}
}
debug("%s Preparing for installation", logID)
values, err := getValues(d)
if err != nil {
return diag.FromErr(err)
}
err = isChartInstallable(c)
if err != nil {
return diag.FromErr(err)
}
client.ChartPathOptions = *cpo
client.ClientOnly = false
client.DryRun = true
client.DisableHooks = d.Get("disable_webhooks").(bool)
client.Wait = d.Get("wait").(bool)
client.Devel = d.Get("devel").(bool)
client.DependencyUpdate = d.Get("dependency_update").(bool)
client.Timeout = time.Duration(d.Get("timeout").(int)) * time.Second
client.Namespace = d.Get("namespace").(string)
client.ReleaseName = d.Get("name").(string)
client.GenerateName = false
client.NameTemplate = ""
client.OutputDir = ""
client.Atomic = d.Get("atomic").(bool)
client.SkipCRDs = d.Get("skip_crds").(bool)
client.SubNotes = d.Get("render_subchart_notes").(bool)
client.DisableOpenAPIValidation = d.Get("disable_openapi_validation").(bool)
client.Replace = d.Get("replace").(bool)
client.Description = d.Get("description").(string)
client.CreateNamespace = d.Get("create_namespace").(bool)
if ver := d.Get("kube_version").(string); ver != "" {
parsedVer, err := chartutil.ParseKubeVersion(ver)
if err != nil {
return diag.FromErr(fmt.Errorf("couldn't parse string %q into kube-version", ver))
}
client.KubeVersion = parsedVer
}
// The following source has been adapted from the source of the helm template command
// https://github.com/helm/helm/blob/v3.5.3/cmd/helm/template.go#L67
client.DryRun = true
// NOTE Do not set fixed release name as client.ReleaseName like in helm template command
client.Replace = true // Skip the name check
client.ClientOnly = !d.Get("validate").(bool)
client.APIVersions = chartutil.VersionSet(apiVersions)
client.IncludeCRDs = d.Get("include_crds").(bool)
skipTests := d.Get("skip_tests").(bool)
debug("%s Rendering Chart", logID)
rel, err := client.Run(c, values)
if err != nil {
return diag.FromErr(err)
}
var manifests bytes.Buffer
fmt.Fprintln(&manifests, strings.TrimSpace(rel.Manifest))
if !client.DisableHooks {
for _, m := range rel.Hooks {
if skipTests && isTestHook(m) {
continue
}
fmt.Fprintf(&manifests, "---\n# Source: %s\n%s\n", m.Path, m.Manifest)
}
}
// Difference to the implementation of helm template in newTemplateCmd:
// Independent of templates, names of the charts templates are always resolved from the manifests
// to be able to populate the keys in the manifests computed attribute.
var manifestsToRender []string
splitManifests := releaseutil.SplitManifests(manifests.String())
manifestsKeys := make([]string, 0, len(splitManifests))
for k := range splitManifests {
manifestsKeys = append(manifestsKeys, k)
}
sort.Sort(releaseutil.BySplitManifestsOrder(manifestsKeys))
var chartCRDs []string
for _, crd := range rel.Chart.CRDObjects() {
chartCRDs = append(chartCRDs, string(crd.File.Data))
}
// Mapping of manifest key to manifest template name
manifestNamesByKey := make(map[string]string, len(manifestsKeys))
manifestNameRegex := regexp.MustCompile("# Source: [^/]+/(.+)")
for _, manifestKey := range manifestsKeys {
manifest := splitManifests[manifestKey]
submatch := manifestNameRegex.FindStringSubmatch(manifest)
if len(submatch) == 0 {
continue
}
manifestName := submatch[1]
manifestNamesByKey[manifestKey] = manifestName
}
// if we have a list of files to render, then check that each of the
// provided files exists in the chart.
if len(showFiles) > 0 {
for _, f := range showFiles {
missing := true
// Use linux-style filepath separators to unify user's input path
f = filepath.ToSlash(f)
for manifestKey, manifestName := range manifestNamesByKey {
// manifest.Name is rendered using linux-style filepath separators on Windows as
// well as macOS/linux.
manifestPathSplit := strings.Split(manifestName, "/")
// manifest.Path is connected using linux-style filepath separators on Windows as
// well as macOS/linux
manifestPath := strings.Join(manifestPathSplit, "/")
// if the filepath provided matches a manifest path in the
// chart, render that manifest
if matched, _ := filepath.Match(f, manifestPath); !matched {
continue
}
manifestsToRender = append(manifestsToRender, manifestKey)
missing = false
}
if missing {
return diag.Errorf("could not find template %q in chart", f)
}
}
} else {
manifestsToRender = manifestsKeys
}
// We need to sort the manifests so the order stays stable when they are
// concatenated back together in the computedManifests map
sort.Strings(manifestsToRender)
// Map from rendered manifests to data source output
computedManifests := make(map[string]string, 0)
computedManifest := &strings.Builder{}
for _, manifestKey := range manifestsToRender {
manifest := splitManifests[manifestKey]
manifestName := manifestNamesByKey[manifestKey]
// Manifests
computedManifests[manifestName] = fmt.Sprintf("%s---\n%s\n", computedManifests[manifestName], manifest)
// Manifest bundle
fmt.Fprintf(computedManifest, "---\n%s\n", manifest)
}
computedNotes := rel.Info.Notes
d.SetId(name)
err = d.Set("crds", chartCRDs)
if err != nil {
return diag.FromErr(err)
}
err = d.Set("manifests", computedManifests)
if err != nil {
return diag.FromErr(err)
}
err = d.Set("manifest", computedManifest.String())
if err != nil {
return diag.FromErr(err)
}
err = d.Set("notes", computedNotes)
if err != nil {
return diag.FromErr(err)
}
return nil
}
func isTestHook(h *release.Hook) bool {
for _, e := range h.Events {
if e == release.HookTest {
return true
}
}
return false
}

232
helm/kubeconfig.go Normal file
View file

@ -0,0 +1,232 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package helm
import (
"context"
"fmt"
"os"
"path/filepath"
"sync"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mitchellh/go-homedir"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// Struct holding k8s client config, burst limit for api requests, and mutex for sync
type KubeConfig struct {
ClientConfig clientcmd.ClientConfig
Burst int
sync.Mutex
}
// Converting KubeConfig to a REST config, which will be used to create k8s clients
func (k *KubeConfig) ToRESTConfig() (*rest.Config, error) {
config, err := k.ToRawKubeConfigLoader().ClientConfig()
return config, err
}
// Converting KubeConfig to a discovery client, which will be used to find api resources
func (k *KubeConfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := k.ToRESTConfig()
if err != nil {
return nil, err
}
config.Burst = k.Burst
return memory.NewMemCacheClient(discovery.NewDiscoveryClientForConfigOrDie(config)), nil
}
// Converting KubeConfig to a REST mapper, which will be used to map REST resources to their API obj
func (k *KubeConfig) ToRESTMapper() (meta.RESTMapper, error) {
discoveryClient, err := k.ToDiscoveryClient()
if err != nil {
return nil, err
}
// Using the appropriate types for the arguments
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
warningHandler := func(warning string) {
fmt.Printf("Warning: %s\n", warning)
}
// Pass the warning handler to the NewShortcutExpander function
expander := restmapper.NewShortcutExpander(mapper, discoveryClient, warningHandler)
return expander, nil
}
// Function returning raw k8s client config
func (k *KubeConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return k.ClientConfig
}
// Generates a k8s client config, based on providers settings and namespace, which this config will be used to interact with the k8s cluster
func (m *Meta) NewKubeConfig(ctx context.Context, namespace string) (*KubeConfig, error) {
overrides := &clientcmd.ConfigOverrides{}
loader := &clientcmd.ClientConfigLoadingRules{}
configPaths := []string{}
if m == nil || m.Data == nil || m.Data.Kubernetes.IsNull() || m.Data.Kubernetes.IsUnknown() {
return nil, fmt.Errorf("configuration error: missing required structural data")
}
tflog.Debug(ctx, "Raw Kubernetes Data before conversion", map[string]interface{}{
"KubernetesData": m.Data.Kubernetes,
})
// Needing to get the Kubernetes configuration as an obj
var kubernetesConfig KubernetesConfigModel
diags := m.Data.Kubernetes.As(ctx, &kubernetesConfig, basetypes.ObjectAsOptions{})
if diags.HasError() {
for _, d := range diags {
tflog.Error(ctx, "Kubernetes config conversion error", map[string]interface{}{
"summary": d.Summary(),
"detail": d.Detail(),
})
}
return nil, fmt.Errorf("configuration error: unable to extract Kubernetes config")
}
// Check ConfigPath
if !kubernetesConfig.ConfigPath.IsNull() {
if v := kubernetesConfig.ConfigPath.ValueString(); v != "" {
configPaths = []string{v}
}
}
if !kubernetesConfig.ConfigPaths.IsNull() {
additionalPaths := expandStringSlice(kubernetesConfig.ConfigPaths.Elements())
configPaths = append(configPaths, additionalPaths...)
}
if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
configPaths = filepath.SplitList(v)
}
tflog.Debug(ctx, "Initial configPaths", map[string]interface{}{"configPaths": configPaths})
if len(configPaths) > 0 {
expandedPaths := []string{}
for _, p := range configPaths {
path, err := homedir.Expand(p)
if err != nil {
tflog.Error(ctx, "Error expanding home directory", map[string]interface{}{
"path": p,
"error": err,
})
return nil, err
}
expandedPaths = append(expandedPaths, path)
}
if len(expandedPaths) == 1 {
loader.ExplicitPath = expandedPaths[0]
} else {
loader.Precedence = expandedPaths
}
// Check ConfigContext
if !kubernetesConfig.ConfigContext.IsNull() {
overrides.CurrentContext = kubernetesConfig.ConfigContext.ValueString()
}
if !kubernetesConfig.ConfigContextAuthInfo.IsNull() {
overrides.Context.AuthInfo = kubernetesConfig.ConfigContextAuthInfo.ValueString()
}
if !kubernetesConfig.ConfigContextCluster.IsNull() {
overrides.Context.Cluster = kubernetesConfig.ConfigContextCluster.ValueString()
}
}
// Check and assign remaining fields
if !kubernetesConfig.Insecure.IsNull() {
overrides.ClusterInfo.InsecureSkipTLSVerify = kubernetesConfig.Insecure.ValueBool()
}
if !kubernetesConfig.TLSServerName.IsNull() {
overrides.ClusterInfo.TLSServerName = kubernetesConfig.TLSServerName.ValueString()
}
if !kubernetesConfig.ClusterCACertificate.IsNull() {
overrides.ClusterInfo.CertificateAuthorityData = []byte(kubernetesConfig.ClusterCACertificate.ValueString())
}
if !kubernetesConfig.ClientCertificate.IsNull() {
overrides.AuthInfo.ClientCertificateData = []byte(kubernetesConfig.ClientCertificate.ValueString())
}
if !kubernetesConfig.Host.IsNull() && kubernetesConfig.Host.ValueString() != "" {
hasCA := len(overrides.ClusterInfo.CertificateAuthorityData) != 0
hasCert := len(overrides.AuthInfo.ClientCertificateData) != 0
defaultTLS := hasCA || hasCert || overrides.ClusterInfo.InsecureSkipTLSVerify
host, _, err := rest.DefaultServerURL(kubernetesConfig.Host.ValueString(), "", schema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, err
}
overrides.ClusterInfo.Server = host.String()
}
if !kubernetesConfig.Username.IsNull() {
overrides.AuthInfo.Username = kubernetesConfig.Username.ValueString()
}
if !kubernetesConfig.Password.IsNull() {
overrides.AuthInfo.Password = kubernetesConfig.Password.ValueString()
}
if !kubernetesConfig.ClientKey.IsNull() {
overrides.AuthInfo.ClientKeyData = []byte(kubernetesConfig.ClientKey.ValueString())
}
if !kubernetesConfig.Token.IsNull() {
overrides.AuthInfo.Token = kubernetesConfig.Token.ValueString()
}
if !kubernetesConfig.ProxyURL.IsNull() {
overrides.ClusterDefaults.ProxyURL = kubernetesConfig.ProxyURL.ValueString()
}
if kubernetesConfig.Exec != nil {
execConfig := kubernetesConfig.Exec
if !execConfig.APIVersion.IsNull() && !execConfig.Command.IsNull() {
args := []string{}
if !execConfig.Args.IsNull() && !execConfig.Args.IsUnknown() {
args = expandStringSlice(execConfig.Args.Elements())
}
env := []clientcmdapi.ExecEnvVar{}
if !execConfig.Env.IsNull() && !execConfig.Env.IsUnknown() {
for k, v := range execConfig.Env.Elements() {
env = append(env, clientcmdapi.ExecEnvVar{
Name: k,
Value: v.(types.String).ValueString(),
})
}
}
overrides.AuthInfo.Exec = &clientcmdapi.ExecConfig{
APIVersion: execConfig.APIVersion.ValueString(),
Command: execConfig.Command.ValueString(),
Args: args,
Env: env,
InteractiveMode: clientcmdapi.IfAvailableExecInteractiveMode,
}
}
}
burstLimit := int(m.Data.BurstLimit.ValueInt64())
client := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
if client == nil {
return nil, fmt.Errorf("failed to initialize kubernetes config")
}
tflog.Info(ctx, "Successfully initialized kubernetes config")
return &KubeConfig{ClientConfig: client, Burst: burstLimit}, nil
}
func expandStringSlice(input []attr.Value) []string {
result := make([]string, len(input))
for i, v := range input {
result[i] = v.(types.String).ValueString()
}
return result
}

View file

@ -10,8 +10,6 @@ import (
"golang.org/x/crypto/sha3"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"
@ -78,29 +76,19 @@ func convertYAMLManifestToJSON(manifest string) (string, error) {
return string(b), nil
}
// hashSensitiveValue creates a hash of a sensitive value and returns the string
// "(sensitive value xxxxxxxx)". We have to do this because Terraform's sensitive
// value feature can't reach inside a text string and would supress the entire
// manifest if we marked it as sensitive. This allows us to redact the value while
// still being able to surface that something has changed so it appears in the diff.
func hashSensitiveValue(v string) string {
hash := make([]byte, 8)
sha3.ShakeSum256(hash, []byte(v))
return fmt.Sprintf("(sensitive value %x)", hash)
}
// redactSensitiveValues removes values that appear in `set_sensitive` blocks from
// the manifest JSON
func redactSensitiveValues(text string, d resourceGetter) string {
// redactSensitiveValues removes values that appear in `set_sensitive` blocks from the manifest JSON
func redactSensitiveValues(text string, sensitiveValues map[string]string) string {
masked := text
for _, v := range d.Get("set_sensitive").(*schema.Set).List() {
vv := v.(map[string]interface{})
if sensitiveValue, ok := vv["value"].(string); ok {
h := hashSensitiveValue(sensitiveValue)
masked = strings.ReplaceAll(masked, sensitiveValue, h)
}
for originalValue := range sensitiveValues {
hashedValue := hashSensitiveValue(originalValue)
masked = strings.ReplaceAll(masked, originalValue, hashedValue)
}
return masked

File diff suppressed because it is too large Load diff

View file

@ -16,10 +16,10 @@ import (
"sync"
"testing"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
@ -37,24 +37,32 @@ const (
var (
accTest bool
testRepositoryURL string
testAccProviders map[string]*schema.Provider
testAccProvider *schema.Provider
client kubernetes.Interface = nil
client kubernetes.Interface = nil
testMeta *Meta
)
func TestMain(m *testing.M) {
testAccProvider = Provider()
testAccProviders = map[string]*schema.Provider{
"helm": testAccProvider,
var providerFactory map[string]func() (tfprotov6.ProviderServer, error)
func protoV6ProviderFactories() map[string]func() (tfprotov6.ProviderServer, error) {
if len(providerFactory) != 0 {
return providerFactory
}
home, err := ioutil.TempDir(os.TempDir(), "helm")
providerFactory = map[string]func() (tfprotov6.ProviderServer, error){
"helm": providerserver.NewProtocol6WithError(New("test")()),
}
return providerFactory
}
func TestMain(m *testing.M) {
home, err := ioutil.TempDir(os.TempDir(), "helm")
if err != nil {
panic(err)
}
defer os.RemoveAll(home)
err = os.Setenv("HELM_REPOSITORY_CONFIG", filepath.Join(home, "config/repositories.yaml"))
if err != nil {
panic(err)
@ -99,16 +107,10 @@ func TestMain(m *testing.M) {
// Build the test repository and start the server
buildChartRepository()
testRepositoryURL, stopRepositoryServer = startRepositoryServer()
log.Println("Test repository is listening on", testRepositoryURL)
}
ec := m.Run()
err = os.RemoveAll(home)
if err != nil {
panic(err)
}
if accTest {
stopRepositoryServer()
cleanupChartRepository()
@ -117,16 +119,38 @@ func TestMain(m *testing.M) {
os.Exit(ec)
}
// todo
func TestProvider(t *testing.T) {
if err := Provider().InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
ctx := context.Background()
provider := New("test")()
// Create the provider server
providerServer, err := createProviderServer(provider)
if err != nil {
t.Fatalf("Failed to create provider server: %s", err)
}
// Perform config validation
validateResponse, err := providerServer.ValidateProviderConfig(ctx, &tfprotov6.ValidateProviderConfigRequest{})
if err != nil {
t.Fatalf("Provider config validation failed, error: %v", err)
}
if hasError(validateResponse.Diagnostics) {
t.Fatalf("Provider config validation failed, diagnostics: %v", validateResponse.Diagnostics)
}
}
// buildChartRepository packages all the test charts and builds the repository index
func buildChartRepository() {
log.Println("Building chart repository...")
func createProviderServer(provider provider.Provider) (tfprotov6.ProviderServer, error) {
providerServerFunc := providerserver.NewProtocol6WithError(provider)
server, err := providerServerFunc()
if err != nil {
} else {
}
return server, err
}
func buildChartRepository() {
if _, err := os.Stat(testRepositoryDir); os.IsNotExist(err) {
os.Mkdir(testRepositoryDir, os.ModePerm)
}
@ -138,7 +162,7 @@ func buildChartRepository() {
// package all the charts
for _, c := range charts {
cmd := exec.Command("helm", "--kubeconfig", os.Getenv("KUBE_CONFIG_PATH"), "package", "-u",
cmd := exec.Command("helm", "package", "-u",
filepath.Join(testChartsPath, c.Name()),
"-d", testRepositoryDir)
out, err := cmd.CombinedOutput()
@ -151,7 +175,7 @@ func buildChartRepository() {
}
// build the repository index
cmd := exec.Command("helm", "--kubeconfig", os.Getenv("KUBE_CONFIG_PATH"), "repo", "index", testRepositoryDir)
cmd := exec.Command("helm", "repo", "index", testRepositoryDir)
out, err := cmd.CombinedOutput()
if err != nil {
log.Println(string(out))
@ -161,7 +185,6 @@ func buildChartRepository() {
log.Println("Built chart repository index")
}
// cleanupChartRepository cleans up the repository of test charts
func cleanupChartRepository() {
if _, err := os.Stat(testRepositoryDir); err == nil {
err := os.RemoveAll(testRepositoryDir)
@ -171,8 +194,6 @@ func cleanupChartRepository() {
}
}
// startRepositoryServer starts a helm repository in a goroutine using
// a plain HTTP server on a random port and returns the URL
func startRepositoryServer() (string, func()) {
wg := sync.WaitGroup{}
wg.Add(1)
@ -182,8 +203,6 @@ func startRepositoryServer() (string, func()) {
fileserver := http.Server{
Handler: http.FileServer(http.Dir(testRepositoryDir)),
}
// NOTE we disable keep alive to prevent the server from chewing
// up a lot of open connections as the test suite is run
fileserver.SetKeepAlivesEnabled(false)
shutdownFunc = func() { fileserver.Shutdown(context.Background()) }
listener, err := net.Listen("tcp", ":0")
@ -203,16 +222,54 @@ func startRepositoryServer() (string, func()) {
return testRepositoryURL, shutdownFunc
}
func createAndConfigureProviderServer(provider provider.Provider, ctx context.Context) (tfprotov6.ProviderServer, error) {
log.Println("Starting createAndConfigureProviderServer...")
providerServerFunc := providerserver.NewProtocol6WithError(provider)
providerServer, err := providerServerFunc()
if err != nil {
return nil, fmt.Errorf("Failed to create protocol6 provider: %w", err)
}
log.Println("Provider server function created successfully.")
configResponse, err := providerServer.ConfigureProvider(ctx, nil)
if err != nil {
return nil, fmt.Errorf("Error configuring provider: %w", err)
}
log.Println("Provider configured successfully.")
if hasError(configResponse.Diagnostics) {
return nil, fmt.Errorf("Provider configuration failed, diagnostics: %#v", configResponse.Diagnostics[0])
}
if helmProvider, ok := provider.(*HelmProvider); ok {
testMeta = helmProvider.meta
if testMeta == nil {
log.Println("testMeta is nil after type assertion.")
} else {
log.Printf("testMeta initialized: %+v", testMeta)
}
} else {
return nil, fmt.Errorf("Failed to type assert provider to HelmProvider")
}
return providerServer, nil
}
func testAccPreCheck(t *testing.T) {
if !accTest {
t.Skip("TF_ACC=1 not set")
if testing.Short() {
t.Skip("skipping acceptance tests in short mode")
}
http.DefaultClient.CloseIdleConnections()
ctx := context.TODO()
diags := testAccProvider.Configure(ctx, terraform.NewResourceConfigRaw(nil))
if diags.HasError() {
t.Fatal(diags)
ctx := context.TODO()
provider := New("test")()
// Create and configure the ProviderServer
_, err := createAndConfigureProviderServer(provider, ctx)
if err != nil {
t.Fatalf("Pre-check failed: %v", err)
}
}
@ -275,3 +332,19 @@ func deleteNamespace(t *testing.T, namespace string) {
func randName(prefix string) string {
return fmt.Sprintf("%s-%s", prefix, acctest.RandString(10))
}
func hasError(diagnostics []*tfprotov6.Diagnostic) bool {
for _, diagnostic := range diagnostics {
if diagnostic.Severity == tfprotov6.DiagnosticSeverityError {
return true
}
}
return false
}
func DynamicValueEmpty() *tfprotov6.DynamicValue {
return &tfprotov6.DynamicValue{
MsgPack: nil,
JSON: nil,
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,207 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package helm
import (
"bytes"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/mitchellh/go-homedir"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/clientcmd"
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
memcached "k8s.io/client-go/discovery/cached/memory"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// KubeConfig is a RESTClientGetter interface implementation
type KubeConfig struct {
ClientConfig clientcmd.ClientConfig
Burst int
sync.Mutex
}
// ToRESTConfig implemented interface method
func (k *KubeConfig) ToRESTConfig() (*rest.Config, error) {
config, err := k.ToRawKubeConfigLoader().ClientConfig()
return config, err
}
// ToDiscoveryClient implemented interface method
func (k *KubeConfig) ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) {
config, err := k.ToRESTConfig()
if err != nil {
return nil, err
}
// The more groups you have, the more discovery requests you need to make.
// given 25 groups (our groups + a few custom resources) with one-ish version each, discovery needs to make 50 requests
// double it just so we don't end up here again for a while. This config is only used for discovery.
config.Burst = k.Burst
return memcached.NewMemCacheClient(discovery.NewDiscoveryClientForConfigOrDie(config)), nil
}
// ToRESTMapper implemented interface method
func (k *KubeConfig) ToRESTMapper() (meta.RESTMapper, error) {
discoveryClient, err := k.ToDiscoveryClient()
if err != nil {
return nil, err
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(discoveryClient)
expander := restmapper.NewShortcutExpander(mapper, discoveryClient, nil)
return expander, nil
}
// ToRawKubeConfigLoader implemented interface method
func (k *KubeConfig) ToRawKubeConfigLoader() clientcmd.ClientConfig {
return k.ClientConfig
}
func newKubeConfig(configData *schema.ResourceData, namespace *string) (*KubeConfig, error) {
overrides := &clientcmd.ConfigOverrides{}
loader := &clientcmd.ClientConfigLoadingRules{}
configPaths := []string{}
if v, ok := k8sGetOk(configData, "config_path"); ok && v != "" {
configPaths = []string{v.(string)}
} else if v, ok := k8sGetOk(configData, "config_paths"); ok {
for _, p := range v.([]interface{}) {
configPaths = append(configPaths, p.(string))
}
} else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
// NOTE we have to do this here because the schema
// does not yet allow you to set a default for a TypeList
configPaths = filepath.SplitList(v)
}
if len(configPaths) > 0 {
expandedPaths := []string{}
for _, p := range configPaths {
path, err := homedir.Expand(p)
if err != nil {
return nil, err
}
log.Printf("[DEBUG] Using kubeconfig: %s", path)
expandedPaths = append(expandedPaths, path)
}
if len(expandedPaths) == 1 {
loader.ExplicitPath = expandedPaths[0]
} else {
loader.Precedence = expandedPaths
}
ctx, ctxOk := k8sGetOk(configData, "config_context")
authInfo, authInfoOk := k8sGetOk(configData, "config_context_auth_info")
cluster, clusterOk := k8sGetOk(configData, "config_context_cluster")
if ctxOk || authInfoOk || clusterOk {
if ctxOk {
overrides.CurrentContext = ctx.(string)
log.Printf("[DEBUG] Using custom current context: %q", overrides.CurrentContext)
}
overrides.Context = clientcmdapi.Context{}
if authInfoOk {
overrides.Context.AuthInfo = authInfo.(string)
}
if clusterOk {
overrides.Context.Cluster = cluster.(string)
}
log.Printf("[DEBUG] Using overidden context: %#v", overrides.Context)
}
}
// Overriding with static configuration
if v, ok := k8sGetOk(configData, "insecure"); ok {
overrides.ClusterInfo.InsecureSkipTLSVerify = v.(bool)
}
if v, ok := k8sGetOk(configData, "tls_server_name"); ok {
overrides.ClusterInfo.TLSServerName = v.(string)
}
if v, ok := k8sGetOk(configData, "cluster_ca_certificate"); ok {
overrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(configData, "client_certificate"); ok {
overrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(configData, "host"); ok {
// Server has to be the complete address of the kubernetes cluster (scheme://hostname:port), not just the hostname,
// because `overrides` are processed too late to be taken into account by `defaultServerUrlFor()`.
// This basically replicates what defaultServerUrlFor() does with config but for overrides,
// see https://github.com/kubernetes/client-go/blob/v12.0.0/rest/url_utils.go#L85-L87
hasCA := len(overrides.ClusterInfo.CertificateAuthorityData) != 0
hasCert := len(overrides.AuthInfo.ClientCertificateData) != 0
defaultTLS := hasCA || hasCert || overrides.ClusterInfo.InsecureSkipTLSVerify
host, _, err := rest.DefaultServerURL(v.(string), "", apimachineryschema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, err
}
overrides.ClusterInfo.Server = host.String()
}
if v, ok := k8sGetOk(configData, "username"); ok {
overrides.AuthInfo.Username = v.(string)
}
if v, ok := k8sGetOk(configData, "password"); ok {
overrides.AuthInfo.Password = v.(string)
}
if v, ok := k8sGetOk(configData, "client_key"); ok {
overrides.AuthInfo.ClientKeyData = bytes.NewBufferString(v.(string)).Bytes()
}
if v, ok := k8sGetOk(configData, "token"); ok {
overrides.AuthInfo.Token = v.(string)
}
if v, ok := k8sGetOk(configData, "proxy_url"); ok {
overrides.ClusterDefaults.ProxyURL = v.(string)
}
if v, ok := k8sGetOk(configData, "exec"); ok {
exec := &clientcmdapi.ExecConfig{}
if spec, ok := v.([]interface{})[0].(map[string]interface{}); ok {
exec.InteractiveMode = clientcmdapi.IfAvailableExecInteractiveMode
exec.APIVersion = spec["api_version"].(string)
exec.Command = spec["command"].(string)
exec.Args = expandStringSlice(spec["args"].([]interface{}))
for kk, vv := range spec["env"].(map[string]interface{}) {
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.(string)})
}
} else {
log.Printf("[ERROR] Failed to parse exec")
return nil, fmt.Errorf("failed to parse exec")
}
overrides.AuthInfo.Exec = exec
}
overrides.Context.Namespace = "default"
if namespace != nil {
overrides.Context.Namespace = *namespace
}
burstLimit := configData.Get("burst_limit").(int)
client := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
if client == nil {
log.Printf("[ERROR] Failed to initialize kubernetes config")
return nil, nil
}
log.Printf("[INFO] Successfully initialized kubernetes config")
return &KubeConfig{ClientConfig: client, Burst: burstLimit}, nil
}

Binary file not shown.

View file

@ -1,8 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
if [ $# -ne 2 ]
then
echo "Usage: $0 <arg1> <arg2>" >&2
exit 1
fi

View file

@ -14,7 +14,7 @@ resource "kind_cluster" "demo" {
}
provider "helm" {
kubernetes {
kubernetes = {
host = kind_cluster.demo.endpoint
cluster_ca_certificate = kind_cluster.demo.cluster_ca_certificate
client_certificate = kind_cluster.demo.client_certificate

View file

@ -6,7 +6,8 @@ package testing
import (
"testing"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/config"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
@ -17,10 +18,8 @@ import (
"github.com/hashicorp/terraform-provider-helm/helm"
)
var providerFactory = map[string]func() (tfprotov5.ProviderServer, error){
"helm": func() (tfprotov5.ProviderServer, error) {
return helm.Provider().GRPCProvider(), nil
},
var providerFactory = map[string]func() (tfprotov6.ProviderServer, error){
"helm": providerserver.NewProtocol6WithError(helm.New("version")()),
}
func TestAccDeferredActions_basic(t *testing.T) {
@ -34,7 +33,7 @@ func TestAccDeferredActions_basic(t *testing.T) {
},
Steps: []resource.TestStep{
{
ProtoV5ProviderFactories: providerFactory,
ProtoV6ProviderFactories: providerFactory,
ConfigDirectory: func(tscr config.TestStepConfigRequest) string {
return "config-da-basic"
},
@ -56,7 +55,7 @@ func TestAccDeferredActions_basic(t *testing.T) {
},
},
{
ProtoV5ProviderFactories: providerFactory,
ProtoV6ProviderFactories: providerFactory,
ConfigDirectory: func(tscr config.TestStepConfigRequest) string {
return "config-da-basic"
},

28
main.go
View file

@ -6,30 +6,40 @@ package main
import (
"context"
"flag"
"log"
"github.com/hashicorp/terraform-plugin-sdk/v2/plugin"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-provider-helm/helm"
"k8s.io/klog"
)
// Generate docs for website
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs
// Example version string that can be overwritten by a release process
var Version string = "dev"
func main() {
var debug bool
debugFlag := flag.Bool("debug", false, "Start provider in stand-alone debug mode.")
flag.Parse()
klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
klog.InitFlags(klogFlags)
err := klogFlags.Set("logtostderr", "false")
if err != nil {
panic(err)
}
serveOpts := &plugin.ServeOpts{
ProviderFunc: helm.Provider,
opts := providerserver.ServeOpts{
Address: "registry.terraform.io/hashicorp/helm",
Debug: debug,
ProtocolVersion: 6,
}
if debugFlag != nil && *debugFlag {
plugin.Debug(context.Background(), "registry.terraform.io/hashicorp/helm", serveOpts)
} else {
plugin.Serve(serveOpts)
if *debugFlag {
opts.Debug = true
}
serveErr := providerserver.Serve(context.Background(), helm.New(Version), opts)
if serveErr != nil {
log.Fatal(serveErr.Error())
}
}

View file

@ -9,4 +9,4 @@ function get_latest_version() {
sort -V -r | head -1
}
echo "matrix=[$(get_latest_version v0.12), $(get_latest_version v0.13), $(get_latest_version v0.14), $(get_latest_version v0.15), $(get_latest_version v1.0), $(get_latest_version v1.3), $(get_latest_version v1.5), $(get_latest_version v1.7), $(get_latest_version v1.9)]" >> "$GITHUB_OUTPUT"
echo "matrix=[$(get_latest_version v1.0), $(get_latest_version v1.3), $(get_latest_version v1.5), $(get_latest_version v1.7), $(get_latest_version v1.9)]" >> "$GITHUB_OUTPUT"