diff --git a/digitalocean/kubernetes/kubernetes.go b/digitalocean/kubernetes/kubernetes.go index e15e3c322..94fa694a8 100644 --- a/digitalocean/kubernetes/kubernetes.go +++ b/digitalocean/kubernetes/kubernetes.go @@ -240,6 +240,29 @@ func expandMaintPolicyOpts(config []interface{}) (*godo.KubernetesMaintenancePol return maintPolicy, nil } +func expandControlPlaneFirewallOpts(raw []interface{}) *godo.KubernetesControlPlaneFirewall { + if len(raw) == 0 || raw[0] == nil { + return &godo.KubernetesControlPlaneFirewall{} + } + controlPlaneFirewallConfig := raw[0].(map[string]interface{}) + + controlPlaneFirewall := &godo.KubernetesControlPlaneFirewall{ + Enabled: godo.PtrTo(controlPlaneFirewallConfig["enabled"].(bool)), + AllowedAddresses: expandAllowedAddresses(controlPlaneFirewallConfig["allowed_addresses"].([]interface{})), + } + return controlPlaneFirewall +} + +func expandAllowedAddresses(addrs []interface{}) []string { + var expandedAddrs []string + for _, item := range addrs { + if str, ok := item.(string); ok { + expandedAddrs = append(expandedAddrs, str) + } + } + return expandedAddrs +} + func flattenMaintPolicyOpts(opts *godo.KubernetesMaintenancePolicy) []map[string]interface{} { result := make([]map[string]interface{}, 0) item := make(map[string]interface{}) @@ -252,6 +275,17 @@ func flattenMaintPolicyOpts(opts *godo.KubernetesMaintenancePolicy) []map[string return result } +func flattenControlPlaneFirewallOpts(opts *godo.KubernetesControlPlaneFirewall) []map[string]interface{} { + result := make([]map[string]interface{}, 0) + item := make(map[string]interface{}) + + item["enabled"] = opts.Enabled + item["allowed_addresses"] = opts.AllowedAddresses + result = append(result, item) + + return result +} + func flattenNodePool(d *schema.ResourceData, keyPrefix string, pool *godo.KubernetesNodePool, parentTags ...string) []interface{} { rawPool := map[string]interface{}{ "id": pool.ID, diff --git a/digitalocean/kubernetes/resource_kubernetes_cluster.go b/digitalocean/kubernetes/resource_kubernetes_cluster.go index 92d99310e..f5abbb43b 100644 --- a/digitalocean/kubernetes/resource_kubernetes_cluster.go +++ b/digitalocean/kubernetes/resource_kubernetes_cluster.go @@ -23,6 +23,10 @@ var ( MultipleNodePoolImportError = fmt.Errorf("Cluster contains multiple node pools. Manually add the `%s` tag to the pool that should be used as the default. Additional pools must be imported separately as 'digitalocean_kubernetes_node_pool' resources.", DigitaloceanKubernetesDefaultNodePoolTag) ) +const ( + controlPlaneFirewallField = "control_plane_firewall" +) + func ResourceDigitalOceanKubernetesCluster() *schema.Resource { return &schema.Resource{ CreateContext: resourceDigitalOceanKubernetesClusterCreate, @@ -197,6 +201,28 @@ func ResourceDigitalOceanKubernetesCluster() *schema.Resource { Optional: true, ValidateFunc: validation.IntAtLeast(0), }, + + controlPlaneFirewallField: { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "enabled": { + Type: schema.TypeBool, + Required: true, + }, + "allowed_addresses": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, }, Timeouts: &schema.ResourceTimeout{ @@ -326,6 +352,10 @@ func resourceDigitalOceanKubernetesClusterCreate(ctx context.Context, d *schema. opts.AutoUpgrade = autoUpgrade.(bool) } + if controlPlaneFirewall, ok := d.GetOk(controlPlaneFirewallField); ok { + opts.ControlPlaneFirewall = expandControlPlaneFirewallOpts(controlPlaneFirewall.([]interface{})) + } + cluster, _, err := client.Kubernetes.Create(context.Background(), opts) if err != nil { return diag.Errorf("Error creating Kubernetes cluster: %s", err) @@ -389,6 +419,10 @@ func digitaloceanKubernetesClusterRead( d.Set("auto_upgrade", cluster.AutoUpgrade) d.Set("urn", cluster.URN()) + if err := d.Set(controlPlaneFirewallField, flattenControlPlaneFirewallOpts(cluster.ControlPlaneFirewall)); err != nil { + return diag.Errorf("[DEBUG] Error setting %s - error: %#v", controlPlaneFirewallField, err) + } + if err := d.Set("maintenance_policy", flattenMaintPolicyOpts(cluster.MaintenancePolicy)); err != nil { return diag.Errorf("[DEBUG] Error setting maintenance_policy - error: %#v", err) } @@ -447,14 +481,15 @@ func resourceDigitalOceanKubernetesClusterUpdate(ctx context.Context, d *schema. client := meta.(*config.CombinedConfig).GodoClient() // Figure out the changes and then call the appropriate API methods - if d.HasChanges("name", "tags", "auto_upgrade", "surge_upgrade", "maintenance_policy", "ha") { + if d.HasChanges("name", "tags", "auto_upgrade", "surge_upgrade", "maintenance_policy", "ha", controlPlaneFirewallField) { opts := &godo.KubernetesClusterUpdateRequest{ - Name: d.Get("name").(string), - Tags: tag.ExpandTags(d.Get("tags").(*schema.Set).List()), - AutoUpgrade: godo.PtrTo(d.Get("auto_upgrade").(bool)), - SurgeUpgrade: d.Get("surge_upgrade").(bool), - HA: godo.PtrTo(d.Get("ha").(bool)), + Name: d.Get("name").(string), + Tags: tag.ExpandTags(d.Get("tags").(*schema.Set).List()), + AutoUpgrade: godo.PtrTo(d.Get("auto_upgrade").(bool)), + SurgeUpgrade: d.Get("surge_upgrade").(bool), + HA: godo.PtrTo(d.Get("ha").(bool)), + ControlPlaneFirewall: expandControlPlaneFirewallOpts(d.Get(controlPlaneFirewallField).([]interface{})), } if maint, ok := d.GetOk("maintenance_policy"); ok { diff --git a/digitalocean/kubernetes/resource_kubernetes_cluster_test.go b/digitalocean/kubernetes/resource_kubernetes_cluster_test.go index 5debbf34e..85e1362b0 100644 --- a/digitalocean/kubernetes/resource_kubernetes_cluster_test.go +++ b/digitalocean/kubernetes/resource_kubernetes_cluster_test.go @@ -365,6 +365,52 @@ func TestAccDigitalOceanKubernetesCluster_MaintenancePolicy(t *testing.T) { }) } +func TestAccDigitalOceanKubernetesCluster_ControlPlaneFirewall(t *testing.T) { + rName := acceptance.RandomTestName() + var k8s godo.KubernetesCluster + + firewall := ` + control_plane_firewall { + enabled = true + allowed_addresses = ["1.2.3.4/16"] + } +` + + firewallUpdate := ` + control_plane_firewall { + enabled = false + allowed_addresses = ["1.2.3.4/16", "5.6.7.8/16"] + } +` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t) }, + ProviderFactories: acceptance.TestAccProviderFactories, + CheckDestroy: testAccCheckDigitalOceanKubernetesClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccDigitalOceanKubernetesConfigControlPlaneFirewall(testClusterVersionLatest, rName, firewall), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDigitalOceanKubernetesClusterExists("digitalocean_kubernetes_cluster.foobar", &k8s), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "name", rName), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "control_plane_firewall.0.enabled", "true"), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "control_plane_firewall.0.allowed_addresses.0", "1.2.3.4/16"), + ), + }, + { + Config: testAccDigitalOceanKubernetesConfigControlPlaneFirewall(testClusterVersionLatest, rName, firewallUpdate), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckDigitalOceanKubernetesClusterExists("digitalocean_kubernetes_cluster.foobar", &k8s), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "name", rName), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "control_plane_firewall.0.enabled", "false"), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "control_plane_firewall.0.allowed_addresses.0", "1.2.3.4/16"), + resource.TestCheckResourceAttr("digitalocean_kubernetes_cluster.foobar", "control_plane_firewall.0.allowed_addresses.1", "5.6.7.8/16"), + ), + }, + }, + }) +} + func TestAccDigitalOceanKubernetesCluster_UpdatePoolDetails(t *testing.T) { rName := acceptance.RandomTestName() var k8s godo.KubernetesCluster @@ -840,6 +886,36 @@ resource "digitalocean_kubernetes_cluster" "foobar" { `, testClusterVersion, rName, policy) } +func testAccDigitalOceanKubernetesConfigControlPlaneFirewall(testClusterVersion string, rName string, controlPlaneFirewall string) string { + return fmt.Sprintf(`%s + +resource "digitalocean_kubernetes_cluster" "foobar" { + name = "%s" + region = "lon1" + version = data.digitalocean_kubernetes_versions.test.latest_version + surge_upgrade = true + tags = ["foo", "bar", "one"] + +%s + + node_pool { + name = "default" + size = "s-1vcpu-2gb" + node_count = 1 + tags = ["one", "two"] + labels = { + priority = "high" + } + taint { + key = "key1" + value = "val1" + effect = "PreferNoSchedule" + } + } +} +`, testClusterVersion, rName, controlPlaneFirewall) +} + func testAccDigitalOceanKubernetesConfigBasic2(testClusterVersion string, rName string) string { return fmt.Sprintf(`%s