From eab9aca26fca0f48244be4991235cb6568c8197e Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Tue, 3 Sep 2024 16:26:08 +0200 Subject: [PATCH] terraform-provider-constellation: make kubeconfig output fine-grained (#3334) --- .../docs/resources/cluster.md | 6 +- .../internal/provider/BUILD.bazel | 1 + .../internal/provider/cluster_resource.go | 90 +++++++++++++++++-- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/terraform-provider-constellation/docs/resources/cluster.md b/terraform-provider-constellation/docs/resources/cluster.md index e4a4f963a5..542200750b 100644 --- a/terraform-provider-constellation/docs/resources/cluster.md +++ b/terraform-provider-constellation/docs/resources/cluster.md @@ -90,8 +90,12 @@ See the [full list of CSPs](https://docs.edgeless.systems/constellation/overview ### Read-Only +- `client_certificate` (String) The client certificate of the cluster. +- `client_key` (String, Sensitive) The client key of the cluster. +- `cluster_ca_certificate` (String) The cluster CA certificate of the cluster. - `cluster_id` (String) The cluster ID of the cluster. -- `kubeconfig` (String, Sensitive) The kubeconfig of the cluster. +- `host` (String) The host of the cluster. +- `kubeconfig` (String, Sensitive) The kubeconfig (file) of the cluster. - `owner_id` (String) The owner ID of the cluster. diff --git a/terraform-provider-constellation/internal/provider/BUILD.bazel b/terraform-provider-constellation/internal/provider/BUILD.bazel index 54400f07f3..8f6f573d13 100644 --- a/terraform-provider-constellation/internal/provider/BUILD.bazel +++ b/terraform-provider-constellation/internal/provider/BUILD.bazel @@ -58,6 +58,7 @@ go_library( "@com_github_hashicorp_terraform_plugin_framework_validators//stringvalidator", "@com_github_hashicorp_terraform_plugin_log//tflog", "@com_github_spf13_afero//:afero", + "@io_k8s_client_go//tools/clientcmd", ], ) diff --git a/terraform-provider-constellation/internal/provider/cluster_resource.go b/terraform-provider-constellation/internal/provider/cluster_resource.go index 096621af67..01aa6615be 100644 --- a/terraform-provider-constellation/internal/provider/cluster_resource.go +++ b/terraform-provider-constellation/internal/provider/cluster_resource.go @@ -54,6 +54,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/spf13/afero" + "k8s.io/client-go/tools/clientcmd" ) var ( @@ -102,9 +103,13 @@ type ClusterResourceModel struct { Azure types.Object `tfsdk:"azure"` OpenStack types.Object `tfsdk:"openstack"` - OwnerID types.String `tfsdk:"owner_id"` - ClusterID types.String `tfsdk:"cluster_id"` - KubeConfig types.String `tfsdk:"kubeconfig"` + OwnerID types.String `tfsdk:"owner_id"` + ClusterID types.String `tfsdk:"cluster_id"` + KubeConfig types.String `tfsdk:"kubeconfig"` + Host types.String `tfsdk:"host"` + ClientCertificate types.String `tfsdk:"client_certificate"` + ClientKey types.String `tfsdk:"client_key"` + ClusterCACertificate types.String `tfsdk:"cluster_ca_certificate"` } // networkConfigAttribute is the network config attribute's data model. @@ -417,8 +422,8 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "kubeconfig": schema.StringAttribute{ - MarkdownDescription: "The kubeconfig of the cluster.", - Description: "The kubeconfig of the cluster.", + MarkdownDescription: "The kubeconfig (file) of the cluster.", + Description: "The kubeconfig (file) of the cluster.", Computed: true, Sensitive: true, PlanModifiers: []planmodifier.String{ @@ -426,6 +431,43 @@ func (r *ClusterResource) Schema(_ context.Context, _ resource.SchemaRequest, re stringplanmodifier.UseStateForUnknown(), }, }, + "host": schema.StringAttribute{ + MarkdownDescription: "The host of the cluster.", + Description: "The host of the cluster.", + Computed: true, + PlanModifiers: []planmodifier.String{ + // We know that this value will never change after creation, so we can use the state value for upgrades. + stringplanmodifier.UseStateForUnknown(), + }, + }, + "client_certificate": schema.StringAttribute{ + MarkdownDescription: "The client certificate of the cluster.", + Description: "The client certificate of the cluster.", + Computed: true, + PlanModifiers: []planmodifier.String{ + // We know that this value will never change after creation, so we can use the state value for upgrades. + stringplanmodifier.UseStateForUnknown(), + }, + }, + "client_key": schema.StringAttribute{ + MarkdownDescription: "The client key of the cluster.", + Description: "The client key of the cluster.", + Computed: true, + Sensitive: true, + PlanModifiers: []planmodifier.String{ + // We know that this value will never change after creation, so we can use the state value for upgrades. + stringplanmodifier.UseStateForUnknown(), + }, + }, + "cluster_ca_certificate": schema.StringAttribute{ + MarkdownDescription: "The cluster CA certificate of the cluster.", + Description: "The cluster CA certificate of the cluster.", + Computed: true, + PlanModifiers: []planmodifier.String{ + // We know that this value will never change after creation, so we can use the state value for upgrades. + stringplanmodifier.UseStateForUnknown(), + }, + }, }, } } @@ -1171,7 +1213,45 @@ func (r *ClusterResource) runInitRPC(ctx context.Context, applier *constellation } // Save data from init response into the Terraform state + + // Save the raw kubeconfig file. data.KubeConfig = types.StringValue(string(initOutput.Kubeconfig)) + + // Unmarshal the kubeconfig to get the fine-grained values. + kubeconfig, err := clientcmd.Load(initOutput.Kubeconfig) + if err != nil { + diags.AddError("Unmarshalling kubeconfig", err.Error()) + return diags + } + + clusterContext, ok := kubeconfig.Contexts[kubeconfig.CurrentContext] + if !ok { + diags.AddError("Getting cluster context", + fmt.Sprintf("Context %s not found in kubeconfig", kubeconfig.CurrentContext)) + return diags + } + + cluster, ok := kubeconfig.Clusters[clusterContext.Cluster] + if !ok { + diags.AddError("Getting cluster", + fmt.Sprintf("Cluster %s not found in kubeconfig", clusterContext.Cluster)) + return diags + } + + data.Host = types.StringValue(cluster.Server) + data.ClusterCACertificate = types.StringValue(string(cluster.CertificateAuthorityData)) + + authInfo, ok := kubeconfig.AuthInfos[clusterContext.AuthInfo] + if !ok { + diags.AddError("Getting auth info", + fmt.Sprintf("Auth info %s not found in kubeconfig", clusterContext.AuthInfo)) + return diags + } + + data.ClientCertificate = types.StringValue(string(authInfo.ClientCertificateData)) + data.ClientKey = types.StringValue(string(authInfo.ClientKeyData)) + + // Save other values from the init response. data.ClusterID = types.StringValue(initOutput.ClusterID) data.OwnerID = types.StringValue(initOutput.OwnerID)