Add ephemeral resources: kubernetes_token_request_v1, kubernetes_certificate_signing_request_v1 (#2628)

This commit is contained in:
John Houston 2024-11-25 12:54:25 -05:00 committed by GitHub
parent ec878df352
commit b0a4fb976a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 965 additions and 256 deletions

5
.changelog/2628.txt Normal file
View file

@ -0,0 +1,5 @@
```release-note:enhancement
FEATURES:
* New ephemeral resource: `kubernetes_certificate_signing_request_v1`
* New ephemeral resource: `kubernetes_token_request_v1`
```

View file

@ -67,6 +67,7 @@ jobs:
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
run: |
make testacc
make frameworkacc
- name: Destroy AKS
if: always()
working-directory: ${{ github.workspace }}/kubernetes/test-infra/aks

View file

@ -85,6 +85,7 @@ jobs:
# More information: https://developer.hashicorp.com/terraform/plugin/sdkv2/testing/acceptance-tests#terraform-cli-installation-behaviors
run: |
make testacc
make frameworkacc
- name: Destroy EKS cluster
if: always() # we should destroy the cluster even if the tests fail
working-directory: ${{ github.workspace }}/kubernetes/test-infra/eks

View file

@ -85,6 +85,7 @@ jobs:
# More information: https://developer.hashicorp.com/terraform/plugin/sdkv2/testing/acceptance-tests#terraform-cli-installation-behaviors
run: |
make testacc
make frameworkacc
- name: Destroy GKE cluster
if: always() # we should destroy the cluster even if the tests fail
working-directory: ${{ github.workspace }}/kubernetes/test-infra/gke

View file

@ -11,7 +11,7 @@ on:
default: "^TestAcc"
terraformVersion:
description: Terraform version
default: 1.7.5
default: 1.10.0-rc3 # FIXME update this once 1.10 goes out
parallelRuns:
description: The maximum number of tests to run simultaneously
default: 8
@ -29,7 +29,7 @@ env:
KUBECONFIG: ${{ github.workspace }}/.kube/config
KIND_VERSION: ${{ github.event.inputs.kindVersion || vars.KIND_VERSION || '0.25.0' }}
PARALLEL_RUNS: ${{ github.event.inputs.parallelRuns || vars.PARALLEL_RUNS || '8' }}
TERRAFORM_VERSION: ${{ github.event.inputs.terraformVersion || vars.TERRAFORM_VERSION || '1.9.2' }}
TERRAFORM_VERSION: ${{ github.event.inputs.terraformVersion || vars.TERRAFORM_VERSION || '1.10.0-rc3' }}
jobs:
acceptance_tests_kind:
@ -71,7 +71,7 @@ jobs:
- name: Install Terraform
uses: hashicorp/setup-terraform@a1502cd9e758c50496cc9ac5308c4843bcd56d36 # v3.0.0
with:
terraform_version: ${{ env.TERRAFORM_VERSION }}
# terraform_version: ${{ env.TERRAFORM_VERSION }}
terraform_wrapper: false
- name: Setup kind
uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.9.0
@ -90,3 +90,4 @@ jobs:
# More information: https://developer.hashicorp.com/terraform/plugin/sdkv2/testing/acceptance-tests#terraform-cli-installation-behaviors
run: |
make testacc
make frameworkacc

View file

@ -8,6 +8,7 @@ OS_ARCH := $(shell go env GOOS)_$(shell go env GOARCH)
TF_PROV_DOCS := $(PWD)/kubernetes/test-infra/tfproviderdocs
PROVIDER_FUNCTIONS_DIR := "$(PROVIDER_DIR)/internal/framework/provider/functions"
PROVIDER_FRAMEWORK_DIR := "$(PROVIDER_DIR)/internal/framework/provider/..."
ifneq ($(PWD),$(PROVIDER_DIR))
$(error "Makefile must be run from the provider directory")
@ -77,7 +78,10 @@ testacc: fmtcheck vet
TF_ACC=1 go test $(TEST) -v -vet=off $(TESTARGS) -parallel $(PARALLEL_RUNS) -timeout 3h
testfuncs: fmtcheck
go test $(PROVIDER_FUNCTIONS_DIR) -v -vet=off $(TESTARGS) -parallel $(PARALLEL_RUNS)
go test $(PROVIDER_FUNCTIONS_DIR) -v -vet=off $(TESTARGS) -parallel $(PARALLEL_RUNS)
frameworkacc:
TF_ACC=1 go test $(PROVIDER_FRAMEWORK_DIR) -v -vet=off $(TESTARGS) -parallel $(PARALLEL_RUNS)
test-compile:
@if [ "$(TEST)" = "./..." ]; then \
@ -175,4 +179,4 @@ docs-lint-fix: tools
@echo "==> Fixing website terraform blocks code with terrafmt..."
@terrafmt fmt ./docs --pattern '*.markdown'
.PHONY: build test testacc tools vet fmt fmtcheck terrafmt test-compile depscheck tests-lint tests-lint-fix docs-lint docs-lint-fix changelog changelog-entry
.PHONY: build test testacc frameworkacc tools vet fmt fmtcheck terrafmt test-compile depscheck tests-lint tests-lint-fix docs-lint docs-lint-fix changelog changelog-entry

View file

@ -0,0 +1,141 @@
---
subcategory: "certificates/v1"
page_title: "Kubernetes: kubernetes_certificate_signing_request_v1"
description: |-
Use this resource to generate TLS certificates using Kubernetes.
---
# Ephemeral: kubernetes_certificate_signing_request_v1
Use this resource to generate TLS certificates using Kubernetes. This resource enables automation of [X.509](https://www.itu.int/rec/T-REC-X.509) credential provisioning (including TLS/SSL certificates). It does this by creating a CertificateSigningRequest using the Kubernetes API, which generates a certificate from the Certificate Authority (CA) configured in the Kubernetes cluster. The CSR can be approved automatically by Terraform, or it can be approved by a custom controller running in Kubernetes. See [Kubernetes reference](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) for all available options pertaining to CertificateSigningRequests.
## Schema
### Required
- `metadata` (Block List, Min: 1, Max: 1) Standard certificate signing request's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata (see [below for nested schema](#nestedblock--metadata))
- `spec` (Block List, Min: 1, Max: 1) CertificateSigningRequest objects provide a mechanism to obtain x509 certificates by submitting a certificate signing request, and having it asynchronously approved and issued.
Kubelets use this API to obtain:
1. client certificates to authenticate to kube-apiserver (with the "kubernetes.io/kube-apiserver-client-kubelet" signerName).
2. serving certificates for TLS endpoints kube-apiserver can connect to securely (with the "kubernetes.io/kubelet-serving" signerName).
This API can be used to request client certificates to authenticate to kube-apiserver (with the "kubernetes.io/kube-apiserver-client" signerName), or to obtain certificates from custom non-Kubernetes signers. (see [below for nested schema](#nestedblock--spec))
### Optional
- `auto_approve` (Boolean) Automatically approve the CertificateSigningRequest
### Read-Only
- `certificate` (String) certificate is populated with an issued certificate by the signer after an Approved condition is present. This field is set via the /status subresource. Once populated, this field is immutable.
If the certificate signing request is denied, a condition of type "Denied" is added and this field remains empty. If the signer cannot issue the certificate, a condition of type "Failed" is added and this field remains empty.
Validation requirements:
1. certificate must contain one or more PEM blocks.
2. All PEM blocks must have the "CERTIFICATE" label, contain no headers, and the encoded data
must be a BER-encoded ASN.1 Certificate structure as described in section 4 of RFC5280.
3. Non-PEM content may appear before or after the "CERTIFICATE" PEM blocks and is unvalidated,
to allow for explanatory text as described in section 5.2 of RFC7468.
If more than one PEM block is present, and the definition of the requested spec.signerName does not indicate otherwise, the first block is the issued certificate, and subsequent blocks should be treated as intermediate certificates and presented in TLS handshakes.
The certificate is encoded in PEM format.
When serialized as JSON or YAML, the data is additionally base64-encoded, so it consists of:
base64(
- `id` (String) The ID of this resource.
<a id="nestedblock--metadata"></a>
### Nested Schema for `metadata`
Optional:
- `name` (String) Name of the certificate signing request, must be unique. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
<a id="nestedblock--spec"></a>
### Nested Schema for `spec`
Required:
- `request` (String) request contains an x509 certificate signing request encoded in a "CERTIFICATE REQUEST" PEM block. When serialized as JSON or YAML, the data is additionally base64-encoded.
- `signer_name` (String) signerName indicates the requested signer, and is a qualified name.
List/watch requests for CertificateSigningRequests can filter on this field using a "spec.signerName=NAME" fieldSelector.
Well-known Kubernetes signers are:
1. "kubernetes.io/kube-apiserver-client": issues client certificates that can be used to authenticate to kube-apiserver.
Requests for this signer are never auto-approved by kube-controller-manager, can be issued by the "csrsigning" controller in kube-controller-manager.
2. "kubernetes.io/kube-apiserver-client-kubelet": issues client certificates that kubelets use to authenticate to kube-apiserver.
Requests for this signer can be auto-approved by the "csrapproving" controller in kube-controller-manager, and can be issued by the "csrsigning" controller in kube-controller-manager.
3. "kubernetes.io/kubelet-serving" issues serving certificates that kubelets use to serve TLS endpoints, which kube-apiserver can connect to securely.
Requests for this signer are never auto-approved by kube-controller-manager, and can be issued by the "csrsigning" controller in kube-controller-manager.
More details are available at https://k8s.io/docs/reference/access-authn-authz/certificate-signing-requests/#kubernetes-signers
Custom signerNames can also be specified. The signer defines:
1. Trust distribution: how trust (CA bundles) are distributed.
2. Permitted subjects: and behavior when a disallowed subject is requested.
3. Required, permitted, or forbidden x509 extensions in the request (including whether subjectAltNames are allowed, which types, restrictions on allowed values) and behavior when a disallowed extension is requested.
4. Required, permitted, or forbidden key usages / extended key usages.
5. Expiration/certificate lifetime: whether it is fixed by the signer, configurable by the admin.
6. Whether or not requests for CA certificates are allowed.
Optional:
- `expiration_seconds` (Integer) expirationSeconds is the requested duration of validity of the issued certificate.
The certificate signer may issue a certificate with a different validity duration so a client must check the delta between the notBefore and and notAfter fields in the issued certificate to determine the actual duration. The v1.22+ in-tree implementations of the well-known Kubernetes signers will honor this field as long as the requested duration is not greater than the maximum duration they will honor per the --cluster-signing-duration CLI flag to the Kubernetes controller manager.
Certificate signers may not honor this field for various reasons:
1. Old signer that is unaware of the field (such as the in-tree implementations prior to v1.22)
2. Signer whose configured maximum is shorter than the requested duration
3. Signer whose configured minimum is longer than the requested duration
The minimum valid value for expirationSeconds is 600, i.e. 10 minutes.
- `usages` (Set of String) usages specifies a set of key usages requested in the issued certificate.
Requests for TLS client certificates typically request: "digital signature", "key encipherment", "client auth".
Requests for TLS serving certificates typically request: "key encipherment", "digital signature", "server auth".
Valid values are:
"signing", "digital signature", "content commitment",
"key encipherment", "key agreement", "data encipherment",
"cert sign", "crl sign", "encipher only", "decipher only", "any",
"server auth", "client auth",
"code signing", "email protection", "s/mime",
"ipsec end system", "ipsec tunnel", "ipsec user",
"timestamping", "ocsp signing", "microsoft sgc", "netscape sgc"
## Example Usage
```terraform
ephemeral "kubernetes_certificate_signing_request_v1" "example" {
metadata {
name = "example"
}
spec {
usages = ["client auth", "server auth"]
signer_name = "kubernetes.io/kube-apiserver-client"
request = <<EOT
-----BEGIN CERTIFICATE REQUEST-----
MIHSMIGBAgEAMCoxGDAWBgNVBAoTD2V4YW1wbGUgY2x1c3RlcjEOMAwGA1UEAxMF
YWRtaW4wTjAQBgcqhkjOPQIBBgUrgQQAIQM6AASSG8S2+hQvfMq5ucngPCzK0m0C
ImigHcF787djpF2QDbz3oQ3QsM/I7ftdjB/HHlG2a5YpqjzT0KAAMAoGCCqGSM49
BAMCA0AAMD0CHQDErNLjX86BVfOsYh/A4zmjmGknZpc2u6/coTHqAhxcR41hEU1I
DpNPvh30e0Js8/DYn2YUfu/pQU19
-----END CERTIFICATE REQUEST-----
EOT
}
auto_approve = true
}
```

View file

@ -0,0 +1,75 @@
---
subcategory: "authentication/v1"
page_title: "Kubernetes: kubernetes_token_request_v1"
description: |-
TokenRequest requests a token for a given service account.
---
# Ephemeral: kubernetes_token_request_v1
TokenRequest requests a token for a given service account.
## Schema
### Required
- `metadata` (Block List, Min: 1, Max: 1) Standard token request's metadata. More info: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md#metadata (see [below for nested schema](#nestedblock--metadata))
### Optional
- `spec` (Block List, Max: 1) (see [below for nested schema](#nestedblock--spec))
### Read-Only
- `token` (String, Sensitive) Token is the opaque bearer token.
<a id="nestedblock--metadata"></a>
### Nested Schema for `metadata`
Optional:
- `name` (String) Name of the token request, must be unique. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
- `namespace` (String) Namespace defines the space within which name of the token request must be unique.
<a id="nestedblock--spec"></a>
### Nested Schema for `spec`
Optional:
- `audiences` (List of String) Audiences are the intendend audiences of the token. A recipient of a token must identify themself with an identifier in the list of audiences of the token, and otherwise should reject the token. A token issued for multiple audiences may be used to authenticate against any of the audiences listed but implies a high degree of trust between the target audiences.
- `bound_object_ref` (Block List, Max: 1) BoundObjectRef is a reference to an object that the token will be bound to. The token will only be valid for as long as the bound object exists. NOTE: The API server's TokenReview endpoint will validate the BoundObjectRef, but other audiences may not. Keep ExpirationSeconds small if you want prompt revocation. (see [below for nested schema](#nestedblock--spec--bound_object_ref))
- `expiration_seconds` (Number) expiration_seconds is the requested duration of validity of the request. The token issuer may return a token with a different validity duration so a client needs to check the 'expiration' field in a response. The expiration can't be less than 10 minutes.
<a id="nestedblock--spec--bound_object_ref"></a>
### Nested Schema for `spec.bound_object_ref`
Optional:
- `api_version` (String) API version of the referent.
- `kind` (String) Kind of the referent. Valid kinds are 'Pod' and 'Secret'.
- `name` (String) Name of the referent.
- `uid` (String) UID of the referent.
## Example Usage
```terraform
resource "kubernetes_service_account_v1" "test" {
metadata {
name = "test"
}
}
ephemeral "kubernetes_token_request_v1" "test" {
metadata {
name = kubernetes_service_account_v1.test.metadata.0.name
}
spec {
audiences = [
"api",
"vault",
"factors"
]
}
}
```

53
go.mod
View file

@ -1,31 +1,33 @@
module github.com/hashicorp/terraform-provider-kubernetes
go 1.21
go 1.22.0
toolchain go1.22.5
require (
github.com/Masterminds/semver v1.5.0
github.com/getkin/kin-openapi v0.111.0
github.com/google/go-cmp v0.6.0
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.6.0
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hc-install v0.6.4
github.com/hashicorp/hcl/v2 v2.20.1
github.com/hashicorp/go-plugin v1.6.2
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hc-install v0.9.0
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/terraform-exec v0.21.0
github.com/hashicorp/terraform-json v0.22.1
github.com/hashicorp/terraform-json v0.23.0
github.com/hashicorp/terraform-plugin-docs v0.16.0
github.com/hashicorp/terraform-plugin-framework v1.7.0
github.com/hashicorp/terraform-plugin-go v0.23.0
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-go v0.25.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.16.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-mux v0.17.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0
github.com/hashicorp/terraform-plugin-testing v1.11.0
github.com/jinzhu/copier v0.3.5
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/hashstructure v1.1.0
github.com/robfig/cron v1.2.0
github.com/stretchr/testify v1.8.2
golang.org/x/mod v0.16.0
github.com/stretchr/testify v1.8.3
golang.org/x/mod v0.21.0
k8s.io/api v0.28.6
k8s.io/apiextensions-apiserver v0.28.6
k8s.io/apimachinery v0.28.6
@ -47,6 +49,7 @@ require (
github.com/cloudflare/circl v1.3.7 // indirect
github.com/emicklei/go-restful/v3 v3.10.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/mitchellh/cli v1.1.5 // indirect
@ -59,9 +62,9 @@ require (
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect
golang.org/x/sync v0.6.0 // indirect
golang.org/x/tools v0.16.1 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
)
require (
@ -123,18 +126,18 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/xlab/treeprint v1.2.0 // indirect
github.com/zclconf/go-cty v1.14.4
github.com/zclconf/go-cty v1.15.0
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.23.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/term v0.26.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.34.0 // indirect
google.golang.org/grpc v1.67.1
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

102
go.sum
View file

@ -158,37 +158,39 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
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=
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0=
github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA=
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/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE=
github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.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=
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/terraform-plugin-docs v0.16.0 h1:UmxFr3AScl6Wged84jndJIfFccGyBZn52KtMNsS12dI=
github.com/hashicorp/terraform-plugin-docs v0.16.0/go.mod h1:M3ZrlKBJAbPMtNOPwHicGi1c+hZUh7/g0ifT/z7TVfA=
github.com/hashicorp/terraform-plugin-framework v1.7.0 h1:wOULbVmfONnJo9iq7/q+iBOBJul5vRovaYJIu2cY/Pw=
github.com/hashicorp/terraform-plugin-framework v1.7.0/go.mod h1:jY9Id+3KbZ17OMpulgnWLSfwxNVYSoYBQFTgsx044CI=
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-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw=
github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU=
github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks=
github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw=
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-mux v0.16.0 h1:RCzXHGDYwUwwqfYYWJKBFaS3fQsWn/ZECEiW7p2023I=
github.com/hashicorp/terraform-plugin-mux v0.16.0/go.mod h1:PF79mAsPc8CpusXPfEVa4X8PtkB+ngWoiUClMrNZlYo=
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-mux v0.17.0 h1:/J3vv3Ps2ISkbLPiZOLspFcIZ0v5ycUXCEQScudGCCw=
github.com/hashicorp/terraform-plugin-mux v0.17.0/go.mod h1:yWuM9U1Jg8DryNfvCp+lH70WcYv6D8aooQxxxIzFDsE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw=
github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A=
github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U=
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=
@ -333,8 +335,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
@ -349,10 +351,10 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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.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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@ -362,8 +364,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U=
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
@ -373,8 +375,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
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.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -386,19 +388,19 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -417,22 +419,22 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -444,8 +446,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -457,13 +459,13 @@ google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJ
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -474,8 +476,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=

View file

@ -0,0 +1,30 @@
// Copyright (c) HashiCorp, Inc.
package authenticationv1_test
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
"github.com/hashicorp/terraform-provider-kubernetes/internal/framework/provider"
"github.com/hashicorp/terraform-provider-kubernetes/kubernetes"
sdkv2 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
// NOTE this is a shim back to the SDKv2 so we don't have to duplicate
// the client initialization code.
func sdkv2providerMeta() func() any {
p := kubernetes.Provider()
p.Configure(context.Background(), sdkv2.NewResourceConfigRaw(nil))
return p.Meta
}
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"kubernetes": providerserver.NewProtocol6WithError(provider.New("test", sdkv2providerMeta())),
"echo": echoprovider.NewProviderServer(),
}

View file

@ -0,0 +1,211 @@
// Copyright (c) HashiCorp, Inc.
package authenticationv1
import (
"context"
"time"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-kubernetes/kubernetes"
authv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
)
var (
_ ephemeral.EphemeralResource = (*TokenRequestEphemeralResource)(nil)
_ ephemeral.EphemeralResourceWithConfigure = (*TokenRequestEphemeralResource)(nil)
)
type TokenRequestEphemeralResource struct {
SDKv2Meta func() any
}
type TokenRequestMetadata struct {
Name types.String `tfsdk:"name"`
Namespace types.String `tfsdk:"namespace"`
}
type BoundObjectReference struct {
APIVersion types.String `tfsdk:"api_version"`
Kind types.String `tfsdk:"kind"`
Name types.String `tfsdk:"name"`
UID types.String `tfsdk:"uid"`
}
type TokenRequestSpec struct {
Audiences []types.String `tfsdk:"audiences"`
BoundObjecRef *BoundObjectReference `tfsdk:"bound_object_ref"`
ExpirationSeconds types.Int64 `tfsdk:"expiration_seconds"`
}
type TokenRequestModel struct {
Metadata TokenRequestMetadata `tfsdk:"metadata"`
Spec *TokenRequestSpec `tfsdk:"spec"`
Token types.String `tfsdk:"token"`
ExpirationTimestamp types.String `tfsdk:"expiration_timestamp"`
}
func NewTokenRequestEphemeralResource() ephemeral.EphemeralResource {
return &TokenRequestEphemeralResource{}
}
func (r *TokenRequestEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
if req.ProviderData == nil {
return
}
r.SDKv2Meta = req.ProviderData.(func() any)
}
func (r *TokenRequestEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_token_request_v1"
}
func (r *TokenRequestEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
objectMetaOpenAPI := metav1.ObjectMeta{}.SwaggerDoc()
tokenreqOpenAPISpec := authv1.TokenRequestSpec{}.SwaggerDoc()
tokenreqOpenAPIStatus := authv1.TokenRequestStatus{}.SwaggerDoc()
tokenreqOpenAPIBoundObjRef := authv1.BoundObjectReference{}.SwaggerDoc()
resp.Schema = schema.Schema{
Description: "TokenRequest requests a token for a given service account.",
Attributes: map[string]schema.Attribute{
"token": schema.StringAttribute{
Computed: true,
Optional: true,
Description: tokenreqOpenAPIStatus["token"],
},
"expiration_timestamp": schema.StringAttribute{
Computed: true,
Optional: true,
Description: tokenreqOpenAPIStatus["expirationTimestamp"],
},
},
Blocks: map[string]schema.Block{
"metadata": schema.SingleNestedBlock{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
Description: objectMetaOpenAPI["name"],
},
"namespace": schema.StringAttribute{
Required: true,
Description: objectMetaOpenAPI["namespace"],
},
},
},
"spec": schema.SingleNestedBlock{
Description: tokenreqOpenAPISpec[""],
Attributes: map[string]schema.Attribute{
"audiences": schema.ListAttribute{
Optional: true,
Computed: true,
ElementType: types.StringType,
Description: tokenreqOpenAPISpec["audiences"],
},
"expiration_seconds": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: tokenreqOpenAPISpec["expirationSeconds"],
},
},
Blocks: map[string]schema.Block{
"bound_object_ref": schema.SingleNestedBlock{
Attributes: map[string]schema.Attribute{
"api_version": schema.StringAttribute{
Optional: true,
Description: tokenreqOpenAPIBoundObjRef["apiVersion"],
},
"kind": schema.StringAttribute{
Optional: true,
Description: tokenreqOpenAPIBoundObjRef["kind"],
},
"name": schema.StringAttribute{
Optional: true,
Description: tokenreqOpenAPIBoundObjRef["name"],
},
"uid": schema.StringAttribute{
Optional: true,
Description: tokenreqOpenAPIBoundObjRef["uid"],
},
},
},
},
},
},
}
}
func expandStringSlice(s []types.String) []string {
ss := make([]string, len(s))
for i := 0; i < len(s); i++ {
ss[i] = s[i].ValueString()
}
return ss
}
func (r *TokenRequestEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data TokenRequestModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
name := data.Metadata.Name.ValueString()
namespace := data.Metadata.Namespace.ValueString()
if namespace == "" {
namespace = "default"
}
conn, err := r.SDKv2Meta().(kubernetes.KubeClientsets).MainClientset()
if err != nil {
resp.Diagnostics.AddError("error initializing kubernetes client", err.Error())
return
}
tokenRequest := authv1.TokenRequest{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}
if data.Spec != nil {
tokenRequest.Spec = authv1.TokenRequestSpec{
Audiences: expandStringSlice(data.Spec.Audiences),
ExpirationSeconds: data.Spec.ExpirationSeconds.ValueInt64Pointer(),
}
if data.Spec.BoundObjecRef != nil {
tokenRequest.Spec.BoundObjectRef = &authv1.BoundObjectReference{
Kind: data.Spec.BoundObjecRef.Kind.ValueString(),
APIVersion: data.Spec.BoundObjecRef.APIVersion.ValueString(),
Name: data.Spec.BoundObjecRef.Name.ValueString(),
UID: k8stypes.UID(data.Spec.BoundObjecRef.UID.ValueString()),
}
}
}
res, err := conn.CoreV1().ServiceAccounts(namespace).CreateToken(ctx, name, &tokenRequest, metav1.CreateOptions{})
if err != nil {
resp.Diagnostics.AddError("error creating token request", err.Error())
return
}
data.ExpirationTimestamp = types.StringValue(res.Status.ExpirationTimestamp.Format(time.RFC3339))
data.Token = types.StringValue(res.Status.Token)
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
}

View file

@ -0,0 +1,53 @@
// Copyright (c) HashiCorp, Inc.
package authenticationv1_test
import (
"fmt"
"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 TestAccEpehemeralTokenRequest_basic(t *testing.T) {
name := "default"
namespace := "default"
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testEphemeralTokenRequestV1Config(name, namespace),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(
"echo.test",
tfjsonpath.New("data").AtMapKey("token"),
knownvalue.NotNull(),
),
},
},
},
})
}
func testEphemeralTokenRequestV1Config(name, namespace string) string {
return fmt.Sprintf(`
ephemeral "kubernetes_token_request_v1" "test" {
metadata {
name = %q
namespace = %q
}
spec {
audiences = ["api", "vault"]
}
}
provider "echo" {
data = ephemeral.kubernetes_token_request_v1.test
}
resource "echo" "test" {}`, name, namespace)
}

View file

@ -0,0 +1,30 @@
// Copyright (c) HashiCorp, Inc.
package certificatesv1_test
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-testing/echoprovider"
"github.com/hashicorp/terraform-provider-kubernetes/internal/framework/provider"
"github.com/hashicorp/terraform-provider-kubernetes/kubernetes"
sdkv2 "github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
// NOTE this is a shim back to the SDKv2 so we don't have to duplicate
// the client initialization code.
func sdkv2providerMeta() func() any {
p := kubernetes.Provider()
p.Configure(context.Background(), sdkv2.NewResourceConfigRaw(nil))
return p.Meta
}
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"kubernetes": providerserver.NewProtocol6WithError(provider.New("test", sdkv2providerMeta())),
"echo": echoprovider.NewProviderServer(),
}

