Skip to content

Commit 5e61d2b

Browse files
Cloud composer maintenance window GA (#5471) (#11170)
Signed-off-by: Modular Magician <[email protected]>
1 parent c6220ca commit 5e61d2b

File tree

4 files changed

+217
-3
lines changed

4 files changed

+217
-3
lines changed

.changelog/5471.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
composer: added support for Cloud Composer maintenance window in GA
3+
```

google/resource_composer_environment.go

+86-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var (
5555
"config.0.database_config",
5656
"config.0.web_server_config",
5757
"config.0.encryption_config",
58+
"config.0.maintenance_window",
5859
"config.0.workloads_config",
5960
"config.0.environment_size",
6061
}
@@ -310,7 +311,7 @@ func resourceComposerEnvironment() *schema.Resource {
310311
AtLeastOneOf: composerSoftwareConfigKeys,
311312
Elem: &schema.Schema{Type: schema.TypeString},
312313
ValidateFunc: validateComposerEnvironmentEnvVariables,
313-
Description: `Additional environment variables to provide to the Apache Airflow schedulerf, worker, and webserver processes. Environment variable names must match the regular expression [a-zA-Z_][a-zA-Z0-9_]*. They cannot specify Apache Airflow software configuration overrides (they cannot match the regular expression AIRFLOW__[A-Z0-9_]+__[A-Z0-9_]+), and they cannot match any of the following reserved names: AIRFLOW_HOME C_FORCE_ROOT CONTAINER_NAME DAGS_FOLDER GCP_PROJECT GCS_BUCKET GKE_CLUSTER_NAME SQL_DATABASE SQL_INSTANCE SQL_PASSWORD SQL_PROJECT SQL_REGION SQL_USER.`,
314+
Description: `Additional environment variables to provide to the Apache Airflow scheduler, worker, and webserver processes. Environment variable names must match the regular expression [a-zA-Z_][a-zA-Z0-9_]*. They cannot specify Apache Airflow software configuration overrides (they cannot match the regular expression AIRFLOW__[A-Z0-9_]+__[A-Z0-9_]+), and they cannot match any of the following reserved names: AIRFLOW_HOME C_FORCE_ROOT CONTAINER_NAME DAGS_FOLDER GCP_PROJECT GCS_BUCKET GKE_CLUSTER_NAME SQL_DATABASE SQL_INSTANCE SQL_PASSWORD SQL_PROJECT SQL_REGION SQL_USER.`,
314315
},
315316
"image_version": {
316317
Type: schema.TypeString,
@@ -463,7 +464,36 @@ func resourceComposerEnvironment() *schema.Resource {
463464
},
464465
},
465466
},
466-
467+
"maintenance_window": {
468+
Type: schema.TypeList,
469+
Optional: true,
470+
Computed: true,
471+
AtLeastOneOf: composerConfigKeys,
472+
MaxItems: 1,
473+
Description: `The configuration for Cloud Composer maintenance window.`,
474+
Elem: &schema.Resource{
475+
Schema: map[string]*schema.Schema{
476+
"start_time": {
477+
Type: schema.TypeString,
478+
Required: true,
479+
ForceNew: false,
480+
Description: `Start time of the first recurrence of the maintenance window.`,
481+
},
482+
"end_time": {
483+
Type: schema.TypeString,
484+
Required: true,
485+
ForceNew: false,
486+
Description: `Maintenance window end time. It is used only to calculate the duration of the maintenance window. The value for end-time must be in the future, relative to 'start_time'.`,
487+
},
488+
"recurrence": {
489+
Type: schema.TypeString,
490+
Required: true,
491+
ForceNew: false,
492+
Description: `Maintenance window recurrence. Format is a subset of RFC-5545 (https://tools.ietf.org/html/rfc5545) 'RRULE'. The only allowed values for 'FREQ' field are 'FREQ=DAILY' and 'FREQ=WEEKLY;BYDAY=...'. Example values: 'FREQ=WEEKLY;BYDAY=TU,WE', 'FREQ=DAILY'.`,
493+
},
494+
},
495+
},
496+
},
467497
"workloads_config": {
468498
Type: schema.TypeList,
469499
Optional: true,
@@ -869,6 +899,17 @@ func resourceComposerEnvironmentUpdate(d *schema.ResourceData, meta interface{})
869899
}
870900
}
871901

