mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-10-01 01:36:09 -04:00
terraform-provider: support importing Constellation clusters (#2702)
* terraform-provider: support importing Constellation clusters * bazel: shfmt exclusion for import script * ci: fix godot check * bazel: shellcheck exclusion for import script * Update dev-docs/workflows/terraform-provider.md Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com> * ci: fix Terraform lock exclude directories --------- Co-authored-by: Adrian Stobbe <stobbe.adrian@gmail.com>
This commit is contained in:
parent
d08e75bf9c
commit
367136add2
@ -28,6 +28,7 @@ excludeDirs=(
|
||||
"internal/constellation/helm/charts/cilium"
|
||||
"build"
|
||||
"docs/node_modules"
|
||||
"terraform-provider-constellation/examples"
|
||||
)
|
||||
|
||||
excludeFiles=(
|
||||
|
@ -26,6 +26,7 @@ excludeDirs=(
|
||||
"internal/constellation/helm/charts/cilium"
|
||||
"build"
|
||||
"docs/node_modules"
|
||||
"terraform-provider-constellation/examples"
|
||||
)
|
||||
|
||||
echo "The following scripts are excluded and won't be formatted with shfmt:"
|
||||
|
@ -46,9 +46,7 @@ excludeDirs=(
|
||||
excludeLockDirs=(
|
||||
"build"
|
||||
"terraform-provider-constellation"
|
||||
"terraform/aws-constellation"
|
||||
"terraform/azure-constellation"
|
||||
"terraform/gcp-constellation"
|
||||
"terraform/legacy-module"
|
||||
)
|
||||
|
||||
excludeCheckDirs=(
|
||||
|
@ -43,7 +43,7 @@ TF_CLI_CONFIG_FILE=config.tfrc terraform apply
|
||||
Terraform acceptance tests can be run hermetically through Bazel (recommended):
|
||||
|
||||
```bash
|
||||
bazel test --test_tag_filters=integration //terraform-provider-constellation/internal/provider:provider_acc_test
|
||||
bazel test --config=integration-only //terraform-provider-constellation/internal/provider:provider_test
|
||||
```
|
||||
|
||||
The tests can also be run through Go, but the `TF_ACC` environment variable needs to be set to `1`, and the host's Terraform binary is used, which may produce inaccurate test results.
|
||||
|
@ -268,6 +268,21 @@ MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAELcPl4Ik+qZuH4K049wksoXK/Os3Z
|
||||
b92PDCpM7FZAINQF88s1TZS/HmRXYk62UJ4eqPduvUnJmXhNikhLbMi6fw==
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
||||
|
||||
//
|
||||
// Terraform Provider.
|
||||
//
|
||||
|
||||
// ConstellationClusterURIScheme is the scheme used in Terraform Constellation cluster import URIs.
|
||||
ConstellationClusterURIScheme = "constellation-cluster"
|
||||
// KubeConfigURIKey is the key used for the KubeConfig in Terraform Constellation cluster import URIs.
|
||||
KubeConfigURIKey = "kubeConfig"
|
||||
// ClusterEndpointURIKey is the key used for the cluster endpoint in Terraform Constellation cluster import URIs.
|
||||
ClusterEndpointURIKey = "clusterEndpoint"
|
||||
// MasterSecretURIKey is the key used for the master secret in Terraform Constellation cluster import URIs.
|
||||
MasterSecretURIKey = "masterSecret"
|
||||
// MasterSecretSaltURIKey is the key used for the master secret salt in Terraform Constellation cluster import URIs.
|
||||
MasterSecretSaltURIKey = "masterSecretSalt"
|
||||
)
|
||||
|
||||
// BinaryVersion returns the version of this Binary.
|
||||
|
@ -155,3 +155,11 @@ Required:
|
||||
|
||||
- `project_id` (String) ID of the GCP project the cluster resides in.
|
||||
- `service_account_key` (String) Base64-encoded private key JSON object of the service account used within the cluster.
|
||||
|
||||
## Import
|
||||
|
||||
Import is supported using the following syntax:
|
||||
|
||||
```shell
|
||||
terraform import constellation_cluster.constellation_cluster constellation-cluster://?kubeConfig=<base64-encoded-kubeconfig>&clusterEndpoint=<cluster-endpoint>&masterSecret=<hex-encoded-mastersecret>&masterSecretSalt=<hex-encoded-mastersecret-salt>
|
||||
```
|
||||
|
@ -0,0 +1 @@
|
||||
terraform import constellation_cluster.constellation_cluster constellation-cluster://?kubeConfig=<base64-encoded-kubeconfig>&clusterEndpoint=<cluster-endpoint>&masterSecret=<hex-encoded-mastersecret>&masterSecretSalt=<hex-encoded-mastersecret-salt>
|
@ -23,6 +23,7 @@ go_library(
|
||||
"//internal/cloud/azureshared",
|
||||
"//internal/cloud/cloudprovider",
|
||||
"//internal/config",
|
||||
"//internal/constants",
|
||||
"//internal/constellation",
|
||||
"//internal/constellation/helm",
|
||||
"//internal/constellation/state",
|
||||
@ -53,33 +54,14 @@ go_test(
|
||||
name = "provider_test",
|
||||
srcs = [
|
||||
"attestation_data_source_test.go",
|
||||
"cluster_resource_test.go",
|
||||
"convert_test.go",
|
||||
"image_data_source_test.go",
|
||||
"provider_test.go",
|
||||
],
|
||||
embed = [":provider"],
|
||||
deps = [
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/config",
|
||||
"@com_github_hashicorp_terraform_plugin_framework//providerserver",
|
||||
"@com_github_hashicorp_terraform_plugin_go//tfprotov6",
|
||||
"@com_github_hashicorp_terraform_plugin_testing//helper/resource",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@io_bazel_rules_go//go/runfiles:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "provider_acc_test",
|
||||
srcs = [
|
||||
"image_data_source_test.go",
|
||||
"provider_test.go",
|
||||
],
|
||||
# keep
|
||||
count = 1,
|
||||
# keep
|
||||
data = [
|
||||
"//bazel/ci:com_github_hashicorp_terraform",
|
||||
],
|
||||
@ -98,9 +80,16 @@ go_test(
|
||||
# keep
|
||||
x_defs = {"runsUnder": "bazel"},
|
||||
deps = [
|
||||
"//internal/attestation/idkeydigest",
|
||||
"//internal/attestation/measurements",
|
||||
"//internal/attestation/variant",
|
||||
"//internal/config",
|
||||
"@com_github_hashicorp_terraform_plugin_framework//providerserver",
|
||||
"@com_github_hashicorp_terraform_plugin_go//tfprotov6",
|
||||
"@com_github_hashicorp_terraform_plugin_testing//helper/resource",
|
||||
"@com_github_hashicorp_terraform_plugin_testing//terraform",
|
||||
"@com_github_stretchr_testify//assert",
|
||||
"@com_github_stretchr_testify//require",
|
||||
"@io_bazel_rules_go//go/runfiles:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/edgelesssys/constellation/v2/internal/atls"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/azureshared"
|
||||
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/v2/internal/config"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constants"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constellation"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constellation/helm"
|
||||
"github.com/edgelesssys/constellation/v2/internal/constellation/state"
|
||||
@ -400,10 +402,80 @@ func (r *ClusterResource) Delete(ctx context.Context, req resource.DeleteRequest
|
||||
}
|
||||
|
||||
// ImportState imports to the resource.
|
||||
func (r *ClusterResource) ImportState(_ context.Context, _ resource.ImportStateRequest, _ *resource.ImportStateResponse) {
|
||||
// TODO: Implement
|
||||
func (r *ClusterResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
expectedSchemaMsg := fmt.Sprintf(
|
||||
"Expected URI of schema '%s://?%s=<...>&%s=<...>&%s=<...>&%s=<...>'",
|
||||
constants.ConstellationClusterURIScheme, constants.KubeConfigURIKey, constants.ClusterEndpointURIKey,
|
||||
constants.MasterSecretURIKey, constants.MasterSecretSaltURIKey)
|
||||
|
||||
// Take Kubeconfig, Cluster Endpoint and Master Secret and save to state
|
||||
uri, err := url.Parse(req.ID)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: %s.\n%s", err, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
if uri.Scheme != constants.ConstellationClusterURIScheme {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Invalid scheme '%s'.\n%s", uri.Scheme, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
query := uri.Query()
|
||||
kubeConfig := query.Get(constants.KubeConfigURIKey)
|
||||
clusterEndpoint := query.Get(constants.ClusterEndpointURIKey)
|
||||
masterSecret := query.Get(constants.MasterSecretURIKey)
|
||||
masterSecretSalt := query.Get(constants.MasterSecretSaltURIKey)
|
||||
|
||||
if kubeConfig == "" {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Missing query parameter '%s'.\n%s", constants.KubeConfigURIKey, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
if clusterEndpoint == "" {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Missing query parameter '%s'.\n%s", constants.ClusterEndpointURIKey, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
if masterSecret == "" {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Missing query parameter '%s'.\n%s", constants.MasterSecretURIKey, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
if masterSecretSalt == "" {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Missing query parameter '%s'.\n%s", constants.MasterSecretSaltURIKey, expectedSchemaMsg))
|
||||
return
|
||||
}
|
||||
|
||||
decodedKubeConfig, err := base64.StdEncoding.DecodeString(kubeConfig)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Decoding base64-encoded kubeconfig: %s.", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Sanity checks for master secret and master secret salt
|
||||
if _, err := hex.DecodeString(masterSecret); err != nil {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Decoding hex-encoded master secret: %s.", err))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := hex.DecodeString(masterSecretSalt); err != nil {
|
||||
resp.Diagnostics.AddError("Parsing cluster URI",
|
||||
fmt.Sprintf("Parsing cluster URI: Decoding hex-encoded master secret salt: %s.", err))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("kubeconfig"), string(decodedKubeConfig))...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("out_of_cluster_endpoint"), clusterEndpoint)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("master_secret"), masterSecret)...)
|
||||
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("master_secret_salt"), masterSecretSalt)...)
|
||||
}
|
||||
|
||||
// apply applies changes to a cluster. It can be used for both creating and updating a cluster.
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright (c) Edgeless Systems GmbH
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
|
||||
"github.com/hashicorp/terraform-plugin-testing/terraform"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAccClusteResourceImports(t *testing.T) {
|
||||
// Set the path to the Terraform binary for acceptance testing when running under Bazel.
|
||||
bazelPreCheck := func() { bazelSetTerraformBinaryPath(t) }
|
||||
|
||||
testCases := map[string]resource.TestCase{
|
||||
"import success": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
resource "constellation_cluster" "test" {}
|
||||
`,
|
||||
ResourceName: "constellation_cluster.test",
|
||||
ImportState: true,
|
||||
ImportStateId: "constellation-cluster://?kubeConfig=YWJjZGU=&" + // valid base64 of "abcde"
|
||||
"clusterEndpoint=b&" +
|
||||
"masterSecret=de&" +
|
||||
"masterSecretSalt=ad",
|
||||
ImportStateCheck: func(states []*terraform.InstanceState) error {
|
||||
state := states[0]
|
||||
assert := assert.New(t)
|
||||
assert.Equal("abcde", state.Attributes["kubeconfig"])
|
||||
assert.Equal("b", state.Attributes["out_of_cluster_endpoint"])
|
||||
assert.Equal("de", state.Attributes["master_secret"])
|
||||
assert.Equal("ad", state.Attributes["master_secret_salt"])
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"kubeconfig not base64": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
resource "constellation_cluster" "test" {}
|
||||
`,
|
||||
ResourceName: "constellation_cluster.test",
|
||||
ImportState: true,
|
||||
ImportStateId: "constellation-cluster://?kubeConfig=a&" +
|
||||
"clusterEndpoint=b&" +
|
||||
"masterSecret=de&" +
|
||||
"masterSecretSalt=ad",
|
||||
ExpectError: regexp.MustCompile(".*illegal base64 data.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"mastersecret not hex": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
resource "constellation_cluster" "test" {}
|
||||
`,
|
||||
ResourceName: "constellation_cluster.test",
|
||||
ImportState: true,
|
||||
ImportStateId: "constellation-cluster://?kubeConfig=test&" +
|
||||
"clusterEndpoint=b&" +
|
||||
"masterSecret=xx&" +
|
||||
"masterSecretSalt=ad",
|
||||
ExpectError: regexp.MustCompile(".*Decoding hex-encoded master secret.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
"parameter missing": {
|
||||
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
|
||||
PreCheck: bazelPreCheck,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testingConfig + `
|
||||
resource "constellation_cluster" "test" {}
|
||||
`,
|
||||
ResourceName: "constellation_cluster.test",
|
||||
ImportState: true,
|
||||
ImportStateId: "constellation-cluster://?kubeConfig=test&" +
|
||||
"clusterEndpoint=b&" +
|
||||
"masterSecret=xx&",
|
||||
ExpectError: regexp.MustCompile(".*Missing query parameter.*"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
resource.Test(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user