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 24, 2017
1 parent d0a76e5 commit b09279c
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 15 deletions.
108 changes: 93 additions & 15 deletions 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 @@ -76,6 +75,31 @@ 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,
Optional: true,
Computed: true,
},
"cidr_blocks": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
MaxItems: 10,
Elem: &schema.Schema{
Type: schema.TypeString,
ValidateFunc: validation.CIDRNetwork(0, 32),
},
},
},
},
},

"name": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -123,20 +147,11 @@ func resourceContainerCluster() *schema.Resource {
},

"cluster_ipv4_cidr": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
value := v.(string)
_, ipnet, err := net.ParseCIDR(value)

if err != nil || ipnet == nil || value != ipnet.String() {
errors = append(errors, fmt.Errorf(
"%q must contain a valid CIDR", k))
}
return
},
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validateRFC1918Network(11, 19),
},

"description": {
Expand Down Expand Up @@ -304,6 +319,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 @@ -465,6 +484,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 @@ -508,6 +531,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 @@ -798,6 +845,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 @@ -838,6 +902,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 @@ -653,6 +697,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 @@ -856,6 +907,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
}
}
11 changes: 11 additions & 0 deletions website/docs/r/container_cluster.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ resource "google_container_cluster" "primary" {
* `master_auth` - (Optional) The authentication information for accessing the
Kubernetes master.

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

* `additional_zones` - (Optional) If additional zones are configured, the number
of nodes specified in `initial_node_count` is created in all specified zones.

Expand Down Expand Up @@ -119,6 +122,14 @@ which the cluster's instances are launched
* `username` - (Required) The username to use for HTTP basic authentication when accessing
the Kubernetes master endpoint

**Master Authorized Networks Config** supports the following arguments:

* `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.

**Node Config** supports the following arguments:

* `machine_type` - (Optional) The name of a Google Compute Engine machine type.
Expand Down

0 comments on commit b09279c

Please sign in to comment.