This is an automated email from the ASF dual-hosted git repository.

sudo87 pushed a commit to branch k8ClusterConfig
in repository 
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git

commit 45d3db0a909bcece94240b87872422ffcfe8a74b
Author: Manoj Kumar <[email protected]>
AuthorDate: Wed Aug 12 14:58:33 2026 +0530

    Add cloudstack_kubernetes_cluster_config data source
    
    CloudStack's CKS clusters expose their kubeconfig only through the
    getKubernetesClusterConfig API, with no Terraform-native way to feed it
    into the kubernetes/helm providers. This adds a data source that fetches
    it and parses out endpoint, cluster_ca_certificate, client_certificate,
    and client_key as separate attributes named to match those providers'
    own arguments, alongside the raw config_data for anything else.
    
    Context resolution deliberately refuses to guess: if a kubeconfig's
    current-context names a cluster or user absent from a list of more than
    one entry, parsing errors instead of silently falling back, since a
    wrong guess there would silently serve one cluster's endpoint paired
    with a different cluster's credentials.
    
    Closes #276.
---
 ..._source_cloudstack_kubernetes_cluster_config.go | 290 ++++++++++++++++++
 ...ce_cloudstack_kubernetes_cluster_config_test.go | 332 +++++++++++++++++++++
 cloudstack/provider.go                             |  55 ++--
 go.mod                                             |   1 +
 .../docs/d/kubernetes_cluster_config.html.markdown |  96 ++++++
 5 files changed, 747 insertions(+), 27 deletions(-)

