From b0a4fb976a9190ffe706814d225f7eda8a6dcffe Mon Sep 17 00:00:00 2001 From: John Houston Date: Mon, 25 Nov 2024 12:54:25 -0500 Subject: [PATCH] Add ephemeral resources: `kubernetes_token_request_v1`, `kubernetes_certificate_signing_request_v1` (#2628) --- .changelog/2628.txt | 5 + .github/workflows/acceptance_tests_aks.yaml | 1 + .github/workflows/acceptance_tests_eks.yaml | 1 + .github/workflows/acceptance_tests_gke.yaml | 1 + .github/workflows/acceptance_tests_kind.yaml | 7 +- GNUmakefile | 8 +- ...bernetes_certificate_signing_request_v1.md | 141 +++++++++++ .../kubernetes_token_request_v1.md | 75 ++++++ go.mod | 53 ++-- go.sum | 102 ++++---- .../authenticationv1/authenticationv1_test.go | 30 +++ .../ephemeral_token_request_v1.go | 211 ++++++++++++++++ .../ephemeral_token_request_v1_test.go | 53 ++++ .../certificatesv1/certificatesv1_test.go | 30 +++ ...phemeral_certificate_signing_request_v1.go | 235 ++++++++++++++++++ ...ral_certificate_signing_request_v1_test.go | 62 +++++ .../provider/functions/functions_test.go | 2 +- internal/framework/provider/provider.go | 23 +- .../framework/provider/provider_configure.go | 150 +---------- internal/framework/provider/provider_test.go | 25 -- internal/mux/mux.go | 6 +- 21 files changed, 965 insertions(+), 256 deletions(-) create mode 100644 .changelog/2628.txt create mode 100644 docs/ephemeral-resources/kubernetes_certificate_signing_request_v1.md create mode 100644 docs/ephemeral-resources/kubernetes_token_request_v1.md create mode 100644 internal/framework/provider/authenticationv1/authenticationv1_test.go create mode 100644 internal/framework/provider/authenticationv1/ephemeral_token_request_v1.go create mode 100644 internal/framework/provider/authenticationv1/ephemeral_token_request_v1_test.go create mode 100644 internal/framework/provider/certificatesv1/certificatesv1_test.go create mode 100644 internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1.go create mode 100644 internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1_test.go delete mode 100644 internal/framework/provider/provider_test.go diff --git a/.changelog/2628.txt b/.changelog/2628.txt new file mode 100644 index 0000000000..d2f4ff01bd --- /dev/null +++ b/.changelog/2628.txt @@ -0,0 +1,5 @@ +```release-note:enhancement +FEATURES: + * New ephemeral resource: `kubernetes_certificate_signing_request_v1` + * New ephemeral resource: `kubernetes_token_request_v1` +``` diff --git a/.github/workflows/acceptance_tests_aks.yaml b/.github/workflows/acceptance_tests_aks.yaml index 2408f6fa14..298fbd196a 100644 --- a/.github/workflows/acceptance_tests_aks.yaml +++ b/.github/workflows/acceptance_tests_aks.yaml @@ -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 diff --git a/.github/workflows/acceptance_tests_eks.yaml b/.github/workflows/acceptance_tests_eks.yaml index 7c9c9dd0e9..e858641aae 100644 --- a/.github/workflows/acceptance_tests_eks.yaml +++ b/.github/workflows/acceptance_tests_eks.yaml @@ -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 diff --git a/.github/workflows/acceptance_tests_gke.yaml b/.github/workflows/acceptance_tests_gke.yaml index 53dcce3fe6..ad3c56d1fd 100644 --- a/.github/workflows/acceptance_tests_gke.yaml +++ b/.github/workflows/acceptance_tests_gke.yaml @@ -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 diff --git a/.github/workflows/acceptance_tests_kind.yaml b/.github/workflows/acceptance_tests_kind.yaml index d585fc5c7e..3bd2cce9ff 100644 --- a/.github/workflows/acceptance_tests_kind.yaml +++ b/.github/workflows/acceptance_tests_kind.yaml @@ -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 diff --git a/GNUmakefile b/GNUmakefile index ee22bc5da4..6bc2de7360 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -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 diff --git a/docs/ephemeral-resources/kubernetes_certificate_signing_request_v1.md b/docs/ephemeral-resources/kubernetes_certificate_signing_request_v1.md new file mode 100644 index 0000000000..b135315bf9 --- /dev/null +++ b/docs/ephemeral-resources/kubernetes_certificate_signing_request_v1.md @@ -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. + + +### 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 + + +### 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 = < +### 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. + + +### 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. + + +### 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" + ] + } +} +``` + diff --git a/go.mod b/go.mod index 159d9b1f13..08768ded06 100644 --- a/go.mod +++ b/go.mod @@ -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 diff --git a/go.sum b/go.sum index 4c1e0e14ef..047e970538 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/framework/provider/authenticationv1/authenticationv1_test.go b/internal/framework/provider/authenticationv1/authenticationv1_test.go new file mode 100644 index 0000000000..6b84cd9bc4 --- /dev/null +++ b/internal/framework/provider/authenticationv1/authenticationv1_test.go @@ -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(), +} diff --git a/internal/framework/provider/authenticationv1/ephemeral_token_request_v1.go b/internal/framework/provider/authenticationv1/ephemeral_token_request_v1.go new file mode 100644 index 0000000000..2a5ad4e29e --- /dev/null +++ b/internal/framework/provider/authenticationv1/ephemeral_token_request_v1.go @@ -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 + } +} diff --git a/internal/framework/provider/authenticationv1/ephemeral_token_request_v1_test.go b/internal/framework/provider/authenticationv1/ephemeral_token_request_v1_test.go new file mode 100644 index 0000000000..07a5595f78 --- /dev/null +++ b/internal/framework/provider/authenticationv1/ephemeral_token_request_v1_test.go @@ -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) +} diff --git a/internal/framework/provider/certificatesv1/certificatesv1_test.go b/internal/framework/provider/certificatesv1/certificatesv1_test.go new file mode 100644 index 0000000000..47617edc2c --- /dev/null +++ b/internal/framework/provider/certificatesv1/certificatesv1_test.go @@ -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(), +} diff --git a/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1.go b/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1.go new file mode 100644 index 0000000000..2239501ed2 --- /dev/null +++ b/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1.go @@ -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 + } +} diff --git a/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1_test.go b/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1_test.go new file mode 100644 index 0000000000..7fcebf7f1e --- /dev/null +++ b/internal/framework/provider/certificatesv1/ephemeral_certificate_signing_request_v1_test.go @@ -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 = < 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 -} + // 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. -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 } diff --git a/internal/framework/provider/provider_test.go b/internal/framework/provider/provider_test.go deleted file mode 100644 index 72b215bdd8..0000000000 --- a/internal/framework/provider/provider_test.go +++ /dev/null @@ -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. -} diff --git a/internal/mux/mux.go b/internal/mux/mux.go index c4c279e1ff..96f7eb54cb 100644 --- a/internal/mux/mux.go +++ b/internal/mux/mux.go @@ -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...)