mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-10-04 17:51:11 +00:00
Add support for maintenance window on google_container_cluster (#670)
* Add support for maintenance window on google_container_cluster (#526) * Address review comments - Set ForceNew: true on the schema element daily_maintenance_window - Correct resource name in acceptance test - Correct documentation of resource attribute maintenance_policy.0.daily_maintenance_window.0.duration
This commit is contained in:
parent
102c2127ef
commit
12060f9f3d
@ -172,6 +172,37 @@ func resourceContainerCluster() *schema.Resource {
|
|||||||
ValidateFunc: validation.StringInSlice([]string{"logging.googleapis.com", "none"}, false),
|
ValidateFunc: validation.StringInSlice([]string{"logging.googleapis.com", "none"}, false),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"maintenance_policy": {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
MaxItems: 1,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"daily_maintenance_window": {
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
MaxItems: 1,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"start_time": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
ValidateFunc: validateRFC3339Time,
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
"master_auth": {
|
"master_auth": {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
@ -327,6 +358,19 @@ func resourceContainerClusterCreate(d *schema.ResourceData, meta interface{}) er
|
|||||||
|
|
||||||
timeoutInMinutes := int(d.Timeout(schema.TimeoutCreate).Minutes())
|
timeoutInMinutes := int(d.Timeout(schema.TimeoutCreate).Minutes())
|
||||||
|
|
||||||
|
if v, ok := d.GetOk("maintenance_policy"); ok {
|
||||||
|
maintenancePolicy := v.([]interface{})[0].(map[string]interface{})
|
||||||
|
dailyMaintenanceWindow := maintenancePolicy["daily_maintenance_window"].([]interface{})[0].(map[string]interface{})
|
||||||
|
startTime := dailyMaintenanceWindow["start_time"].(string)
|
||||||
|
cluster.MaintenancePolicy = &container.MaintenancePolicy{
|
||||||
|
Window: &container.MaintenanceWindow{
|
||||||
|
DailyMaintenanceWindow: &container.DailyMaintenanceWindow{
|
||||||
|
StartTime: startTime,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if v, ok := d.GetOk("master_auth"); ok {
|
if v, ok := d.GetOk("master_auth"); ok {
|
||||||
masterAuths := v.([]interface{})
|
masterAuths := v.([]interface{})
|
||||||
masterAuth := masterAuths[0].(map[string]interface{})
|
masterAuth := masterAuths[0].(map[string]interface{})
|
||||||
@ -494,6 +538,20 @@ func resourceContainerClusterRead(d *schema.ResourceData, meta interface{}) erro
|
|||||||
|
|
||||||
d.Set("endpoint", cluster.Endpoint)
|
d.Set("endpoint", cluster.Endpoint)
|
||||||
|
|
||||||
|
if cluster.MaintenancePolicy != nil && cluster.MaintenancePolicy.Window != nil && cluster.MaintenancePolicy.Window.DailyMaintenanceWindow != nil {
|
||||||
|
maintenancePolicy := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"daily_maintenance_window": []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"start_time": cluster.MaintenancePolicy.Window.DailyMaintenanceWindow.StartTime,
|
||||||
|
"duration": cluster.MaintenancePolicy.Window.DailyMaintenanceWindow.Duration,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
d.Set("maintenance_policy", maintenancePolicy)
|
||||||
|
}
|
||||||
|
|
||||||
masterAuth := []map[string]interface{}{
|
masterAuth := []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"username": cluster.MasterAuth.Username,
|
"username": cluster.MasterAuth.Username,
|
||||||
|
@ -576,6 +576,25 @@ func TestAccContainerCluster_withNodePoolNodeConfig(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccContainerCluster_withMaintenanceWindow(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckContainerClusterDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccContainerCluster_withMaintenanceWindow("03:00"),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckContainerCluster(
|
||||||
|
"google_container_cluster.with_maintenance_window"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckContainerClusterDestroy(s *terraform.State) error {
|
func testAccCheckContainerClusterDestroy(s *terraform.State) error {
|
||||||
config := testAccProvider.Meta().(*Config)
|
config := testAccProvider.Meta().(*Config)
|
||||||
|
|
||||||
@ -685,6 +704,11 @@ func testAccCheckContainerCluster(n string) resource.TestCheckFunc {
|
|||||||
clusterTests = append(clusterTests, clusterTestField{"addons_config.0.horizontal_pod_autoscaling.0.disabled", horizontalPodAutoscalingDisabled})
|
clusterTests = append(clusterTests, clusterTestField{"addons_config.0.horizontal_pod_autoscaling.0.disabled", horizontalPodAutoscalingDisabled})
|
||||||
clusterTests = append(clusterTests, clusterTestField{"addons_config.0.kubernetes_dashboard.0.disabled", kubernetesDashboardDisabled})
|
clusterTests = append(clusterTests, clusterTestField{"addons_config.0.kubernetes_dashboard.0.disabled", kubernetesDashboardDisabled})
|
||||||
|
|
||||||
|
if cluster.MaintenancePolicy != nil {
|
||||||
|
clusterTests = append(clusterTests, clusterTestField{"maintenance_policy.0.daily_maintenance_window.0.start_time", cluster.MaintenancePolicy.Window.DailyMaintenanceWindow.StartTime})
|
||||||
|
clusterTests = append(clusterTests, clusterTestField{"maintenance_policy.0.daily_maintenance_window.0.duration", cluster.MaintenancePolicy.Window.DailyMaintenanceWindow.Duration})
|
||||||
|
}
|
||||||
|
|
||||||
for i, np := range cluster.NodePools {
|
for i, np := range cluster.NodePools {
|
||||||
prefix := fmt.Sprintf("node_pool.%d.", i)
|
prefix := fmt.Sprintf("node_pool.%d.", i)
|
||||||
clusterTests = append(clusterTests, clusterTestField{prefix + "name", np.Name})
|
clusterTests = append(clusterTests, clusterTestField{prefix + "name", np.Name})
|
||||||
@ -1441,3 +1465,18 @@ resource "google_container_cluster" "with_node_pool_node_config" {
|
|||||||
}
|
}
|
||||||
`, testId, testId)
|
`, testId, testId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccContainerCluster_withMaintenanceWindow(startTime string) string {
|
||||||
|
return fmt.Sprintf(`
|
||||||
|
resource "google_container_cluster" "with_maintenance_window" {
|
||||||
|
name = "cluster-test-%s"
|
||||||
|
zone = "us-central1-a"
|
||||||
|
initial_node_count = 1
|
||||||
|
|
||||||
|
maintenance_policy {
|
||||||
|
daily_maintenance_window {
|
||||||
|
start_time = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`, acctest.RandString(10), startTime)
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/hashicorp/terraform/helper/validation"
|
"github.com/hashicorp/terraform/helper/validation"
|
||||||
"net"
|
"net"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -61,3 +62,20 @@ func validateRFC1918Network(min, max int) schema.SchemaValidateFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateRFC3339Time(v interface{}, k string) (warnings []string, errors []error) {
|
||||||
|
time := v.(string)
|
||||||
|
if len(time) != 5 || time[2] != ':' {
|
||||||
|
errors = append(errors, fmt.Errorf("%q (%q) must be in the format HH:mm (RFC3399)", k, time))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hour, err := strconv.ParseUint(time[:2], 10, 0); err != nil || hour > 23 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q (%q) does not contain a valid hour (00-23)", k, time))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if min, err := strconv.ParseUint(time[3:], 10, 0); err != nil || min > 59 {
|
||||||
|
errors = append(errors, fmt.Errorf("%q (%q) does not contain a valid minute (00-59)", k, time))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
@ -2,11 +2,12 @@ package google
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValidateGCPName(t *testing.T) {
|
func TestValidateGCPName(t *testing.T) {
|
||||||
x := []GCPNameTestCase{
|
x := []StringValidationTestCase{
|
||||||
// No errors
|
// No errors
|
||||||
{TestName: "basic", Value: "foobar"},
|
{TestName: "basic", Value: "foobar"},
|
||||||
{TestName: "with numbers", Value: "foobar123"},
|
{TestName: "with numbers", Value: "foobar123"},
|
||||||
@ -22,7 +23,7 @@ func TestValidateGCPName(t *testing.T) {
|
|||||||
{TestName: "too long", Value: "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob", ExpectError: true},
|
{TestName: "too long", Value: "foobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoobarfoob", ExpectError: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
es := testGCPNames(x)
|
es := testStringValidationCases(x, validateGCPName)
|
||||||
if len(es) > 0 {
|
if len(es) > 0 {
|
||||||
t.Errorf("Failed to validate GCP names: %v", es)
|
t.Errorf("Failed to validate GCP names: %v", es)
|
||||||
}
|
}
|
||||||
@ -53,7 +54,27 @@ func TestValidateRFC1918Network(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type GCPNameTestCase struct {
|
func TestValidateRFC3339Time(t *testing.T) {
|
||||||
|
cases := []StringValidationTestCase{
|
||||||
|
// No errors
|
||||||
|
{TestName: "midnight", Value: "00:00"},
|
||||||
|
{TestName: "one minute before midnight", Value: "23:59"},
|
||||||
|
|
||||||
|
// With errors
|
||||||
|
{TestName: "single-digit hour", Value: "3:00", ExpectError: true},
|
||||||
|
{TestName: "hour out of range", Value: "24:00", ExpectError: true},
|
||||||
|
{TestName: "minute out of range", Value: "03:60", ExpectError: true},
|
||||||
|
{TestName: "missing colon", Value: "0100", ExpectError: true},
|
||||||
|
{TestName: "not numbers", Value: "ab:cd", ExpectError: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
es := testStringValidationCases(cases, validateRFC3339Time)
|
||||||
|
if len(es) > 0 {
|
||||||
|
t.Errorf("Failed to validate RFC3339 times: %v", es)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringValidationTestCase struct {
|
||||||
TestName string
|
TestName string
|
||||||
Value string
|
Value string
|
||||||
ExpectError bool
|
ExpectError bool
|
||||||
@ -67,17 +88,17 @@ type RFC1918NetworkTestCase struct {
|
|||||||
ExpectError bool
|
ExpectError bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGCPNames(cases []GCPNameTestCase) []error {
|
func testStringValidationCases(cases []StringValidationTestCase, validationFunc schema.SchemaValidateFunc) []error {
|
||||||
es := make([]error, 0)
|
es := make([]error, 0)
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
es = append(es, testGCPName(c)...)
|
es = append(es, testStringValidation(c, validationFunc)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return es
|
return es
|
||||||
}
|
}
|
||||||
|
|
||||||
func testGCPName(testCase GCPNameTestCase) []error {
|
func testStringValidation(testCase StringValidationTestCase, validationFunc schema.SchemaValidateFunc) []error {
|
||||||
_, es := validateGCPName(testCase.Value, testCase.TestName)
|
_, es := validationFunc(testCase.Value, testCase.TestName)
|
||||||
if testCase.ExpectError {
|
if testCase.ExpectError {
|
||||||
if len(es) > 0 {
|
if len(es) > 0 {
|
||||||
return nil
|
return nil
|
||||||
|
@ -102,6 +102,9 @@ output "cluster_ca_certificate" {
|
|||||||
write logs to. Available options include `logging.googleapis.com` and
|
write logs to. Available options include `logging.googleapis.com` and
|
||||||
`none`. Defaults to `logging.googleapis.com`
|
`none`. Defaults to `logging.googleapis.com`
|
||||||
|
|
||||||
|
* `maintenance_policy` - (Optional) The maintenance policy to use for the cluster. Structure is
|
||||||
|
documented below.
|
||||||
|
|
||||||
* `master_auth` - (Optional) The authentication information for accessing the
|
* `master_auth` - (Optional) The authentication information for accessing the
|
||||||
Kubernetes master. Structure is documented below.
|
Kubernetes master. Structure is documented below.
|
||||||
|
|
||||||
@ -167,6 +170,20 @@ addons_config {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `maintenance_policy` block supports:
|
||||||
|
|
||||||
|
* `daily_maintenance_window` - (Required) Time window specified for daily maintenance operations.
|
||||||
|
Specify `start_time` in [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) format "HH:MM”,
|
||||||
|
where HH : \[00-23\] and MM : \[00-59\] GMT. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
maintenance_policy {
|
||||||
|
daily_maintenance_window {
|
||||||
|
start_time = "03:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
The `master_auth` block supports:
|
The `master_auth` block supports:
|
||||||
|
|
||||||
* `password` - (Required) The password to use for HTTP basic authentication when accessing
|
* `password` - (Required) The password to use for HTTP basic authentication when accessing
|
||||||
@ -243,6 +260,10 @@ exported:
|
|||||||
* `instance_group_urls` - List of instance group URLs which have been assigned
|
* `instance_group_urls` - List of instance group URLs which have been assigned
|
||||||
to the cluster.
|
to the cluster.
|
||||||
|
|
||||||
|
* `maintenance_policy.0.daily_maintenance_window.0.duration` - Duration of the time window, automatically chosen to be
|
||||||
|
smallest possible in the given scenario.
|
||||||
|
Duration will be in [RFC3339](https://www.ietf.org/rfc/rfc3339.txt) format "PTnHnMnS".
|
||||||
|
|
||||||
* `master_auth.0.client_certificate` - Base64 encoded public certificate
|
* `master_auth.0.client_certificate` - Base64 encoded public certificate
|
||||||
used by clients to authenticate to the cluster endpoint.
|
used by clients to authenticate to the cluster endpoint.
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user