From 0f4a9edc4afb9bc347ef21de0c1fa794fd4d6e98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wilson=20J=C3=BAnior?= Date: Thu, 20 May 2021 10:29:07 -0300 Subject: [PATCH] tsuru_cluster: Add support to set kubeConfig object --- go.mod | 2 +- go.sum | 8 + tsuru/resource_tsuru_cluster.go | 298 ++++++++++++++++++++++++++- tsuru/resource_tsuru_cluster_test.go | 146 +++++++++++++ 4 files changed, 452 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f3976d1..6d46665 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.0 github.com/stretchr/testify v1.7.0 - github.com/tsuru/go-tsuruclient v0.0.0-20210426181646-b7774d33597a + github.com/tsuru/go-tsuruclient v0.0.0-20210520132303-680a5dd3035f github.com/ulikunitz/xz v0.5.10 // indirect golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/mod v0.4.2 // indirect diff --git a/go.sum b/go.sum index c4937ba..bdbd8c4 100644 --- a/go.sum +++ b/go.sum @@ -678,6 +678,14 @@ github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/A github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I= github.com/tsuru/go-tsuruclient v0.0.0-20210426181646-b7774d33597a h1:bblOOxOJvfo+MDWGTbTakKKYz2/aFi2X1tm8+ryB3D8= github.com/tsuru/go-tsuruclient v0.0.0-20210426181646-b7774d33597a/go.mod h1:wYe4ngw/l34brrJ8IZqGmhTWNTycnQsxcAPxGNSY8YY= +github.com/tsuru/go-tsuruclient v0.0.0-20210519141511-b11911caa559 h1:sO9+L74OR3da67NZ1d72gPPVaecMR32M2axz88iCscg= +github.com/tsuru/go-tsuruclient v0.0.0-20210519141511-b11911caa559/go.mod h1:wYe4ngw/l34brrJ8IZqGmhTWNTycnQsxcAPxGNSY8YY= +github.com/tsuru/go-tsuruclient v0.0.0-20210520125036-8c9b7d2fbf31 h1:pcUL6RnemDbSWAiVAHybQ7dwq/G+RJXeFY7LBVqx16E= +github.com/tsuru/go-tsuruclient v0.0.0-20210520125036-8c9b7d2fbf31/go.mod h1:wYe4ngw/l34brrJ8IZqGmhTWNTycnQsxcAPxGNSY8YY= +github.com/tsuru/go-tsuruclient v0.0.0-20210520125331-2c41cb4a0a95 h1:1KBC4CymN3L2yIaJhFlK7GA5UsKke6yjCAJSWddEs/Q= +github.com/tsuru/go-tsuruclient v0.0.0-20210520125331-2c41cb4a0a95/go.mod h1:wYe4ngw/l34brrJ8IZqGmhTWNTycnQsxcAPxGNSY8YY= +github.com/tsuru/go-tsuruclient v0.0.0-20210520132303-680a5dd3035f h1:KsRtww720oFxvfEXWNCTMrcBfzg+Ers89xeGiZr3/qU= +github.com/tsuru/go-tsuruclient v0.0.0-20210520132303-680a5dd3035f/go.mod h1:wYe4ngw/l34brrJ8IZqGmhTWNTycnQsxcAPxGNSY8YY= github.com/tsuru/tablecli v0.0.0-20180215113938-82de88f75181 h1:R1SnLQm/RZp35wa9e9nXyxy4EkEgU2v0rP4DGadrJ0g= github.com/tsuru/tablecli v0.0.0-20180215113938-82de88f75181/go.mod h1:ztYpOhW+u1k21FEqp7nZNgpWbr0dUKok5lgGCZi+1AQ= github.com/tsuru/tsuru v0.0.0-20180820205921-0e7f7f02eac5 h1:DWs/guyBAFqPQrpJeCKY+avcVOt6h/8AGXCSBIQB3N8= diff --git a/tsuru/resource_tsuru_cluster.go b/tsuru/resource_tsuru_cluster.go index 6e60113..4770699 100644 --- a/tsuru/resource_tsuru_cluster.go +++ b/tsuru/resource_tsuru_cluster.go @@ -2,6 +2,7 @@ package tsuru import ( "context" + "reflect" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -67,6 +68,12 @@ func resourceTsuruCluster() *schema.Resource { Default: false, Description: "Whether true, the cluster is the default for all pools", }, + "local": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "Whether true, the cluster is auth by the local credentials of kubernetes", + }, "initial_pools": { Type: schema.TypeList, Elem: &schema.Schema{ @@ -83,6 +90,154 @@ func resourceTsuruCluster() *schema.Resource { Computed: true, Description: "Name of pools that belongs to the cluster", }, + "kube_config": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster": kubeConfigClusterSchema(), + "user": kubeConfigUserSchema(), + }, + }, + }, + "http_proxy": { + Type: schema.TypeString, + Optional: true, + Description: "Client HTTP proxy", + }, + }, + } +} + +func kubeConfigUserSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "auth_provider": kubeConfigAuthProviderSchema(), + "exec": kubeConfigAuthExecSchema(), + "client_certificate_data": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "client_key_data": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "username": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "password": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + }, + }, + } +} + +func kubeConfigAuthProviderSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + }, + "config": { + Type: schema.TypeMap, + Optional: true, + }, + }, + }, + } +} + +func kubeConfigAuthExecSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "api_version": { + Type: schema.TypeString, + Optional: true, + }, + "command": { + Type: schema.TypeString, + Optional: true, + }, + "args": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "env": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + } +} + +func kubeConfigClusterSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "server": { + Type: schema.TypeString, + Required: true, + }, + "tls_server_name": { + Type: schema.TypeString, + Optional: true, + }, + "insecure_skip_tls_verify": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "certificate_authority_data": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + }, }, } } @@ -127,7 +282,11 @@ func resourceTsuruClusterRead(ctx context.Context, d *schema.ResourceData, meta d.Set("ca_cert", string(cluster.Cacert)) d.Set("client_key", string(cluster.Clientkey)) d.Set("client_cert", string(cluster.Clientcert)) + d.Set("default", cluster.Default) + d.Set("local", cluster.Local) d.Set("pools", cluster.Pools) + d.Set("http_proxy", cluster.HttpProxy) + d.Set("kube_config", flattenKubeConfig(cluster.KubeConfig)) return nil } @@ -174,6 +333,74 @@ func clusterFromResourceData(d *schema.ResourceData) tsuru.Cluster { customData[key] = value.(string) } + var kubeConfig tsuru.ClusterKubeConfig + + if data, ok := d.GetOk("kube_config"); ok { + dataArray := data.([]interface{}) + kubeConfigRaw := dataArray[0].(map[string]interface{}) + + dataArray = kubeConfigRaw["cluster"].([]interface{}) + serverRaw, _ := dataArray[0].(map[string]interface{}) + + dataArray = kubeConfigRaw["user"].([]interface{}) + userRaw, _ := dataArray[0].(map[string]interface{}) + + dataArray = userRaw["auth_provider"].([]interface{}) + authProviderRaw, _ := dataArray[0].(map[string]interface{}) + + kubeConfig.Cluster.Server = serverRaw["server"].(string) + kubeConfig.Cluster.CertificateAuthorityData = serverRaw["certificate_authority_data"].(string) + kubeConfig.Cluster.TlsServerName = serverRaw["tls_server_name"].(string) + kubeConfig.Cluster.InsecureSkipTlsVerify = serverRaw["insecure_skip_tls_verify"].(bool) + + kubeConfig.User.AuthProvider.Name = authProviderRaw["name"].(string) + kubeConfig.User.AuthProvider.Config = map[string]string{} + + configRaw, _ := authProviderRaw["config"].(map[string]interface{}) + + for key, item := range configRaw { + kubeConfig.User.AuthProvider.Config[key] = item.(string) + } + + kubeConfig.User.ClientCertificateData = userRaw["client_certificate_data"].(string) + kubeConfig.User.ClientKeyData = userRaw["client_key_data"].(string) + kubeConfig.User.Token = userRaw["token"].(string) + kubeConfig.User.Username = userRaw["username"].(string) + kubeConfig.User.Password = userRaw["password"].(string) + + dataArray = userRaw["exec"].([]interface{}) + execRaw, _ := dataArray[0].(map[string]interface{}) + + kubeConfig.User.Exec.ApiVersion = execRaw["api_version"].(string) + kubeConfig.User.Exec.Command = execRaw["command"].(string) + kubeConfig.User.Exec.Args = []string{} + kubeConfig.User.Exec.Env = []tsuru.ClusterKubeConfigUserExecEnv{} + + for _, arg := range execRaw["args"].([]interface{}) { + kubeConfig.User.Exec.Args = append(kubeConfig.User.Exec.Args, arg.(string)) + } + + for _, arg := range execRaw["env"].([]interface{}) { + + m := arg.(map[string]interface{}) + kubeConfig.User.Exec.Env = append(kubeConfig.User.Exec.Env, tsuru.ClusterKubeConfigUserExecEnv{ + Name: m["name"].(string), + Value: m["value"].(string), + }) + } + + } + + clusterDefault := false + if value, ok := d.GetOk("default"); ok { + clusterDefault = value.(bool) + } + + clusterLocal := false + if value, ok := d.GetOk("local"); ok { + clusterLocal = value.(bool) + } + return tsuru.Cluster{ Name: d.Get("name").(string), Addresses: addresses, @@ -182,6 +409,75 @@ func clusterFromResourceData(d *schema.ResourceData) tsuru.Cluster { Clientcert: []byte(d.Get("client_cert").(string)), Clientkey: []byte(d.Get("client_key").(string)), CustomData: customData, - Default: d.Get("default").(bool), + Default: clusterDefault, + Local: clusterLocal, + KubeConfig: kubeConfig, + HttpProxy: d.Get("http_proxy").(string), } } + +func flattenKubeConfig(kubeconfig tsuru.ClusterKubeConfig) []interface{} { + if reflect.DeepEqual(kubeconfig, tsuru.ClusterKubeConfig{}) { + return []interface{}{} + } + + result := map[string]interface{}{ + "cluster": flattenKubeConfigCluster(kubeconfig.Cluster), + "user": flattenKubeConfigUser(kubeconfig.User), + } + return []interface{}{result} +} + +func flattenKubeConfigCluster(cluster tsuru.ClusterKubeConfigCluster) []interface{} { + result := map[string]interface{}{ + "server": cluster.Server, + "certificate_authority_data": cluster.CertificateAuthorityData, + "tls_server_name": cluster.TlsServerName, + "insecure_skip_tls_verify": cluster.InsecureSkipTlsVerify, + } + return []interface{}{result} +} + +func flattenKubeConfigUser(user tsuru.ClusterKubeConfigUser) []interface{} { + result := map[string]interface{}{ + "client_certificate_data": user.ClientCertificateData, + "client_key_data": user.ClientKeyData, + "username": user.Username, + "password": user.Password, + "token": user.Token, + "auth_provider": flattenAuthProvider(user.AuthProvider), + "exec": flattenExec(user.Exec), + } + return []interface{}{result} +} + +func flattenAuthProvider(authProvider tsuru.ClusterKubeConfigUserAuthprovider) []interface{} { + result := map[string]interface{}{ + "name": authProvider.Name, + "config": authProvider.Config, + } + return []interface{}{result} +} + +func flattenExec(exec tsuru.ClusterKubeConfigUserExec) []interface{} { + result := map[string]interface{}{ + "args": exec.Args, + "api_version": exec.ApiVersion, + "command": exec.Command, + "env": flattenEnvs(exec.Env), + } + return []interface{}{result} +} + +func flattenEnvs(envs []tsuru.ClusterKubeConfigUserExecEnv) []interface{} { + result := []interface{}{} + + for _, item := range envs { + result = append(result, map[string]string{ + "name": item.Name, + "value": item.Value, + }) + } + + return result +} diff --git a/tsuru/resource_tsuru_cluster_test.go b/tsuru/resource_tsuru_cluster_test.go index d333915..a8b9d2f 100644 --- a/tsuru/resource_tsuru_cluster_test.go +++ b/tsuru/resource_tsuru_cluster_test.go @@ -86,3 +86,149 @@ resource "tsuru_cluster" "test_cluster" { } `, name) } + +func TestAccTsuruCluster_kubeConfig(t *testing.T) { + expectedKubeConfig := tsuru.ClusterKubeConfig{ + Cluster: tsuru.ClusterKubeConfigCluster{ + Server: "https://mycluster.local", + CertificateAuthorityData: "server-cert", + TlsServerName: "mycluster.local", + InsecureSkipTlsVerify: true, + }, + User: tsuru.ClusterKubeConfigUser{ + AuthProvider: tsuru.ClusterKubeConfigUserAuthprovider{ + Name: "tsuru", + Config: map[string]string{ + "tsuru-flag-01": "result", + }, + }, + Token: "token", + Username: "username", + Password: "password", + ClientCertificateData: "client-cert", + ClientKeyData: "client-key", + Exec: tsuru.ClusterKubeConfigUserExec{ + ApiVersion: "api-version", + Command: "tsuru", + Args: []string{"cluster", "login"}, + Env: []tsuru.ClusterKubeConfigUserExecEnv{ + { + Name: "TSURU_TOKEN", + Value: "token", + }, + }, + }, + }, + } + + fakeServer := echo.New() + fakeServer.POST("/1.3/provisioner/clusters", func(c echo.Context) error { + p := &tsuru.Cluster{} + err := c.Bind(&p) + require.NoError(t, err) + assert.Equal(t, "test_cluster", p.Name) + assert.Nil(t, p.CustomData) + assert.True(t, p.Default) + assert.Equal(t, expectedKubeConfig, p.KubeConfig) + assert.Equal(t, "http://myproxy.io:3128", p.HttpProxy) + + return nil + }) + fakeServer.GET("/1.8/provisioner/clusters/:name", func(c echo.Context) error { + name := c.Param("name") + return c.JSON(http.StatusOK, &tsuru.Cluster{ + Name: name, + Provisioner: "kubernetes", + CustomData: map[string]string{ + "token": "test_token", + }, + Default: true, + KubeConfig: expectedKubeConfig, + HttpProxy: "http://myproxy.io:3128", + }) + }) + fakeServer.DELETE("/1.3/provisioner/clusters/:name", func(c echo.Context) error { + name := c.Param("name") + require.Equal(t, name, "test_cluster") + return c.NoContent(http.StatusNoContent) + }) + fakeServer.HTTPErrorHandler = func(err error, c echo.Context) { + t.Errorf("methods=%s, path=%s, err=%s", c.Request().Method, c.Path(), err.Error()) + } + server := httptest.NewServer(fakeServer) + os.Setenv("TSURU_TARGET", server.URL) + + resourceName := "tsuru_cluster.test_cluster" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + IDRefreshName: resourceName, + ProviderFactories: testAccProviderFactories, + CheckDestroy: nil, + Steps: []resource.TestStep{ + { + Config: testAccTsuruClusterConfig_kubeConfig("test_cluster"), + + Check: resource.ComposeAggregateTestCheckFunc( + testAccResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "kube_config.0.cluster.0.server", "https://mycluster.local"), + ), + }, + { + ImportState: true, + ResourceName: resourceName, + ImportStateVerify: true, + Check: resource.ComposeAggregateTestCheckFunc( + testAccResourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "kube_config.0.cluster.0.server", "https://mycluster.local"), + ), + }, + }, + }) +} + +func testAccTsuruClusterConfig_kubeConfig(name string) string { + return fmt.Sprintf(` +resource "tsuru_cluster" "test_cluster" { + name = "%s" + tsuru_provisioner = "kubernetes" + default = true + + http_proxy = "http://myproxy.io:3128" + + kube_config { + cluster { + server = "https://mycluster.local" + tls_server_name = "mycluster.local" + insecure_skip_tls_verify = true + certificate_authority_data = "server-cert" + } + user { + auth_provider { + name = "tsuru" + config = { + "tsuru-flag-01" = "result" + } + } + + client_certificate_data = "client-cert" + client_key_data = "client-key" + + token = "token" + username = "username" + password = "password" + + exec { + api_version = "api-version" + command = "tsuru" + args = ["cluster", "login"] + + env { + name = "TSURU_TOKEN" + value = "token" + } + } + } + } +} +`, name) +}