View file

@ -0,0 +1,235 @@
// Copyright (c) HashiCorp, Inc.
package certificatesv1
import (
"context"
"fmt"
"time"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-kubernetes/kubernetes"
certificatesv1 "k8s.io/api/certificates/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kwait "k8s.io/apimachinery/pkg/util/wait"
kretry "k8s.io/client-go/util/retry"
)
var (
_ ephemeral.EphemeralResource = (*CertificateSigningRequestEphemeralResource)(nil)
_ ephemeral.EphemeralResourceWithConfigure = (*CertificateSigningRequestEphemeralResource)(nil)
)
type CertificateSigningRequestEphemeralResource struct {
SDKv2Meta func() any
}
type CertificateSigningRequestMetadata struct {
Name types.String `tfsdk:"name"`
}
type CertificateSigningRequestSpec struct {
ExpirationSeconds types.Int32 `tfsdk:"expiration_seconds"`
Request types.String `tfsdk:"request"`
SignerName types.String `tfsdk:"signer_name"`
Usages []types.String `tfsdk:"usages"`
}
type CertificateSigningRequestModel struct {
Metadata CertificateSigningRequestMetadata `tfsdk:"metadata"`
Spec CertificateSigningRequestSpec `tfsdk:"spec"`
AutoApprove types.Bool `tfsdk:"auto_approve"`
Certificate types.String `tfsdk:"certificate"`
}
func NewCertificateSigningRequestEphemeralResource() ephemeral.EphemeralResource {
return &CertificateSigningRequestEphemeralResource{}
}
func (r *CertificateSigningRequestEphemeralResource) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) {
if req.ProviderData == nil {
return
}
r.SDKv2Meta = req.ProviderData.(func() any)
}
func (r *CertificateSigningRequestEphemeralResource) Metadata(ctx context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_certificate_signing_request_v1"
}
func (r *CertificateSigningRequestEphemeralResource) Schema(ctx context.Context, req ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) {
objectMetaOpenAPI := metav1.ObjectMeta{}.SwaggerDoc()
csrOpenAPI := certificatesv1.CertificateSigningRequest{}.SwaggerDoc()
csrOpenAPISpec := certificatesv1.CertificateSigningRequestSpec{}.SwaggerDoc()
csrOpenAPIStatus := certificatesv1.CertificateSigningRequestStatus{}.SwaggerDoc()
resp.Schema = schema.Schema{
Description: "TokenRequest requests a token for a given service account.",
Attributes: map[string]schema.Attribute{
"auto_approve": schema.BoolAttribute{
Description: "Automatically approve the Certificate Signing Request",
Optional: true,
},
"certificate": schema.StringAttribute{
Computed: true,
Optional: true,
Description: csrOpenAPIStatus["certificate"],
},
},
Blocks: map[string]schema.Block{
"metadata": schema.SingleNestedBlock{
Attributes: map[string]schema.Attribute{
"name": schema.StringAttribute{
Required: true,
Description: objectMetaOpenAPI["name"],
},
},
},
"spec": schema.SingleNestedBlock{
Description: csrOpenAPI[""],
Attributes: map[string]schema.Attribute{
"usages": schema.ListAttribute{
Description: csrOpenAPISpec["usages"],
Optional: true,
ElementType: types.StringType,
},
"expiration_seconds": schema.Int32Attribute{
Description: csrOpenAPISpec["expirationSeconds"],
Optional: true,
},
"request": schema.StringAttribute{
Description: csrOpenAPISpec["request"],
Required: true,
},
"signer_name": schema.StringAttribute{
Description: csrOpenAPISpec["signerName"],
Required: true,
},
},
},
},
}
}
func expandUsages(s []types.String) []certificatesv1.KeyUsage {
ss := make([]certificatesv1.KeyUsage, len(s))
for i := 0; i < len(s); i++ {
ss[i] = certificatesv1.KeyUsage(s[i].ValueString())
}
return ss
}
const (
TerraformAutoApproveReason = "TerraformAutoApprove"
TerraformAutoApproveMessage = "This CertificateSigningRequest was auto-approved by Terraform"
)
func (r *CertificateSigningRequestEphemeralResource) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
var data CertificateSigningRequestModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
name := data.Metadata.Name.ValueString()
conn, err := r.SDKv2Meta().(kubernetes.KubeClientsets).MainClientset()
if err != nil {
resp.Diagnostics.AddError("error setting up kubernetes client", err.Error())
return
}
csr := certificatesv1.CertificateSigningRequest{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: certificatesv1.CertificateSigningRequestSpec{
ExpirationSeconds: data.Spec.ExpirationSeconds.ValueInt32Pointer(),
Request: []byte(data.Spec.Request.ValueString()),
SignerName: data.Spec.SignerName.ValueString(),
Usages: expandUsages(data.Spec.Usages),
},
}
newcsr, err := conn.CertificatesV1().CertificateSigningRequests().Create(
ctx, &csr, metav1.CreateOptions{})
if err != nil {
resp.Diagnostics.AddError("error creating CSR", err.Error())
return
}
defer conn.CertificatesV1().CertificateSigningRequests().Delete(
ctx, csr.GetName(), metav1.DeleteOptions{})
// auto approve the certificate
if data.AutoApprove.IsNull() || data.AutoApprove.ValueBool() {
err := kretry.RetryOnConflict(kretry.DefaultRetry, func() error {
pendingCSR, err := conn.CertificatesV1().CertificateSigningRequests().Get(
ctx, newcsr.GetName(), metav1.GetOptions{})
if err != nil {
return err
}
approval := certificatesv1.CertificateSigningRequestCondition{
Status: corev1.ConditionTrue,
Type: certificatesv1.CertificateApproved,
Reason: TerraformAutoApproveReason,
Message: TerraformAutoApproveMessage,
}
pendingCSR.Status.Certificate = []byte{}
pendingCSR.Status.Conditions = append(pendingCSR.Status.Conditions, approval)
_, err = conn.CertificatesV1().CertificateSigningRequests().UpdateApproval(
ctx, newcsr.GetName(), pendingCSR, metav1.UpdateOptions{})
return err
})
if err != nil {
resp.Diagnostics.AddError("CSR auto approval failed", err.Error())
return
}
}
// wait for the certificate to be issued
waitingErr := fmt.Errorf("timed out waiting for certificate")
waitForIssue := kwait.Backoff{
Steps: 10,
Duration: 5 * time.Second,
Factor: 1.5,
Jitter: 0.1,
}
err = kretry.OnError(waitForIssue, func(e error) bool { return e == waitingErr }, func() error {
out, err := conn.CertificatesV1().CertificateSigningRequests().Get(ctx,
newcsr.GetName(), metav1.GetOptions{})
if err != nil {
return err
}
for _, condition := range out.Status.Conditions {
if condition.Type == certificatesv1.CertificateApproved &&
len(out.Status.Certificate) > 0 {
return nil
}
}
return waitingErr
})
if err != nil {
resp.Diagnostics.AddError("error waiting for certificate to be issued", err.Error())
return
}
issued, err := conn.CertificatesV1().CertificateSigningRequests().Get(ctx, newcsr.GetName(), metav1.GetOptions{})
if err != nil {
resp.Diagnostics.AddError("error getting CSR", err.Error())
return
}
data.Certificate = types.StringValue(string(issued.Status.Certificate))
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
}

