mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-07-03 08:42:39 +00:00
Add support for master authorized networks in google_container_cluster
(#626)
* Add support for master authorized networks in `google_container_cluster` * [review] remove enabled flag / restructure schema - remove `google_container_cluster.master_authorized_networks_config.enabled` - add `display_name` and restructure schema as follows: master_authorized_networks_config { cidr_blocks { cidr_block = "0.0.0.0/0" display_name = "foo" } } - amend tests * [review] add test for validateRFC1918Network, fix acc test
This commit is contained in:
parent
8cf605f23e
commit
ca7551c8c5
|
@ -3,7 +3,6 @@ package google
|
|||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -134,20 +133,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(8, 32),
|
||||
},
|
||||
|
||||
"description": {
|
||||
|
@ -222,6 +212,35 @@ func resourceContainerCluster() *schema.Resource {
|
|||
},
|
||||
},
|
||||
|
||||
"master_authorized_networks_config": {
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"cidr_blocks": {
|
||||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
MaxItems: 10,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"cidr_block": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ValidateFunc: validation.CIDRNetwork(0, 32),
|
||||
},
|
||||
"display_name": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"min_master_version": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -317,6 +336,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)
|
||||
}
|
||||
|
@ -482,6 +505,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)
|
||||
|
@ -526,6 +553,29 @@ func resourceContainerClusterUpdate(d *schema.ResourceData, meta interface{}) er
|
|||
|
||||
d.Partial(true)
|
||||
|
||||
if d.HasChange("master_authorized_networks_config") {
|
||||
c := d.Get("master_authorized_networks_config")
|
||||
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)
|
||||
|
@ -816,6 +866,26 @@ func expandClusterAddonsConfig(configured interface{}) *container.AddonsConfig {
|
|||
return ac
|
||||
}
|
||||
|
||||
func expandMasterAuthorizedNetworksConfig(configured interface{}) *container.MasterAuthorizedNetworksConfig {
|
||||
result := &container.MasterAuthorizedNetworksConfig{}
|
||||
if len(configured.([]interface{})) > 0 {
|
||||
result.Enabled = true
|
||||
config := configured.([]interface{})[0].(map[string]interface{})
|
||||
if _, ok := config["cidr_blocks"]; ok {
|
||||
cidrBlocks := config["cidr_blocks"].(*schema.Set).List()
|
||||
result.CidrBlocks = make([]*container.CidrBlock, 0)
|
||||
for _, v := range cidrBlocks {
|
||||
cidrBlock := v.(map[string]interface{})
|
||||
result.CidrBlocks = append(result.CidrBlocks, &container.CidrBlock{
|
||||
CidrBlock: cidrBlock["cidr_block"].(string),
|
||||
DisplayName: cidrBlock["display_name"].(string),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func flattenClusterAddonsConfig(c *container.AddonsConfig) []map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
if c.HorizontalPodAutoscaling != nil {
|
||||
|
@ -856,6 +926,21 @@ func flattenClusterNodePools(d *schema.ResourceData, config *Config, c []*contai
|
|||
return nodePools, nil
|
||||
}
|
||||
|
||||
func flattenMasterAuthorizedNetworksConfig(c *container.MasterAuthorizedNetworksConfig) []map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
if c.Enabled && len(c.CidrBlocks) > 0 {
|
||||
cidrBlocks := make([]map[string]interface{}, 0, len(c.CidrBlocks))
|
||||
for _, v := range c.CidrBlocks {
|
||||
cidrBlocks = append(cidrBlocks, map[string]interface{}{
|
||||
"cidr_block": v.CidrBlock,
|
||||
"display_name": v.DisplayName,
|
||||
})
|
||||
}
|
||||
result["cidr_blocks"] = cidrBlocks
|
||||
}
|
||||
return []map[string]interface{}{result}
|
||||
}
|
||||
|
||||
func resourceContainerClusterStateImporter(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
|
||||
parts := strings.Split(d.Id(), "/")
|
||||
if len(parts) != 2 {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package google
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
@ -106,6 +107,44 @@ 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, []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.cidr_blocks.#", "1"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName, []string{}),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckContainerCluster("google_container_cluster.with_master_authorized_networks"),
|
||||
resource.TestCheckNoResourceAttr("google_container_cluster.with_master_authorized_networks",
|
||||
"master_authorized_networks_config.0.cidr_blocks"),
|
||||
),
|
||||
},
|
||||
{
|
||||
Config: testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName, []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.cidr_blocks.#", "1"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccContainerCluster_withAdditionalZones(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -882,6 +921,32 @@ resource "google_container_cluster" "with_master_auth" {
|
|||
}
|
||||
}`, acctest.RandString(10))
|
||||
|
||||
func testAccContainerCluster_withMasterAuthorizedNetworksConfig(clusterName string, cidrs []string) string {
|
||||
|
||||
cidrBlocks := ""
|
||||
if len(cidrs) > 0 {
|
||||
var buf bytes.Buffer
|
||||
for _, c := range cidrs {
|
||||
buf.WriteString(fmt.Sprintf(`
|
||||
cidr_blocks {
|
||||
cidr_block = "%s"
|
||||
}`, c))
|
||||
}
|
||||
cidrBlocks = buf.String()
|
||||
}
|
||||
|
||||
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 {
|
||||
%s
|
||||
}
|
||||
}`, clusterName, cidrBlocks)
|
||||
}
|
||||
|
||||
func testAccContainerCluster_withAdditionalZones(clusterName string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_container_cluster" "with_additional_zones" {
|
||||
|
|
|
@ -3,6 +3,8 @@ package google
|
|||
import (
|
||||
"fmt"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
"net"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,12 +28,45 @@ func TestValidateGCPName(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateRFC1918Network(t *testing.T) {
|
||||
x := []RFC1918NetworkTestCase{
|
||||
// No errors
|
||||
{TestName: "valid 10.x", CIDR: "10.0.0.0/8", MinPrefix: 0, MaxPrefix: 32},
|
||||
{TestName: "valid 172.x", CIDR: "172.16.0.0/16", MinPrefix: 0, MaxPrefix: 32},
|
||||
{TestName: "valid 192.x", CIDR: "192.168.0.0/32", MinPrefix: 0, MaxPrefix: 32},
|
||||
{TestName: "valid, bounded 10.x CIDR", CIDR: "10.0.0.0/8", MinPrefix: 8, MaxPrefix: 32},
|
||||
{TestName: "valid, bounded 172.x CIDR", CIDR: "172.16.0.0/16", MinPrefix: 12, MaxPrefix: 32},
|
||||
{TestName: "valid, bounded 192.x CIDR", CIDR: "192.168.0.0/32", MinPrefix: 16, MaxPrefix: 32},
|
||||
|
||||
// With errors
|
||||
{TestName: "empty CIDR", CIDR: "", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
|
||||
{TestName: "missing mask", CIDR: "10.0.0.0", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
|
||||
{TestName: "invalid CIDR", CIDR: "10.1.0.0/8", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
|
||||
{TestName: "valid 10.x CIDR with lower bound violation", CIDR: "10.0.0.0/8", MinPrefix: 16, MaxPrefix: 32, ExpectError: true},
|
||||
{TestName: "valid 10.x CIDR with upper bound violation", CIDR: "10.0.0.0/24", MinPrefix: 8, MaxPrefix: 16, ExpectError: true},
|
||||
{TestName: "valid public CIDR", CIDR: "8.8.8.8/32", MinPrefix: 0, MaxPrefix: 32, ExpectError: true},
|
||||
}
|
||||
|
||||
es := testRFC1918Networks(x)
|
||||
if len(es) > 0 {
|
||||
t.Errorf("Failed to validate RFC1918 Networks: %v", es)
|
||||
}
|
||||
}
|
||||
|
||||
type GCPNameTestCase struct {
|
||||
TestName string
|
||||
Value string
|
||||
ExpectError bool
|
||||
}
|
||||
|
||||
type RFC1918NetworkTestCase struct {
|
||||
TestName string
|
||||
CIDR string
|
||||
MinPrefix int
|
||||
MaxPrefix int
|
||||
ExpectError bool
|
||||
}
|
||||
|
||||
func testGCPNames(cases []GCPNameTestCase) []error {
|
||||
es := make([]error, 0)
|
||||
for _, c := range cases {
|
||||
|
@ -55,3 +88,26 @@ func testGCPName(testCase GCPNameTestCase) []error {
|
|||
|
||||
return es
|
||||
}
|
||||
|
||||
func testRFC1918Networks(cases []RFC1918NetworkTestCase) []error {
|
||||
es := make([]error, 0)
|
||||
for _, c := range cases {
|
||||
es = append(es, testRFC1918Network(c)...)
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
||||
|
||||
func testRFC1918Network(testCase RFC1918NetworkTestCase) []error {
|
||||
f := validateRFC1918Network(testCase.MinPrefix, testCase.MaxPrefix)
|
||||
_, es := f(testCase.CIDR, testCase.TestName)
|
||||
if testCase.ExpectError {
|
||||
if len(es) > 0 {
|
||||
return nil
|
||||
}
|
||||
return []error{fmt.Errorf("Didn't see expected error in case \"%s\" with CIDR=\"%s\" MinPrefix=%v MaxPrefix=%v",
|
||||
testCase.TestName, testCase.CIDR, testCase.MinPrefix, testCase.MaxPrefix)}
|
||||
}
|
||||
|
||||
return es
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
}
|
||||
}
|
||||
|
@ -105,6 +105,10 @@ output "cluster_ca_certificate" {
|
|||
* `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. Omit the nested `cidr_blocks` attribute to disallow
|
||||
external access (except the cluster node IPs, which GKE automatically whitelists).
|
||||
|
||||
* `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.
|
||||
|
@ -171,6 +175,18 @@ 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:
|
||||
|
||||
* `cidr_blocks` - (Optional) Defines up to 10 external networks that can access
|
||||
Kubernetes master through HTTPS.
|
||||
|
||||
The `master_authorized_networks_config.cidr_blocks` block supports:
|
||||
|
||||
* `cidr_block` - (Optional) External network that can access Kubernetes master through HTTPS.
|
||||
Must be specified in CIDR notation.
|
||||
|
||||
* `display_name` - (Optional) Field for users to identify CIDR blocks.
|
||||
|
||||
The `node_config` block supports:
|
||||
|
||||
* `disk_size_gb` - (Optional) Size of the disk attached to each node, specified
|
||||
|
@ -214,7 +230,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
|
||||
|
|
Loading…
Reference in New Issue
Block a user