Skip to content

Commit

Permalink
Add support for master authorized networks in google_container_cluster
Browse files Browse the repository at this point in the history
  • Loading branch information
davidquarles committed Oct 30, 2017
1 parent c3b5498 commit 17745c1
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 5 deletions.
88 changes: 87 additions & 1 deletion google/resource_container_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package google
import (
"fmt"
"log"
"net"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -215,6 +214,30 @@ func resourceContainerCluster() *schema.Resource {
},
},

"master_authorized_networks_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"enabled": {
Type: schema.TypeBool,
Required: true,
},
"cidr_blocks": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
MaxItems: 10,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.CIDRNetwork(0, 32),
},
},
},
},
},

"min_master_version": {
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -310,6 +333,10 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er
}
}

if v, ok := d.GetOk("master_authorized_networks_config"); ok {
cluster.MasterAuthorizedNetworksConfig = expandMasterAuthorizedNetworksConfig(v)
}

if v, ok := d.GetOk("min_master_version"); ok {
cluster.InitialClusterVersion = v.(string)
}
Expand Down Expand Up @@ -471,6 +498,10 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro
}
d.Set("master_auth", masterAuth)

if cluster.MasterAuthorizedNetworksConfig != nil {
d.Set("master_authorized_networks_config", flattenMasterAuthorizedNetworksConfig(cluster.MasterAuthorizedNetworksConfig))
}

d.Set("initial_node_count", cluster.InitialNodeCount)
d.Set("master_version", cluster.CurrentMasterVersion)
d.Set("node_version", cluster.CurrentNodeVersion)
Expand Down Expand Up @@ -514,6 +545,30 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er

d.Partial(true)

if d.HasChange("master_authorized_networks_config") {
if c, ok := d.GetOk("master_authorized_networks_config"); ok {
req := &container.UpdateClusterRequest{
Update: &container.ClusterUpdate{
DesiredMasterAuthorizedNetworksConfig: expandMasterAuthorizedNetworksConfig(c),
},
}
op, err := config.clientContainer.Projects.Zones.Clusters.Update(
project, zoneName, clusterName, req).Do()
if err != nil {
return err
}

// Wait until it's updated
waitErr := containerOperationWait(config, op, project, zoneName, "updating GKE cluster master authorized networks", timeoutInMinutes, 2)
if waitErr != nil {
return waitErr
}
log.Printf("[INFO] GKE cluster %s master authorized networks config has been updated", d.Id())

d.SetPartial("master_authorized_networks_config")
}
}

// The master must be updated before the nodes
if d.HasChange("min_master_version") {
desiredMasterVersion := d.Get("min_master_version").(string)
Expand Down Expand Up @@ -804,6 +859,23 @@ func expandClusterAddonsConfig(configured interface{}) *container.AddonsConfig {
return ac
}

func expandMasterAuthorizedNetworksConfig(configured interface{}) *container.MasterAuthorizedNetworksConfig {
config := configured.([]interface{})[0].(map[string]interface{})
cidrBlocks := config["cidr_blocks"].(*schema.Set).List()
result := &container.MasterAuthorizedNetworksConfig{
Enabled: config["enabled"].(bool),
CidrBlocks: make([]*container.CidrBlock, 0),
}
if result.Enabled {
for _, v := range cidrBlocks {
result.CidrBlocks = append(result.CidrBlocks, &container.CidrBlock{
CidrBlock: v.(string),
})
}
}
return result
}

func flattenClusterAddonsConfig(c *container.AddonsConfig) []map[string]interface{} {
result := make(map[string]interface{})
if c.HorizontalPodAutoscaling != nil {
Expand Down Expand Up @@ -844,6 +916,20 @@ func flattenClusterNodePools(d *schema.ResourceData, config *Config, c []*contai
return nodePools, nil
}

func flattenMasterAuthorizedNetworksConfig(c *container.MasterAuthorizedNetworksConfig) []map[string]interface{} {
cidrBlocks := make([]string, 0, len(c.CidrBlocks))
for _, v := range c.CidrBlocks {
cidrBlocks = append(cidrBlocks, v.CidrBlock)
}
result := []map[string]interface{}{
map[string]interface{}{
"enabled": c.Enabled,
"cidr_blocks": cidrBlocks,
},
}
return result
}

func resourceContainerClusterStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
Expand Down
70 changes: 70 additions & 0 deletions google/resource_container_cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,50 @@ func TestAccContainerCluster_withMasterAuth(t *testing.T) {
})
}

func TestAccContainerCluster_withMasterAuthorizedNetworksConfig(t *testing.T) {
t.Parallel()

clusterName := fmt.Sprintf("cluster-test-%s", acctest.RandString(10))

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckContainerClusterDestroy,
Steps: []resource.TestStep{
{
Config: testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName, true, []string{"0.0.0.0/0"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckContainerCluster("google_container_cluster.with_master_authorized_networks"),
resource.TestCheckResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.enabled", "true"),
resource.TestCheckResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.cidr_blocks.#", "1"),
),
},
{
Config: testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName, true, []string{}),
Check: resource.ComposeTestCheckFunc(
testAccCheckContainerCluster("google_container_cluster.with_master_authorized_networks"),
resource.TestCheckResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.enabled", "true"),
resource.TestCheckNoResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.cidr_blocks"),
),
},
{
Config: testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName, false, []string{"8.8.8.8/32"}),
Check: resource.ComposeTestCheckFunc(
testAccCheckContainerCluster("google_container_cluster.with_master_authorized_networks"),
resource.TestCheckResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.enabled", "false"),
resource.TestCheckNoResourceAttr("google_container_cluster.with_master_authorized_networks",
"master_authorized_networks_config.0.cidr_blocks"),
),
},
},
})
}