View file

@ -0,0 +1,62 @@
// Copyright (c) HashiCorp, Inc.
package certificatesv1_test
import (
"fmt"
"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 TestAccEpehemeralCertificateSigningRequest_basic(t *testing.T) {
name := "test"
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testEphemeralCertificateSigningRequestRequestV1Config(name),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(
"echo.test",
tfjsonpath.New("data").AtMapKey("certificate"),
knownvalue.NotNull(),
),
},
},
},
})
}
func testEphemeralCertificateSigningRequestRequestV1Config(name string) string {
return fmt.Sprintf(`
ephemeral "kubernetes_certificate_signing_request_v1" "test" {
metadata {
name = %q
}
spec {
request = <<EOT
-----BEGIN CERTIFICATE REQUEST-----
MIHSMIGBAgEAMCoxGDAWBgNVBAoTD2V4YW1wbGUgY2x1c3RlcjEOMAwGA1UEAxMF
YWRtaW4wTjAQBgcqhkjOPQIBBgUrgQQAIQM6AASSG8S2+hQvfMq5ucngPCzK0m0C
ImigHcF787djpF2QDbz3oQ3QsM/I7ftdjB/HHlG2a5YpqjzT0KAAMAoGCCqGSM49
BAMCA0AAMD0CHQDErNLjX86BVfOsYh/A4zmjmGknZpc2u6/coTHqAhxcR41hEU1I
DpNPvh30e0Js8/DYn2YUfu/pQU19
-----END CERTIFICATE REQUEST-----
EOT
usages = ["client auth"]
signer_name = "kubernetes.io/kube-apiserver-client"
}
auto_approve = true
}
provider "echo" {
data = ephemeral.kubernetes_certificate_signing_request_v1.test
}
resource "echo" "test" {}`, name)
}

