diff --git a/example/byok/main.tf b/example/byok/main.tf index e2e807a..c5d02c0 100644 --- a/example/byok/main.tf +++ b/example/byok/main.tf @@ -1,7 +1,7 @@ terraform { required_providers { plural = { - source = "pluralsh/plural" + source = "pluralsh/plural" version = "0.0.1" } } @@ -12,22 +12,18 @@ provider "plural" { } resource "plural_cluster" "byok_workload_cluster" { - name = "workload-cluster-tf" - handle = "wctf" - cloud = "byok" - protect = "false" - cloud_settings = { - byok = { - kubeconfig = { - # Required, can be sourced from environment variables - } - } + name = "workload-cluster-tf" + handle = "wctf" + protect = "false" + kubeconfig = { + # Required, can be sourced from environment variables + # export PLURAL_KUBE_CONFIG_PATH to read from local file } tags = { "managed-by" = "terraform-provider-plural" } } -data "plural_cluster" "byok_workload_cluster" { - handle = "wctf" -} +#data "plural_cluster" "byok_workload_cluster" { +# handle = "wctf" +#} diff --git a/internal/resource/cluster.go b/internal/resource/cluster.go index a5b2fd6..632d5b1 100644 --- a/internal/resource/cluster.go +++ b/internal/resource/cluster.go @@ -65,13 +65,13 @@ func (r *clusterResource) Create(ctx context.Context, req resource.CreateRequest return } - if common.IsCloud(data.Cloud.ValueString(), common.CloudBYOK) { + if common.IsCloud(data.Cloud.ValueString(), common.CloudBYOK) && data.HasKubeconfig() { if result.CreateCluster.DeployToken == nil { resp.Diagnostics.AddError("Client Error", "Unable to fetch cluster deploy token") return } - handler, err := NewOperatorHandler(ctx, &data.CloudSettings.BYOK.Kubeconfig, r.consoleUrl) + handler, err := NewOperatorHandler(ctx, data.GetKubeconfig(), r.consoleUrl) if err != nil { resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to init operator handler, got error: %s", err)) return diff --git a/internal/resource/cluster_model.go b/internal/resource/cluster_model.go index 57ef133..034ad28 100644 --- a/internal/resource/cluster_model.go +++ b/internal/resource/cluster_model.go @@ -25,6 +25,7 @@ type cluster struct { Bindings *common.ClusterBindings `tfsdk:"bindings"` NodePools types.Map `tfsdk:"node_pools"` CloudSettings *ClusterCloudSettings `tfsdk:"cloud_settings"` + Kubeconfig *Kubeconfig `tfsdk:"kubeconfig"` } func (c *cluster) NodePoolsAttribute(ctx context.Context, d diag.Diagnostics) []*console.NodePoolAttributes { @@ -110,6 +111,22 @@ func (c *cluster) FromCreate(cc *console.CreateCluster, ctx context.Context, d d c.NodePools = common.ClusterNodePoolsFrom(cc.CreateCluster.NodePools, c.NodePools, ctx, d) } +func (c *cluster) HasKubeconfig() bool { + return c.Kubeconfig != nil || (c.CloudSettings != nil && c.CloudSettings.BYOK != nil && c.CloudSettings.BYOK.Kubeconfig != nil) +} + +func (c *cluster) GetKubeconfig() *Kubeconfig { + if !c.HasKubeconfig() { + return nil + } + + if c.Kubeconfig != nil { + return c.Kubeconfig + } + + return c.CloudSettings.BYOK.Kubeconfig +} + type ClusterCloudSettings struct { AWS *ClusterCloudSettingsAWS `tfsdk:"aws"` Azure *ClusterCloudSettingsAzure `tfsdk:"azure"` @@ -178,7 +195,7 @@ func (c *ClusterCloudSettingsGCP) Attributes() *console.GcpCloudAttributes { } type ClusterCloudSettingsBYOK struct { - Kubeconfig Kubeconfig `tfsdk:"kubeconfig"` + Kubeconfig *Kubeconfig `tfsdk:"kubeconfig"` } type Kubeconfig struct { diff --git a/internal/resource/cluster_schema.go b/internal/resource/cluster_schema.go index 541b110..f555f61 100644 --- a/internal/resource/cluster_schema.go +++ b/internal/resource/cluster_schema.go @@ -1,6 +1,8 @@ package resource import ( + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "terraform-provider-plural/internal/common" "terraform-provider-plural/internal/defaults" internalvalidator "terraform-provider-plural/internal/validator" @@ -76,7 +78,9 @@ func (r *clusterResource) schema() schema.Schema { "cloud": schema.StringAttribute{ Description: "The cloud provider used to create this cluster.", MarkdownDescription: "The cloud provider used to create this cluster.", - Required: true, + Computed: true, + Optional: true, + Default: stringdefault.StaticString(common.CloudBYOK.String()), PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, Validators: []validator.String{stringvalidator.OneOfCaseInsensitive( common.CloudBYOK.String(), common.CloudAWS.String(), common.CloudAzure.String(), common.CloudGCP.String()), @@ -85,12 +89,17 @@ func (r *clusterResource) schema() schema.Schema { common.CloudAzure.String(), common.CloudGCP.String(), }), path.MatchRoot("provider_id")), + internalvalidator.AlsoRequiresIf(internalvalidator.RequiresIfSourceValueOneOf([]string{ + common.CloudAWS.String(), + common.CloudAzure.String(), + common.CloudGCP.String(), + }), path.MatchRoot("cloud_settings")), }, }, "cloud_settings": schema.SingleNestedAttribute{ Description: "Cloud-specific settings for this cluster.", MarkdownDescription: "Cloud-specific settings for this cluster.", - Required: true, + Optional: true, Attributes: map[string]schema.Attribute{ "aws": r.awsCloudSettingsSchema(), "azure": r.azureCloudSettingsSchema(), @@ -99,6 +108,7 @@ func (r *clusterResource) schema() schema.Schema { }, PlanModifiers: []planmodifier.Object{objectplanmodifier.RequiresReplace()}, }, + "kubeconfig": r.kubeconfigSchema(false), "node_pools": schema.MapNestedAttribute{ Description: "Experimental, not ready for production use. Map of node pool specs managed by this cluster, where the key is name of the node pool and value contains the spec. Leave empty for bring your own cluster.", MarkdownDescription: "**Experimental, not ready for production use.** Map of node pool specs managed by this cluster, where the key is name of the node pool and value contains the spec. Leave empty for bring your own cluster.", @@ -316,143 +326,145 @@ func (r *clusterResource) gcpCloudSettingsSchema() schema.SingleNestedAttribute } } -func (r *clusterResource) byokCloudSettingsSchema() schema.SingleNestedAttribute { +func (r *clusterResource) kubeconfigSchema(deprecated bool) schema.SingleNestedAttribute { return schema.SingleNestedAttribute{ + DeprecationMessage: func() string { + if deprecated { + return "kubeconfig configuration has been moved from byok cloud settings to the cluster" + } + + return "" + }(), Optional: true, Attributes: map[string]schema.Attribute{ - "kubeconfig": schema.SingleNestedAttribute{ - Optional: true, - Attributes: map[string]schema.Attribute{ - "host": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_HOST", ""), - Description: "The complete address of the Kubernetes cluster, using scheme://hostname:port format. Can be sourced from PLURAL_KUBE_HOST.", - MarkdownDescription: "The complete address of the Kubernetes cluster, using scheme://hostname:port format. Can be sourced from `PLURAL_KUBE_HOST`.", - }, - "username": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_USER", ""), - Description: "The username for basic authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_USER.", - MarkdownDescription: "The username for basic authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_USER`.", - }, - "password": schema.StringAttribute{ - Optional: true, - Computed: true, - Sensitive: true, - Default: defaults.Env("PLURAL_KUBE_PASSWORD", ""), - Description: "The password for basic authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_PASSWORD.", - MarkdownDescription: "The password for basic authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_PASSWORD`.", - }, - "insecure": schema.BoolAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_INSECURE", false), - Description: "Skips the validity check for the server's certificate. This will make your HTTPS connections insecure. Can be sourced from PLURAL_KUBE_INSECURE.", - MarkdownDescription: "Skips the validity check for the server's certificate. This will make your HTTPS connections insecure. Can be sourced from `PLURAL_KUBE_INSECURE`.", - }, - "tls_server_name": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_TLS_SERVER_NAME", ""), - Description: "TLS server name is used to check server certificate. If it is empty, the hostname used to contact the server is used. Can be sourced from PLURAL_KUBE_TLS_SERVER_NAME.", - MarkdownDescription: "TLS server name is used to check server certificate. If it is empty, the hostname used to contact the server is used. Can be sourced from `PLURAL_KUBE_TLS_SERVER_NAME`.", - }, - "client_certificate": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CLIENT_CERT_DATA", ""), - Description: "The path to a client cert file for TLS. Can be sourced from PLURAL_KUBE_CLIENT_CERT_DATA.", - MarkdownDescription: "The path to a client cert file for TLS. Can be sourced from `PLURAL_KUBE_CLIENT_CERT_DATA`.", - }, - "client_key": schema.StringAttribute{ - Optional: true, - Computed: true, - Sensitive: true, - Default: defaults.Env("PLURAL_KUBE_CLIENT_KEY_DATA", ""), - Description: "The path to a client key file for TLS. Can be sourced from PLURAL_KUBE_CLIENT_KEY_DATA.", - MarkdownDescription: "The path to a client key file for TLS. Can be sourced from `PLURAL_KUBE_CLIENT_KEY_DATA`.", - }, - "cluster_ca_certificate": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CLUSTER_CA_CERT_DATA", ""), - Description: "The path to a cert file for the certificate authority. Can be sourced from PLURAL_KUBE_CLUSTER_CA_CERT_DATA.", - MarkdownDescription: "The path to a cert file for the certificate authority. Can be sourced from `PLURAL_KUBE_CLUSTER_CA_CERT_DATA`.", - }, - "config_path": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CONFIG_PATH", ""), - Description: "Path to the kubeconfig file. Can be sourced from PLURAL_KUBE_CONFIG_PATH.", - MarkdownDescription: "Path to the kubeconfig file. Can be sourced from `PLURAL_KUBE_CONFIG_PATH`.", - }, - "config_context": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CTX", ""), - Description: "kubeconfig context to use. Can be sourced from PLURAL_KUBE_CTX.", - MarkdownDescription: "kubeconfig context to use. Can be sourced from `PLURAL_KUBE_CTX`.", - }, - "config_context_auth_info": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CTX_AUTH_INFO", ""), - Description: "Can be sourced from PLURAL_KUBE_CTX_AUTH_INFO.", - MarkdownDescription: "Can be sourced from `PLURAL_KUBE_CTX_AUTH_INFO`.", - }, - "config_context_cluster": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_CTX_CLUSTER", ""), - Description: "Can be sourced from PLURAL_KUBE_CTX_CLUSTER.", - MarkdownDescription: "Can be sourced from `PLURAL_KUBE_CTX_CLUSTER`.", - }, - "token": schema.StringAttribute{ - Optional: true, - Computed: true, - Sensitive: true, - Default: defaults.Env("PLURAL_KUBE_TOKEN", ""), - Description: "Token is the bearer token for authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_TOKEN.", - MarkdownDescription: "Token is the bearer token for authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_TOKEN`.", - }, - "proxy_url": schema.StringAttribute{ - Optional: true, - Computed: true, - Default: defaults.Env("PLURAL_KUBE_PROXY_URL", ""), - Description: "The URL to the proxy to be used for all requests made by this client. Can be sourced from PLURAL_KUBE_PROXY_URL.", - MarkdownDescription: "The URL to the proxy to be used for all requests made by this client. Can be sourced from `PLURAL_KUBE_PROXY_URL`.", - }, - "exec": schema.ListNestedAttribute{ - Optional: true, - MarkdownDescription: "Specifies a command to provide client credentials", - Validators: []validator.List{listvalidator.SizeAtMost(1)}, - NestedObject: schema.NestedAttributeObject{ - Attributes: map[string]schema.Attribute{ - "command": schema.StringAttribute{ - Description: "Command to execute.", - MarkdownDescription: "Command to execute.", - Required: true, - }, - "args": schema.ListAttribute{ - Description: "Arguments to pass to the command when executing it.", - MarkdownDescription: "Arguments to pass to the command when executing it.", - Optional: true, - ElementType: types.StringType, - }, - "env": schema.MapAttribute{ - Description: "Defines environment variables to expose to the process.", - MarkdownDescription: "Defines environment variables to expose to the process.", - Optional: true, - ElementType: types.StringType, - }, - "api_version": schema.StringAttribute{ - Description: "Preferred input version.", - MarkdownDescription: "Preferred input version.", - Required: true, - }, - }, + "host": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_HOST", ""), + Description: "The complete address of the Kubernetes cluster, using scheme://hostname:port format. Can be sourced from PLURAL_KUBE_HOST.", + MarkdownDescription: "The complete address of the Kubernetes cluster, using scheme://hostname:port format. Can be sourced from `PLURAL_KUBE_HOST`.", + }, + "username": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_USER", ""), + Description: "The username for basic authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_USER.", + MarkdownDescription: "The username for basic authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_USER`.", + }, + "password": schema.StringAttribute{ + Optional: true, + Computed: true, + Sensitive: true, + Default: defaults.Env("PLURAL_KUBE_PASSWORD", ""), + Description: "The password for basic authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_PASSWORD.", + MarkdownDescription: "The password for basic authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_PASSWORD`.", + }, + "insecure": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_INSECURE", false), + Description: "Skips the validity check for the server's certificate. This will make your HTTPS connections insecure. Can be sourced from PLURAL_KUBE_INSECURE.", + MarkdownDescription: "Skips the validity check for the server's certificate. This will make your HTTPS connections insecure. Can be sourced from `PLURAL_KUBE_INSECURE`.", + }, + "tls_server_name": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_TLS_SERVER_NAME", ""), + Description: "TLS server name is used to check server certificate. If it is empty, the hostname used to contact the server is used. Can be sourced from PLURAL_KUBE_TLS_SERVER_NAME.", + MarkdownDescription: "TLS server name is used to check server certificate. If it is empty, the hostname used to contact the server is used. Can be sourced from `PLURAL_KUBE_TLS_SERVER_NAME`.", + }, + "client_certificate": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CLIENT_CERT_DATA", ""), + Description: "The path to a client cert file for TLS. Can be sourced from PLURAL_KUBE_CLIENT_CERT_DATA.", + MarkdownDescription: "The path to a client cert file for TLS. Can be sourced from `PLURAL_KUBE_CLIENT_CERT_DATA`.", + }, + "client_key": schema.StringAttribute{ + Optional: true, + Computed: true, + Sensitive: true, + Default: defaults.Env("PLURAL_KUBE_CLIENT_KEY_DATA", ""), + Description: "The path to a client key file for TLS. Can be sourced from PLURAL_KUBE_CLIENT_KEY_DATA.", + MarkdownDescription: "The path to a client key file for TLS. Can be sourced from `PLURAL_KUBE_CLIENT_KEY_DATA`.", + }, + "cluster_ca_certificate": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CLUSTER_CA_CERT_DATA", ""), + Description: "The path to a cert file for the certificate authority. Can be sourced from PLURAL_KUBE_CLUSTER_CA_CERT_DATA.", + MarkdownDescription: "The path to a cert file for the certificate authority. Can be sourced from `PLURAL_KUBE_CLUSTER_CA_CERT_DATA`.", + }, + "config_path": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CONFIG_PATH", ""), + Description: "Path to the kubeconfig file. Can be sourced from PLURAL_KUBE_CONFIG_PATH.", + MarkdownDescription: "Path to the kubeconfig file. Can be sourced from `PLURAL_KUBE_CONFIG_PATH`.", + }, + "config_context": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CTX", ""), + Description: "kubeconfig context to use. Can be sourced from PLURAL_KUBE_CTX.", + MarkdownDescription: "kubeconfig context to use. Can be sourced from `PLURAL_KUBE_CTX`.", + }, + "config_context_auth_info": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CTX_AUTH_INFO", ""), + Description: "Can be sourced from PLURAL_KUBE_CTX_AUTH_INFO.", + MarkdownDescription: "Can be sourced from `PLURAL_KUBE_CTX_AUTH_INFO`.", + }, + "config_context_cluster": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_CTX_CLUSTER", ""), + Description: "Can be sourced from PLURAL_KUBE_CTX_CLUSTER.", + MarkdownDescription: "Can be sourced from `PLURAL_KUBE_CTX_CLUSTER`.", + }, + "token": schema.StringAttribute{ + Optional: true, + Computed: true, + Sensitive: true, + Default: defaults.Env("PLURAL_KUBE_TOKEN", ""), + Description: "Token is the bearer token for authentication to the Kubernetes cluster. Can be sourced from PLURAL_KUBE_TOKEN.", + MarkdownDescription: "Token is the bearer token for authentication to the Kubernetes cluster. Can be sourced from `PLURAL_KUBE_TOKEN`.", + }, + "proxy_url": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: defaults.Env("PLURAL_KUBE_PROXY_URL", ""), + Description: "The URL to the proxy to be used for all requests made by this client. Can be sourced from PLURAL_KUBE_PROXY_URL.", + MarkdownDescription: "The URL to the proxy to be used for all requests made by this client. Can be sourced from `PLURAL_KUBE_PROXY_URL`.", + }, + "exec": schema.ListNestedAttribute{ + Optional: true, + MarkdownDescription: "Specifies a command to provide client credentials", + Validators: []validator.List{listvalidator.SizeAtMost(1)}, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "command": schema.StringAttribute{ + Description: "Command to execute.", + MarkdownDescription: "Command to execute.", + Required: true, + }, + "args": schema.ListAttribute{ + Description: "Arguments to pass to the command when executing it.", + MarkdownDescription: "Arguments to pass to the command when executing it.", + Optional: true, + ElementType: types.StringType, + }, + "env": schema.MapAttribute{ + Description: "Defines environment variables to expose to the process.", + MarkdownDescription: "Defines environment variables to expose to the process.", + Optional: true, + ElementType: types.StringType, + }, + "api_version": schema.StringAttribute{ + Description: "Preferred input version.", + MarkdownDescription: "Preferred input version.", + Required: true, }, }, }, @@ -460,3 +472,12 @@ func (r *clusterResource) byokCloudSettingsSchema() schema.SingleNestedAttribute }, } } + +func (r *clusterResource) byokCloudSettingsSchema() schema.SingleNestedAttribute { + return schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "kubeconfig": r.kubeconfigSchema(true), + }, + } +}