func TestAccContainerCluster_withAdditionalZones(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -656,6 +700,13 @@ func testAccCheckContainerCluster(n string) resource.TestCheckFunc {
}
}

masterAuthorizedNetworksEnabled := false
if cluster.MasterAuthorizedNetworksConfig != nil {
masterAuthorizedNetworksEnabled = cluster.MasterAuthorizedNetworksConfig.Enabled
clusterTests = append(clusterTests,
clusterTestField{"master_authorized_networks_config.0.enabled", masterAuthorizedNetworksEnabled})
}

for _, attrs := range clusterTests {
if c := checkMatch(attributes, attrs.tf_attr, attrs.gcp_attr); c != "" {
return fmt.Errorf(c)
Expand Down Expand Up @@ -859,6 +910,25 @@ resource "google_container_cluster" "with_master_auth" {
}
}`, acctest.RandString(10))

func testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName string, enabled bool, cidrBlocks []string) string {

for i, cidr := range cidrBlocks {
cidrBlocks[i] = strconv.Quote(cidr)
}

return fmt.Sprintf(`
resource "google_container_cluster" "with_master_authorized_networks" {
name = "%s"
zone = "us-central1-a"
initial_node_count = 1
master_authorized_networks_config {
enabled = %v
cidr_blocks = [%s]
}
}`, clusterName, enabled, strings.Join(cidrBlocks, ","))
}

func testAccContainerCluster_withAdditionalZones(clusterName string) string {
return fmt.Sprintf(`
resource "google_container_cluster" "with_additional_zones" {
Expand Down
30 changes: 30 additions & 0 deletions google/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package google
import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"net"
"regexp"
)

Expand All @@ -15,6 +17,12 @@ const (
SubnetworkLinkRegex = "projects/(" + ProjectRegex + ")/regions/(" + RegionRegex + ")/subnetworks/(" + SubnetworkRegex + ")$"
)

var rfc1918Networks = []string{
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
}

func validateGCPName(v interface{}, k string) (ws []string, errors []error) {
re := `^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$`
return validateRegexp(re)(v, k)
Expand All @@ -31,3 +39,25 @@ func validateRegexp(re string) schema.SchemaValidateFunc {
return
}
}

func validateRFC1918Network(min, max int) schema.SchemaValidateFunc {
return func(i interface{}, k string) (s []string, es []error) {

s, es = validation.CIDRNetwork(min, max)(i, k)
if len(es) > 0 {
return
}

v, _ := i.(string)
ip, _, _ := net.ParseCIDR(v)
for _, c := range rfc1918Networks {
if _, ipnet, _ := net.ParseCIDR(c); ipnet.Contains(ip) {
return
}
}

es = append(es, fmt.Errorf("expected %q to be an RFC1918-compliant CIDR, got: %s", k, v))

return
}
}
22 changes: 18 additions & 4 deletions website/docs/r/container_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ resource "google_container_cluster" "primary" {
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring",
]
labels {
foo = "bar"
}
tags = ["foo", "bar"]
}
}
Expand Down Expand Up @@ -88,6 +88,9 @@ resource "google_container_cluster" "primary" {
* `master_auth` - (Optional) The authentication information for accessing the
Kubernetes master. Structure is documented below.

* `master_authorized_networks_config` - (Optional) The desired configuration options
for master authorized networks

* `min_master_version` - (Optional) The minimum version of the master. GKE
will auto-update the master to new versions, so this does not guarantee the
current master version--use the read-only `master_version` field to obtain that.
Expand Down Expand Up @@ -154,8 +157,19 @@ The `master_auth` block supports:
* `username` - (Required) The username to use for HTTP basic authentication when accessing
the Kubernetes master endpoint

The `master_authorized_networks_config` block supports:

* `enabled` - (Required) Whether or not master authorized networks is enabled

* `cidr_blocks` - (Optional) Defines up to 10 external networks that can access
Kubernetes master through HTTPS. To avoid upstream failures, an empty list is
passed when this feature is disabled, irrespective of values passed here.

The `node_config` block supports:

* `machine_type` - (Optional) The name of a Google Compute Engine machine type.
Defaults to `n1-standard-1`.

* `disk_size_gb` - (Optional) Size of the disk attached to each node, specified
in GB. The smallest allowed disk size is 10GB. Defaults to 100GB.

Expand Down Expand Up @@ -197,7 +211,7 @@ The `node_config` block supports:
* `service_account` - (Optional) The service account to be used by the Node VMs.
If not specified, the "default" service account is used.

* `tags` - (Optional) The list of instance tags applied to all nodes. Tags are used to identify
* `tags` - (Optional) The list of instance tags applied to all nodes. Tags are used to identify
valid sources or targets for network firewalls.

## Attributes Reference
Expand Down Expand Up @@ -239,4 +253,4 @@ Container clusters can be imported using the `zone`, and `name`, e.g.

```
$ terraform import google_container_cluster.mycluster us-east1-a/my-cluster
```
```

0 comments on commit 17745c1

Please sign in to comment.