902+
if d.HasChange("config.0.maintenance_window") {
903+
patchObj := &composer.Environment{Config: &composer.EnvironmentConfig{}}
904+
if config != nil {
905+
patchObj.Config.MaintenanceWindow = config.MaintenanceWindow
906+
}
907+
err = resourceComposerEnvironmentPatchField("config.maintenanceWindow", userAgent, patchObj, d, tfConfig)
908+
if err != nil {
909+
return err
910+
}
911+
}
912+
872913
if d.HasChange("config.0.workloads_config") {
873914
patchObj := &composer.Environment{Config: &composer.EnvironmentConfig{}}
874915
if config != nil {
@@ -1011,6 +1052,7 @@ func flattenComposerEnvironmentConfig(envCfg *composer.EnvironmentConfig) interf
10111052
transformed["database_config"] = flattenComposerEnvironmentConfigDatabaseConfig(envCfg.DatabaseConfig)
10121053
transformed["web_server_config"] = flattenComposerEnvironmentConfigWebServerConfig(envCfg.WebServerConfig)
10131054
transformed["encryption_config"] = flattenComposerEnvironmentConfigEncryptionConfig(envCfg.EncryptionConfig)
1055+
transformed["maintenance_window"] = flattenComposerEnvironmentConfigMaintenanceWindow(envCfg.MaintenanceWindow)
10141056
transformed["workloads_config"] = flattenComposerEnvironmentConfigWorkloadsConfig(envCfg.WorkloadsConfig)
10151057
transformed["environment_size"] = envCfg.EnvironmentSize
10161058
return []interface{}{transformed}
@@ -1070,6 +1112,19 @@ func flattenComposerEnvironmentConfigEncryptionConfig(encryptionCfg *composer.En
10701112
return []interface{}{transformed}
10711113
}
10721114

1115+
func flattenComposerEnvironmentConfigMaintenanceWindow(maintenanceWindow *composer.MaintenanceWindow) interface{} {
1116+
if maintenanceWindow == nil {
1117+
return nil
1118+
}
1119+
1120+
transformed := make(map[string]interface{})
1121+
transformed["start_time"] = maintenanceWindow.StartTime
1122+
transformed["end_time"] = maintenanceWindow.EndTime
1123+
transformed["recurrence"] = maintenanceWindow.Recurrence
1124+
1125+
return []interface{}{transformed}
1126+
}
1127+
10731128
func flattenComposerEnvironmentConfigWorkloadsConfig(workloadsConfig *composer.WorkloadsConfig) interface{} {
10741129
if workloadsConfig == nil {
10751130
return nil
@@ -1250,6 +1305,11 @@ func expandComposerEnvironmentConfig(v interface{}, d *schema.ResourceData, conf
12501305
}
12511306
transformed.EncryptionConfig = transformedEncryptionConfig
12521307

1308+
transformedMaintenanceWindow, err := expandComposerEnvironmentConfigMaintenanceWindow(original["maintenance_window"], d, config)
1309+
if err != nil {
1310+
return nil, err
1311+
}
1312+
transformed.MaintenanceWindow = transformedMaintenanceWindow
12531313
transformedWorkloadsConfig, err := expandComposerEnvironmentConfigWorkloadsConfig(original["workloads_config"], d, config)
12541314
if err != nil {
12551315
return nil, err
@@ -1342,6 +1402,30 @@ func expandComposerEnvironmentConfigEncryptionConfig(v interface{}, d *schema.Re
13421402
return transformed, nil
13431403
}
13441404

1405+
func expandComposerEnvironmentConfigMaintenanceWindow(v interface{}, d *schema.ResourceData, config *Config) (*composer.MaintenanceWindow, error) {
1406+
l := v.([]interface{})
1407+
if len(l) == 0 {
1408+
return nil, nil
1409+
}
1410+
raw := l[0]
1411+
original := raw.(map[string]interface{})
1412+
transformed := &composer.MaintenanceWindow{}
1413+
1414+
if v, ok := original["start_time"]; ok {
1415+
transformed.StartTime = v.(string)
1416+
}
1417+
1418+
if v, ok := original["end_time"]; ok {
1419+
transformed.EndTime = v.(string)
1420+
}
1421+
1422+
if v, ok := original["recurrence"]; ok {
1423+
transformed.Recurrence = v.(string)
1424+
}
1425+
1426+
return transformed, nil
1427+
}
1428+
13451429
func expandComposerEnvironmentConfigWorkloadsConfig(v interface{}, d *schema.ResourceData, config *Config) (*composer.WorkloadsConfig, error) {
13461430
l := v.([]interface{})
13471431
if len(l) == 0 {

google/resource_composer_environment_test.go

+127
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,75 @@ func TestAccComposerEnvironment_withEncryptionConfig(t *testing.T) {
325325
})
326326
}
327327

328+
func TestAccComposerEnvironment_withMaintenanceWindow(t *testing.T) {
329+
t.Parallel()
330+
331+
envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, randInt(t))
332+
network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, randInt(t))
333+
subnetwork := network + "-1"
334+
335+
vcrTest(t, resource.TestCase{
336+
PreCheck: func() { testAccPreCheck(t) },
337+
Providers: testAccProviders,
338+
CheckDestroy: testAccComposerEnvironmentDestroyProducer(t),
339+
Steps: []resource.TestStep{
340+
{
341+
Config: testAccComposerEnvironment_maintenanceWindow(envName, network, subnetwork),
342+
},
343+
{
344+
ResourceName: "google_composer_environment.test",
345+
ImportState: true,
346+
ImportStateVerify: true,
347+
},
348+
// This is a terrible clean-up step in order to get destroy to succeed,
349+
// due to dangling firewall rules left by the Composer Environment blocking network deletion.
350+
// TODO(dzarmola): Remove this check if firewall rules bug gets fixed by Composer.
351+
{
352+
PlanOnly: true,
353+
ExpectNonEmptyPlan: false,
354+
Config: testAccComposerEnvironment_maintenanceWindow(envName, network, subnetwork),
355+
Check: testAccCheckClearComposerEnvironmentFirewalls(t, network),
356+
},
357+
},
358+
})
359+
}
360+
361+
func TestAccComposerEnvironment_maintenanceWindowUpdate(t *testing.T) {
362+
t.Parallel()
363+
364+
envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, randInt(t))
365+
network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, randInt(t))
366+
subnetwork := network + "-1"
367+
368+
vcrTest(t, resource.TestCase{
369+
PreCheck: func() { testAccPreCheck(t) },
370+
Providers: testAccProviders,
371+
CheckDestroy: testAccComposerEnvironmentDestroyProducer(t),
372+
Steps: []resource.TestStep{
373+
{
374+
Config: testAccComposerEnvironment_maintenanceWindow(envName, network, subnetwork),
375+
},
376+
{
377+
Config: testAccComposerEnvironment_maintenanceWindowUpdate(envName, network, subnetwork),
378+
},
379+
{
380+
ResourceName: "google_composer_environment.test",
381+
ImportState: true,
382+
ImportStateVerify: true,
383+
},
384+
// This is a terrible clean-up step in order to get destroy to succeed,
385+
// due to dangling firewall rules left by the Composer Environment blocking network deletion.
386+
// TODO: Remove this check if firewall rules bug gets fixed by Composer.
387+
{
388+
PlanOnly: true,
389+
ExpectNonEmptyPlan: false,
390+
Config: testAccComposerEnvironment_maintenanceWindowUpdate(envName, network, subnetwork),
391+
Check: testAccCheckClearComposerEnvironmentFirewalls(t, network),
392+
},
393+
},
394+
})
395+
}
396+
328397
func TestAccComposerEnvironment_ComposerV2(t *testing.T) {
329398
t.Parallel()
330399

@@ -934,6 +1003,64 @@ resource "google_compute_subnetwork" "test" {
9341003
`, pid, kmsKey, name, kmsKey, network, subnetwork)
9351004
}
9361005

1006+
func testAccComposerEnvironment_maintenanceWindow(envName, network, subnetwork string) string {
1007+
return fmt.Sprintf(`
1008+
resource "google_composer_environment" "test" {
1009+
name = "%s"
1010+
region = "us-central1"
1011+
config {
1012+
maintenance_window {
1013+
start_time = "2019-08-01T01:00:00Z"
1014+
end_time = "2019-08-01T07:00:00Z"
1015+
recurrence = "FREQ=WEEKLY;BYDAY=TU,WE"
1016+
}
1017+
}
1018+
}
1019+
1020+
resource "google_compute_network" "test" {
1021+
name = "%s"
1022+
auto_create_subnetworks = false
1023+
}
1024+
1025+
resource "google_compute_subnetwork" "test" {
1026+
name = "%s"
1027+
ip_cidr_range = "10.2.0.0/16"
1028+
region = "us-central1"
1029+
network = google_compute_network.test.self_link
1030+
}
1031+
1032+
`, envName, network, subnetwork)
1033+
}
1034+
1035+
func testAccComposerEnvironment_maintenanceWindowUpdate(envName, network, subnetwork string) string {
1036+
return fmt.Sprintf(`
1037+
resource "google_composer_environment" "test" {
1038+
name = "%s"
1039+
region = "us-central1"
1040+
config {
1041+
maintenance_window {
1042+
start_time = "2019-08-01T01:00:00Z"
1043+
end_time = "2019-08-01T07:00:00Z"
1044+
recurrence = "FREQ=DAILY"
1045+
}
1046+
}
1047+
}
1048+
1049+
resource "google_compute_network" "test" {
1050+
name = "%s"
1051+
auto_create_subnetworks = false
1052+
}
1053+
1054+
resource "google_compute_subnetwork" "test" {
1055+
name = "%s"
1056+
ip_cidr_range = "10.2.0.0/16"
1057+
region = "us-central1"
1058+
network = google_compute_network.test.self_link
1059+
}
1060+
1061+
`, envName, network, subnetwork)
1062+
}
1063+
9371064
func testAccComposerEnvironment_composerV2(envName, network, subnetwork string) string {
9381065
return fmt.Sprintf(`
9391066
data "google_composer_image_versions" "all" {

website/docs/r/composer_environment.html.markdown

+1-1
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ The `config` block supports:
656656
below.
657657

658658
* `maintenance_window` -
659-
(Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html))
659+
(Optional)
660660
The configuration settings for Cloud Composer maintenance windows.
661661

662662
* `workloads_config` -

0 commit comments

Comments
 (0)