diff --git a/cloudstack/data_source_cloudstack_kubernetes_cluster_config.go 
b/cloudstack/data_source_cloudstack_kubernetes_cluster_config.go
new file mode 100644
index 0000000..a2ce623
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_kubernetes_cluster_config.go
@@ -0,0 +1,290 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "encoding/base64"
+       "fmt"
+       "log"
+       "slices"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+       "gopkg.in/yaml.v3"
+)
+
+func dataSourceCloudstackKubernetesClusterConfig() *schema.Resource {
+       return &schema.Resource{
+               Read: datasourceCloudStackKubernetesClusterConfigRead,
+               Schema: map[string]*schema.Schema{
+                       "cluster_id": {
+                               Type:        schema.TypeString,
+                               Required:    true,
+                               Description: "The ID of the Kubernetes cluster 
to retrieve the config for.",
+                       },
+
+                       //Computed values
+                       "name": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Description: "The name of the Kubernetes 
cluster.",
+                       },
+
+                       "config_data": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Sensitive:   true,
+                               Description: "The raw kubeconfig of the 
Kubernetes cluster.",
+                       },
+
+                       "endpoint": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Description: "The URL of the Kubernetes API 
server, taken from the kubeconfig.",
+                       },
+
+                       "cluster_ca_certificate": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Description: "The PEM encoded certificate 
authority of the Kubernetes API server.",
+                       },
+
+                       "client_certificate": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Description: "The PEM encoded client 
certificate used to authenticate against the Kubernetes API server.",
+                       },
+
+                       "client_key": {
+                               Type:        schema.TypeString,
+                               Computed:    true,
+                               Sensitive:   true,
+                               Description: "The PEM encoded client key used 
to authenticate against the Kubernetes API server.",
+                       },
+               },
+       }
+}
+
+// kubeConfig models only the subset of a kubeconfig document that this data
+// source exposes as separate attributes.
+type kubeConfig struct {
+       CurrentContext string              `yaml:"current-context"`
+       Clusters       []kubeConfigCluster `yaml:"clusters"`
+       Contexts       []kubeConfigContext `yaml:"contexts"`
+       Users          []kubeConfigUser    `yaml:"users"`
+}
+
+type kubeConfigCluster struct {
+       Name    string                `yaml:"name"`
+       Cluster kubeConfigClusterInfo `yaml:"cluster"`
+}
+
+type kubeConfigClusterInfo struct {
+       Server                   string `yaml:"server"`
+       CertificateAuthorityData string `yaml:"certificate-authority-data"`
+}
+
+type kubeConfigContext struct {
+       Name    string                `yaml:"name"`
+       Context kubeConfigContextInfo `yaml:"context"`
+}
+
+type kubeConfigContextInfo struct {
+       Cluster string `yaml:"cluster"`
+       User    string `yaml:"user"`
+}
+
+type kubeConfigUser struct {
+       Name string             `yaml:"name"`
+       User kubeConfigUserInfo `yaml:"user"`
+}
+
+type kubeConfigUserInfo struct {
+       ClientCertificateData string `yaml:"client-certificate-data"`
+       ClientKeyData         string `yaml:"client-key-data"`
+}
+
+// kubernetesClusterCredentials holds the values extracted from a kubeconfig 
that
+// are needed to connect to the Kubernetes API server.
+type kubernetesClusterCredentials struct {
+       Endpoint             string
+       ClusterCACertificate string
+       ClientCertificate    string
+       ClientKey            string
+}
+
+func datasourceCloudStackKubernetesClusterConfigRead(d *schema.ResourceData, 
meta interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       clusterID := d.Get("cluster_id").(string)
+
+       log.Printf("[DEBUG] Retrieving config of Kubernetes Cluster %s", 
clusterID)
+
+       p := cs.Kubernetes.NewGetKubernetesClusterConfigParams()
+       p.SetId(clusterID)
+
+       config, err := cs.Kubernetes.GetKubernetesClusterConfig(p)
+       if err != nil {
+               // CloudStack refuses to hand out a config while the cluster is 
still
+               // starting, and when the Kubernetes service plugin is disabled.
+               return fmt.Errorf("Failed to get the config of Kubernetes 
Cluster %s: %s", clusterID, err)
+       }
+
+       if config.Configdata == "" {
+               return fmt.Errorf("Kubernetes Cluster %s returned an empty 
config; the cluster ID may not "+
+                       "exist, the cluster may still be starting, or the 
Kubernetes service plugin may be disabled", clusterID)
+       }
+
+       credentials, err := parseKubernetesClusterConfig(config.Configdata)
+       if err != nil {
+               return fmt.Errorf("Failed to parse the config of Kubernetes 
Cluster %s: %s", clusterID, err)
+       }
+
+       if *credentials == (kubernetesClusterCredentials{}) {
+               return fmt.Errorf("The config of Kubernetes Cluster %s does not 
contain a cluster endpoint, "+
+                       "CA certificate, client certificate or client key; use 
config_data directly instead", clusterID)
+       }
+
+       d.SetId(config.Id)
+       d.Set("name", config.Name)
+       d.Set("config_data", config.Configdata)
+       d.Set("endpoint", credentials.Endpoint)
+       d.Set("cluster_ca_certificate", credentials.ClusterCACertificate)
+       d.Set("client_certificate", credentials.ClientCertificate)
+       d.Set("client_key", credentials.ClientKey)
+
+       return nil
+}
+
+// parseKubernetesClusterConfig extracts the endpoint and the client 
credentials
+// from a kubeconfig document. An unparsable document, an undecodable
+// certificate, or a current context that names a cluster/user absent from a
+// multi-entry list is an error; a kubeconfig that simply does not carry a
+// cluster or a user entry at all only yields empty values, so that config_data
+// stays usable.
+func parseKubernetesClusterConfig(configData string) 
(*kubernetesClusterCredentials, error) {
+       var config kubeConfig
+       if err := yaml.Unmarshal([]byte(configData), &config); err != nil {
+               return nil, fmt.Errorf("Invalid kubeconfig: %s", err)
+       }
+
+       // Resolve which cluster and user the current context points at.
+       clusterName, userName := "", ""
+       for _, context := range config.Contexts {
+               if context.Name == config.CurrentContext {
+                       clusterName = context.Context.Cluster
+                       userName = context.Context.User
+                       break
+               }
+       }
+
+       credentials := &kubernetesClusterCredentials{}
+
+       cluster, err := findKubeConfigEntry(config.Clusters, clusterName, 
"cluster",
+               func(c kubeConfigCluster) string { return c.Name })
+       if err != nil {
+               return nil, err
+       }
+       if cluster >= 0 {
+               caCertificate, err := decodeKubernetesClusterConfigValue(
+                       
config.Clusters[cluster].Cluster.CertificateAuthorityData, 
"certificate-authority-data")
+               if err != nil {
+                       return nil, err
+               }
+
+               credentials.Endpoint = config.Clusters[cluster].Cluster.Server
+               credentials.ClusterCACertificate = caCertificate
+       }
+
+       user, err := findKubeConfigEntry(config.Users, userName, "user",
+               func(u kubeConfigUser) string { return u.Name })
+       if err != nil {
+               return nil, err
+       }
+       if user >= 0 {
+               clientCertificate, err := decodeKubernetesClusterConfigValue(
+                       config.Users[user].User.ClientCertificateData, 
"client-certificate-data")
+               if err != nil {
+                       return nil, err
+               }
+
+               clientKey, err := decodeKubernetesClusterConfigValue(
+                       config.Users[user].User.ClientKeyData, 
"client-key-data")
+               if err != nil {
+                       return nil, err
+               }
+
+               credentials.ClientCertificate = clientCertificate
+               credentials.ClientKey = clientKey
+       }
+
+       return credentials, nil
+}
+
+// findKubeConfigEntry returns the index of the named entry in a kubeconfig
+// list. It returns -1 with no error when the list is empty, so that a
+// kubeconfig which does not carry the entry at all still leaves config_data
+// usable. When the current context does not name an entry at all, or there is
+// only one entry to begin with, it falls back to the first one, since there is
+// only one reasonable candidate either way. But when the current context names
+// a specific entry that is absent from a list of more than one, guessing is
+// refused: picking some other entry could silently pair one cluster's endpoint
+// with a different cluster's client credentials.
+func findKubeConfigEntry[T any](entries []T, name string, kind string, nameOf 
func(T) string) (int, error) {
+       if len(entries) == 0 {
+               log.Printf("[WARN] Kubeconfig does not contain any %s", kind)
+               return -1, nil
+       }
+
+       if name != "" {
+               if i := slices.IndexFunc(entries, func(e T) bool { return 
nameOf(e) == name }); i >= 0 {
+                       return i, nil
+               }
+
+               if len(entries) > 1 {
+                       return -1, fmt.Errorf("kubeconfig context references %s 
%q, which is not defined among %d %s entries",
+                               kind, name, len(entries), kind)
+               }
+
+               log.Printf("[WARN] Kubeconfig does not contain the %s %q 
referenced by its current context, using the only %s instead", kind, name, kind)
+               return 0, nil
+       }
+
+       if len(entries) > 1 {
+               log.Printf("[WARN] Kubeconfig has %d %s entries and no current 
context to select one, using the first %s", len(entries), kind, kind)
+       }
+
+       return 0, nil
+}
+
+// decodeKubernetesClusterConfigValue decodes a base64 encoded kubeconfig 
field,
+// passing an absent field through as an empty string.
+func decodeKubernetesClusterConfigValue(value string, field string) (string, 
error) {
+       if value == "" {
+               log.Printf("[WARN] Kubeconfig does not contain %s", field)
+               return "", nil
+       }
+
+       decoded, err := base64.StdEncoding.DecodeString(value)
+       if err != nil {
+               return "", fmt.Errorf("Failed to decode %s: %s", field, err)
+       }
+
+       return string(decoded), nil
+}
diff --git 
a/cloudstack/data_source_cloudstack_kubernetes_cluster_config_test.go 
b/cloudstack/data_source_cloudstack_kubernetes_cluster_config_test.go
new file mode 100644
index 0000000..8365a3f
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_kubernetes_cluster_config_test.go
@@ -0,0 +1,332 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+       "encoding/base64"
+       "fmt"
+       "strings"
+       "testing"
+)
+
+func TestParseKubernetesClusterConfig(t *testing.T) {
+       tests := []struct {
+               name       string
+               configData string
+               expected   kubernetesClusterCredentials
+               expectErr  string
+       }{
+               {
+                       name: "kubeadm admin.conf as returned by CloudStack",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+preferences: {}
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: kubernetes
+contexts:
+- context:
+    cluster: kubernetes
+    user: kubernetes-admin
+  name: kubernetes-admin@kubernetes
+current-context: kubernetes-admin@kubernetes
+users:
+- name: kubernetes-admin
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+`, testBase64("ca-certificate"), testBase64("client-certificate"), 
testBase64("client-key")),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.1.1.1:6443";,
+                               ClusterCACertificate: "ca-certificate",
+                               ClientCertificate:    "client-certificate",
+                               ClientKey:            "client-key",
+                       },
+               },
+               {
+                       // Guards against blindly taking clusters[0] / users[0].
+                       name:       "current context selects the second cluster 
and user",
+                       configData: testKubeConfigTwoClusters("second@second"),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.2.2.2:6443";,
+                               ClusterCACertificate: "second-ca-certificate",
+                               ClientCertificate:    
"second-client-certificate",
+                               ClientKey:            "second-client-key",
+                       },
+               },
+               {
+                       name:       "absent current context falls back to the 
first entry",
+                       configData: testKubeConfigTwoClusters(""),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.1.1.1:6443";,
+                               ClusterCACertificate: "first-ca-certificate",
+                               ClientCertificate:    
"first-client-certificate",
+                               ClientKey:            "first-client-key",
+                       },
+               },
+               {
+                       name:       "unknown current context falls back to the 
first entry",
+                       configData: 
testKubeConfigTwoClusters("missing@missing"),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.1.1.1:6443";,
+                               ClusterCACertificate: "first-ca-certificate",
+                               ClientCertificate:    
"first-client-certificate",
+                               ClientKey:            "first-client-key",
+                       },
+               },
+               {
+                       // The context resolves, but names a cluster and user 
that the
+                       // kubeconfig does not define. With only one entry of 
each,
+                       // there's only one reasonable candidate, so this still 
falls
+                       // back rather than erroring.
+                       name: "current context naming a missing cluster falls 
back to the only entry",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+current-context: third@third
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: first
+contexts:
+- context:
+    cluster: third
+    user: third
+  name: third@third
+users:
+- name: first
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+`, testBase64("first-ca-certificate"),
+                               testBase64("first-client-certificate"), 
testBase64("first-client-key")),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.1.1.1:6443";,
+                               ClusterCACertificate: "first-ca-certificate",
+                               ClientCertificate:    
"first-client-certificate",
+                               ClientKey:            "first-client-key",
+                       },
+               },
+               {
+                       // Here the context resolves and names a cluster absent 
from a
+                       // list of more than one: guessing which of the two is 
intended
+                       // could silently pair the wrong cluster/user together, 
so this
+                       // must be a hard error instead of a silent fallback. 
Clusters are
+                       // resolved before users, so no users section is needed 
to reach
+                       // this error.
+                       name: "current context naming an undefined cluster 
among several is an error",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+current-context: third@third
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: first
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.2.2.2:6443
+  name: second
+contexts:
+- context:
+    cluster: third
+    user: third
+  name: third@third
+`, testBase64("first-ca-certificate"), testBase64("second-ca-certificate")),
+                       expectErr: "which is not defined",
+               },
+               {
+                       // The same hard error, but reached through the user 
branch
+                       // instead of the cluster branch, so a future edit that 
swaps
+                       // clusterName/userName or config.Clusters/config.Users 
between
+                       // the two findKubeConfigEntry call sites would still 
be caught.
+                       name: "current context naming an undefined user among 
several is an error",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+current-context: kubernetes@kubernetes
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: kubernetes
+contexts:
+- context:
+    cluster: kubernetes
+    user: third
+  name: kubernetes@kubernetes
+users:
+- name: first
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+- name: second
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+`, testBase64("ca-certificate"),
+                               testBase64("first-client-certificate"), 
testBase64("first-client-key"),
+                               testBase64("second-client-certificate"), 
testBase64("second-client-key")),
+                       expectErr: "which is not defined",
+               },
+               {
+                       // A kubeconfig without client certificates must not 
fail the data
+                       // source, so that config_data stays usable.
+                       name: "no users leaves the client credentials empty",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: kubernetes
+`, testBase64("ca-certificate")),
+                       expected: kubernetesClusterCredentials{
+                               Endpoint:             "https://10.1.1.1:6443";,
+                               ClusterCACertificate: "ca-certificate",
+                       },
+               },
+               {
+                       name: "no clusters leaves the endpoint empty",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+users:
+- name: kubernetes-admin
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+`, testBase64("client-certificate"), testBase64("client-key")),
+                       expected: kubernetesClusterCredentials{
+                               ClientCertificate: "client-certificate",
+                               ClientKey:         "client-key",
+                       },
+               },
+               {
+                       // The empty-config guard lives in the read function, 
not here.
+                       name:       "empty document yields no credentials and 
no error",
+                       configData: "",
+                       expected:   kubernetesClusterCredentials{},
+               },
+               {
+                       name:       "invalid yaml",
+                       configData: "\tthis is not a kubeconfig",
+                       expectErr:  "Invalid kubeconfig",
+               },
+               {
+                       name: "certificate authority data is not base64",
+                       configData: `apiVersion: v1
+kind: Config
+clusters:
+- cluster:
+    certificate-authority-data: "@@@ not base64 @@@"
+    server: https://10.1.1.1:6443
+  name: kubernetes
+`,
+                       expectErr: "Failed to decode 
certificate-authority-data",
+               },
+               {
+                       name: "client key data is not base64",
+                       configData: fmt.Sprintf(`apiVersion: v1
+kind: Config
+users:
+- name: kubernetes-admin
+  user:
+    client-certificate-data: %s
+    client-key-data: "@@@ not base64 @@@"
+`, testBase64("client-certificate")),
+                       expectErr: "Failed to decode client-key-data",
+               },
+       }
+
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       credentials, err := 
parseKubernetesClusterConfig(tt.configData)
+
+                       if tt.expectErr != "" {
+                               if err == nil {
+                                       t.Fatalf("Expected an error containing 
%q, got none", tt.expectErr)
+                               }
+                               if !strings.Contains(err.Error(), tt.expectErr) 
{
+                                       t.Fatalf("Expected an error containing 
%q, got: %s", tt.expectErr, err)
+                               }
+                               return
+                       }
+
+                       if err != nil {
+                               t.Fatalf("Unexpected error: %s", err)
+                       }
+                       if *credentials != tt.expected {
+                               t.Errorf("Expected %+v, got %+v", tt.expected, 
*credentials)
+                       }
+               })
+       }
+}
+
+func testBase64(value string) string {
+       return base64.StdEncoding.EncodeToString([]byte(value))
+}
+
+// testKubeConfigTwoClusters renders a kubeconfig holding two clusters and two
+// users, so that context resolution can be told apart from taking the first
+// entry. currentContext selects which one context resolution should pick; an
+// empty string omits the current-context key entirely. Every %s verb appears
+// in the same order as its argument below, so the substitution can be checked
+// by reading both top to bottom in lockstep.
+func testKubeConfigTwoClusters(currentContext string) string {
+       return fmt.Sprintf(`apiVersion: v1
+kind: Config
+current-context: %s
+clusters:
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.1.1.1:6443
+  name: first
+- cluster:
+    certificate-authority-data: %s
+    server: https://10.2.2.2:6443
+  name: second
+contexts:
+- context:
+    cluster: first
+    user: first
+  name: first@first
+- context:
+    cluster: second
+    user: second
+  name: second@second
+users:
+- name: first
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+- name: second
+  user:
+    client-certificate-data: %s
+    client-key-data: %s
+`,
+               currentContext,
+               testBase64("first-ca-certificate"),
+               testBase64("second-ca-certificate"),
+               testBase64("first-client-certificate"),
+               testBase64("first-client-key"),
+               testBase64("second-client-certificate"),
+               testBase64("second-client-key"))
+}
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 7209014..5e4b851 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -78,33 +78,34 @@ func Provider() *schema.Provider {
                },
 
                DataSourcesMap: map[string]*schema.Resource{
-                       "cloudstack_autoscale_policy":     
dataSourceCloudstackAutoscalePolicy(),
-                       "cloudstack_autoscale_vm_group":   
dataSourceCloudstackAutoscaleVMGroup(),
-                       "cloudstack_autoscale_vm_profile": 
dataSourceCloudstackAutoscaleVMProfile(),
-                       "cloudstack_condition":            
dataSourceCloudstackCondition(),
-                       "cloudstack_counter":              
dataSourceCloudstackCounter(),
-                       "cloudstack_template":             
dataSourceCloudstackTemplate(),
-                       "cloudstack_ssh_keypair":          
dataSourceCloudstackSSHKeyPair(),
-                       "cloudstack_instance":             
dataSourceCloudstackInstance(),
-                       "cloudstack_network_offering":     
dataSourceCloudstackNetworkOffering(),
-                       "cloudstack_zone":                 
dataSourceCloudStackZone(),
-                       "cloudstack_service_offering":     
dataSourceCloudstackServiceOffering(),
-                       "cloudstack_volume":               
dataSourceCloudstackVolume(),
-                       "cloudstack_vpc":                  
dataSourceCloudstackVPC(),
-                       "cloudstack_ipaddress":            
dataSourceCloudstackIPAddress(),
-                       "cloudstack_user":                 
dataSourceCloudstackUser(),
-                       "cloudstack_vpn_connection":       
dataSourceCloudstackVPNConnection(),
-                       "cloudstack_pod":                  
dataSourceCloudstackPod(),
-                       "cloudstack_domain":               
dataSourceCloudstackDomain(),
-                       "cloudstack_project":              
dataSourceCloudstackProject(),
-                       "cloudstack_physical_network":     
dataSourceCloudStackPhysicalNetwork(),
-                       "cloudstack_role":                 
dataSourceCloudstackRole(),
-                       "cloudstack_cluster":              
dataSourceCloudstackCluster(),
-                       "cloudstack_limits":               
dataSourceCloudStackLimits(),
-                       "cloudstack_quota":                
dataSourceCloudStackQuota(),
-                       "cloudstack_quota_enabled":        
dataSourceCloudStackQuotaEnabled(),
-                       "cloudstack_quota_tariff":         
dataSourceCloudStackQuotaTariff(),
-                       "cloudstack_user_data":            
dataSourceCloudstackUserData(),
+                       "cloudstack_autoscale_policy":          
dataSourceCloudstackAutoscalePolicy(),
+                       "cloudstack_autoscale_vm_group":        
dataSourceCloudstackAutoscaleVMGroup(),
+                       "cloudstack_autoscale_vm_profile":      
dataSourceCloudstackAutoscaleVMProfile(),
+                       "cloudstack_condition":                 
dataSourceCloudstackCondition(),
+                       "cloudstack_counter":                   
dataSourceCloudstackCounter(),
+                       "cloudstack_template":                  
dataSourceCloudstackTemplate(),
+                       "cloudstack_ssh_keypair":               
dataSourceCloudstackSSHKeyPair(),
+                       "cloudstack_instance":                  
dataSourceCloudstackInstance(),
+                       "cloudstack_network_offering":          
dataSourceCloudstackNetworkOffering(),
+                       "cloudstack_zone":                      
dataSourceCloudStackZone(),
+                       "cloudstack_service_offering":          
dataSourceCloudstackServiceOffering(),
+                       "cloudstack_volume":                    
dataSourceCloudstackVolume(),
+                       "cloudstack_vpc":                       
dataSourceCloudstackVPC(),
+                       "cloudstack_ipaddress":                 
dataSourceCloudstackIPAddress(),
+                       "cloudstack_kubernetes_cluster_config": 
dataSourceCloudstackKubernetesClusterConfig(),
+                       "cloudstack_user":                      
dataSourceCloudstackUser(),
+                       "cloudstack_vpn_connection":            
dataSourceCloudstackVPNConnection(),
+                       "cloudstack_pod":                       
dataSourceCloudstackPod(),
+                       "cloudstack_domain":                    
dataSourceCloudstackDomain(),
+                       "cloudstack_project":                   
dataSourceCloudstackProject(),
+                       "cloudstack_physical_network":          
dataSourceCloudStackPhysicalNetwork(),
+                       "cloudstack_role":                      
dataSourceCloudstackRole(),
+                       "cloudstack_cluster":                   
dataSourceCloudstackCluster(),
+                       "cloudstack_limits":                    
dataSourceCloudStackLimits(),
+                       "cloudstack_quota":                     
dataSourceCloudStackQuota(),
+                       "cloudstack_quota_enabled":             
dataSourceCloudStackQuotaEnabled(),
+                       "cloudstack_quota_tariff":              
dataSourceCloudStackQuotaTariff(),
+                       "cloudstack_user_data":                 
dataSourceCloudstackUserData(),
                },
 
                ResourcesMap: map[string]*schema.Resource{
diff --git a/go.mod b/go.mod
index 6e69966..af62d75 100644
--- a/go.mod
+++ b/go.mod
@@ -27,6 +27,7 @@ require (
        github.com/hashicorp/terraform-plugin-mux v0.16.0
        github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0
        github.com/hashicorp/terraform-plugin-testing v1.7.0
+       gopkg.in/yaml.v3 v3.0.1
 )
 
 require (
diff --git a/website/docs/d/kubernetes_cluster_config.html.markdown 
b/website/docs/d/kubernetes_cluster_config.html.markdown
new file mode 100644
index 0000000..5202d3b
--- /dev/null
+++ b/website/docs/d/kubernetes_cluster_config.html.markdown
@@ -0,0 +1,96 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_kubernetes_cluster_config"
+sidebar_current: "docs-cloudstack-datasource-kubernetes-cluster-config"
+description: |-
+  Get the kubeconfig of a CloudStack Kubernetes cluster.
+---
+
+# cloudstack_kubernetes_cluster_config
+
+Use this data source to retrieve the kubeconfig of a CloudStack Kubernetes 
(CKS) cluster, so that
+the `kubernetes` and `helm` providers can be pointed at a cluster managed by 
Terraform.
+
+The cluster must be running for its config to be available. CloudStack does 
not return a config
+while a cluster is still being set up, or when the Kubernetes service plugin 
is disabled
+(`cloud.kubernetes.service.enabled`).
+
+~> **Note:** The kubeconfig contains the cluster administrator credentials, 
and every attribute of
+this data source is stored in plain text in your Terraform state. Protect the 
state accordingly,
+for example by using a remote backend with encryption at rest.
+
+## Example Usage
+
+```hcl
+resource "cloudstack_kubernetes_cluster" "basic" {
+  name               = "basic-cluster"
+  zone               = "zone1"
+  kubernetes_version = "1.25.0"
+  service_offering   = "Medium Instance"
+  size               = 3
+  description        = "Basic Kubernetes cluster"
+}
+
+data "cloudstack_kubernetes_cluster_config" "basic" {
+  cluster_id = cloudstack_kubernetes_cluster.basic.id
+}
+```
+
+### Configuring the Kubernetes and Helm providers
+
+```hcl
+provider "kubernetes" {
+  host                   = 
data.cloudstack_kubernetes_cluster_config.basic.endpoint
+  cluster_ca_certificate = 
data.cloudstack_kubernetes_cluster_config.basic.cluster_ca_certificate
+  client_certificate     = 
data.cloudstack_kubernetes_cluster_config.basic.client_certificate
+  client_key             = 
data.cloudstack_kubernetes_cluster_config.basic.client_key
+}
+
+provider "helm" {
+  kubernetes {
+    host                   = 
data.cloudstack_kubernetes_cluster_config.basic.endpoint
+    cluster_ca_certificate = 
data.cloudstack_kubernetes_cluster_config.basic.cluster_ca_certificate
+    client_certificate     = 
data.cloudstack_kubernetes_cluster_config.basic.client_certificate
+    client_key             = 
data.cloudstack_kubernetes_cluster_config.basic.client_key
+  }
+}
+```
+
+### Writing the kubeconfig to a file
+
+```hcl
+resource "local_sensitive_file" "kubeconfig" {
+  filename = "${path.module}/kubeconfig"
+  content  = data.cloudstack_kubernetes_cluster_config.basic.config_data
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `cluster_id` - (Required) The ID of the Kubernetes cluster to retrieve the 
config for.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the Kubernetes cluster.
+* `name` - The name of the Kubernetes cluster.
+* `config_data` - The raw kubeconfig of the Kubernetes cluster.
+* `endpoint` - The URL of the Kubernetes API server.
+* `cluster_ca_certificate` - The PEM encoded certificate authority of the 
Kubernetes API server.
+* `client_certificate` - The PEM encoded client certificate used to 
authenticate against the
+  Kubernetes API server.
+* `client_key` - The PEM encoded client key used to authenticate against the 
Kubernetes API server.
+
+The `endpoint`, `cluster_ca_certificate`, `client_certificate` and 
`client_key` attributes are
+extracted from `config_data`, resolved through the kubeconfig's current 
context, and base64 decoded
+where applicable. If a kubeconfig does not carry one of them, the 
corresponding attribute is empty
+and `config_data` can be used directly instead:
+
+```hcl
+locals {
+  kubeconfig = 
yamldecode(data.cloudstack_kubernetes_cluster_config.basic.config_data)
+}
+```

Reply via email to