View file

@ -10,5 +10,5 @@ import (
)
var testAccProtoV5ProviderFactories = map[string]func() (tfprotov5.ProviderServer, error){
"kubernetes": providerserver.NewProtocol5WithError(provider.New("test")),
"kubernetes": providerserver.NewProtocol5WithError(provider.New("test", nil)),
}

View file

@ -7,18 +7,22 @@ import (
"context"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/function"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-kubernetes/internal/framework/provider/authenticationv1"
"github.com/hashicorp/terraform-provider-kubernetes/internal/framework/provider/certificatesv1"
pfunctions "github.com/hashicorp/terraform-provider-kubernetes/internal/framework/provider/functions"
)
// Ensure KubernetesProvider satisfies various provider interfaces.
var (
_ provider.Provider = &KubernetesProvider{}
_ provider.ProviderWithFunctions = &KubernetesProvider{}
_ provider.Provider = &KubernetesProvider{}
_ provider.ProviderWithFunctions = &KubernetesProvider{}
_ provider.ProviderWithEphemeralResources = &KubernetesProvider{}
)
// KubernetesProvider defines the provider implementation.
@ -27,6 +31,9 @@ type KubernetesProvider struct {
// provider is built and ran locally, and "test" when running acceptance
// testing.
version string
// SDKv2Meta is a function which returns provider meta struct from the SDKv2 code
SDKv2Meta func() any
}
// KubernetesProviderModel describes the provider data model.
@ -192,6 +199,13 @@ func (p *KubernetesProvider) DataSources(ctx context.Context) []func() datasourc
return []func() datasource.DataSource{}
}
func (p *KubernetesProvider) EphemeralResources(ctx context.Context) []func() ephemeral.EphemeralResource {
return []func() ephemeral.EphemeralResource{
authenticationv1.NewTokenRequestEphemeralResource,
certificatesv1.NewCertificateSigningRequestEphemeralResource,
}
}
func (p *KubernetesProvider) Functions(ctx context.Context) []func() function.Function {
return []func() function.Function{
pfunctions.NewManifestDecodeFunction,
@ -200,8 +214,9 @@ func (p *KubernetesProvider) Functions(ctx context.Context) []func() function.Fu
}
}
func New(version string) provider.Provider {
func New(version string, sdkv2Meta func() any) provider.Provider {
return &KubernetesProvider{
version: version,
version: version,
SDKv2Meta: sdkv2Meta,
}
}

View file

@ -4,157 +4,19 @@
package provider
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/mitchellh/go-homedir"
apimachineryschema "k8s.io/apimachinery/pkg/runtime/schema"
_ "k8s.io/client-go/plugin/pkg/client/auth"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
func (p *KubernetesProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
var data KubernetesProviderModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// NOTE for migration purposes we are re-using the client configurations which are initialized at configure time
// by the SDKv2 codebase. Once all SDKv2 resources have been removed the client initialization code should be
// migrated here.
_, err := newKubernetesClientConfig(ctx, data)
if err != nil {
resp.Diagnostics.Append(diag.NewErrorDiagnostic("failed to initilize Kubernetes client configuration", err.Error()))
}
}
func newKubernetesClientConfig(ctx context.Context, data KubernetesProviderModel) (*restclient.Config, error) {
overrides := &clientcmd.ConfigOverrides{}
loader := &clientcmd.ClientConfigLoadingRules{}
configPaths := []string{}
if v := data.ConfigPath.ValueString(); v != "" {
configPaths = []string{v}
} else if len(data.ConfigPaths) > 0 {
for _, p := range data.ConfigPaths {
configPaths = append(configPaths, p.ValueString())
}
} else if v := os.Getenv("KUBE_CONFIG_PATHS"); v != "" {
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
}
tflog.Debug(ctx, "Using kubeconfig file", map[string]interface{}{"path": path})
expandedPaths = append(expandedPaths, path)
}
if len(expandedPaths) == 1 {
loader.ExplicitPath = expandedPaths[0]
} else {
loader.Precedence = expandedPaths
}
ctxSuffix := "; default context"
kubectx := data.ConfigContext.ValueString()
authInfo := data.ConfigContextAuthInfo.ValueString()
cluster := data.ConfigContextCluster.ValueString()
if kubectx != "" || authInfo != "" || cluster != "" {
ctxSuffix = "; overridden context"
if kubectx != "" {
overrides.CurrentContext = kubectx
ctxSuffix += fmt.Sprintf("; config ctx: %s", overrides.CurrentContext)
tflog.Debug(ctx, "Using custom current context", map[string]interface{}{"context": overrides.CurrentContext})
}
overrides.Context = clientcmdapi.Context{}
if authInfo != "" {
overrides.Context.AuthInfo = authInfo
ctxSuffix += fmt.Sprintf("; auth_info: %s", overrides.Context.AuthInfo)
}
if cluster != "" {
overrides.Context.Cluster = cluster
ctxSuffix += fmt.Sprintf("; cluster: %s", overrides.Context.Cluster)
}
tflog.Debug(ctx, "Using overridden context", map[string]interface{}{"context": overrides.Context})
}
}
// Overriding with static configuration
overrides.ClusterInfo.InsecureSkipTLSVerify = data.Insecure.ValueBool()
overrides.ClusterInfo.TLSServerName = data.TLSServerName.ValueString()
overrides.ClusterInfo.CertificateAuthorityData = bytes.NewBufferString(data.ClusterCACertificate.ValueString()).Bytes()
overrides.AuthInfo.ClientCertificateData = bytes.NewBufferString(data.ClientCertificate.ValueString()).Bytes()
if v := data.Host.ValueString(); v != "" {
// 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 := restclient.DefaultServerURL(v, "", apimachineryschema.GroupVersion{}, defaultTLS)
if err != nil {
return nil, fmt.Errorf("failed to parse host: %s", err)
}
overrides.ClusterInfo.Server = host.String()
}
overrides.AuthInfo.Username = data.Username.ValueString()
overrides.AuthInfo.Password = data.Password.ValueString()
overrides.AuthInfo.ClientKeyData = bytes.NewBufferString(data.ClientKey.ValueString()).Bytes()
overrides.AuthInfo.Token = data.Token.ValueString()
overrides.ClusterDefaults.ProxyURL = data.ProxyURL.ValueString()
if len(data.Exec) > 0 {
execData := data.Exec[0]
exec := &clientcmdapi.ExecConfig{}
exec.InteractiveMode = clientcmdapi.IfAvailableExecInteractiveMode
exec.APIVersion = execData.APIVersion.ValueString()
exec.Command = execData.Command.ValueString()
exec.Args = expandStringSlice(execData.Args)
for kk, vv := range execData.Env {
exec.Env = append(exec.Env, clientcmdapi.ExecEnvVar{Name: kk, Value: vv.ValueString()})
}
overrides.AuthInfo.Exec = exec
}
cc := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, overrides)
cfg, err := cc.ClientConfig()
if err != nil {
tflog.Warn(ctx, "Invalid provider configuration was supplied. Provider operations likely to fail", map[string]interface{}{
"error": err.Error(),
})
return nil, nil
}
return cfg, nil
}
func expandStringSlice(s []types.String) []string {
v := []string{}
for _, vv := range s {
v = append(v, vv.ValueString())
}
return v
resp.ResourceData = p.SDKv2Meta
resp.DataSourceData = p.SDKv2Meta
resp.EphemeralResourceData = p.SDKv2Meta
}

View file

@ -1,25 +0,0 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
// testAccProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach.
var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"kubernetes": providerserver.NewProtocol6WithError(New("test")),
}
func testAccPreCheck(t *testing.T) {
// You can add code here to run prior to any test case execution, for example assertions
// about the appropriate environment variables being set are common to see in a pre-check
// function.
}

View file

@ -15,10 +15,12 @@ import (
)
func MuxServer(ctx context.Context, v string) (tfprotov5.ProviderServer, error) {
kubernetesProvider := kubernetes.Provider()
providers := []func() tfprotov5.ProviderServer{
kubernetes.Provider().GRPCProvider,
kubernetesProvider.GRPCProvider,
manifest.Provider(),
providerserver.NewProtocol5(framework.New(v)),
providerserver.NewProtocol5(framework.New(v, kubernetesProvider.Meta)),
}
return tf5muxserver.NewMuxServer(ctx, providers...)