From a330838d978d6c485afafa7c24a1538aac515e80 Mon Sep 17 00:00:00 2001 From: Modular Magician Date: Mon, 23 Aug 2021 19:28:57 +0000 Subject: [PATCH] Hierarchical firewall policies (#5090) * Send empty instead of nil array * Adding hierarchical firewall rules * Update firewall policy yamls * Fix test: * Add markdown for hierarchical firewall policies * PR review * Add auto id renaming Signed-off-by: Modular Magician --- .changelog/5090.txt | 9 + google/expanders.go | 7 +- google/provider.go | 3 + google/provider_dcl_client_creation.go | 20 + google/provider_dcl_endpoints.go | 12 + google/resource_compute_firewall_policy.go | 306 +++++++++++ ...rce_compute_firewall_policy_association.go | 205 ++++++++ ...ompute_firewall_policy_association_test.go | 60 +++ .../resource_compute_firewall_policy_rule.go | 479 ++++++++++++++++++ ...ource_compute_firewall_policy_rule_test.go | 437 ++++++++++++++++ ...ce_compute_firewall_policy_sweeper_test.go | 72 +++ .../resource_compute_firewall_policy_test.go | 78 +++ .../r/compute_firewall_policy.html.markdown | 110 ++++ ..._firewall_policy_association.html.markdown | 96 ++++ ...compute_firewall_policy_rule.html.markdown | 162 ++++++ website/google.erb | 24 + 16 files changed, 2079 insertions(+), 1 deletion(-) create mode 100644 .changelog/5090.txt create mode 100644 google/resource_compute_firewall_policy.go create mode 100644 google/resource_compute_firewall_policy_association.go create mode 100644 google/resource_compute_firewall_policy_association_test.go create mode 100644 google/resource_compute_firewall_policy_rule.go create mode 100644 google/resource_compute_firewall_policy_rule_test.go create mode 100644 google/resource_compute_firewall_policy_sweeper_test.go create mode 100644 google/resource_compute_firewall_policy_test.go create mode 100644 website/docs/r/compute_firewall_policy.html.markdown create mode 100644 website/docs/r/compute_firewall_policy_association.html.markdown create mode 100644 website/docs/r/compute_firewall_policy_rule.html.markdown diff --git a/.changelog/5090.txt b/.changelog/5090.txt new file mode 100644 index 00000000000..4c65743a322 --- /dev/null +++ b/.changelog/5090.txt @@ -0,0 +1,9 @@ +```release-note:new-resource +google_compute_firewall_policy_rule +``` +```release-note:new-resource +google_compute_firewall_policy_association +``` +```release-note:new-resource +google_compute_firewall_policy +``` diff --git a/google/expanders.go b/google/expanders.go index c84756ba8c5..98fd012b8e3 100644 --- a/google/expanders.go +++ b/google/expanders.go @@ -13,5 +13,10 @@ func expandStringArray(v interface{}) []string { return convertStringSet(arr) } - return convertStringArr(v.([]interface{})) + arr = convertStringArr(v.([]interface{})) + if arr == nil { + // Send empty array specifically instead of nil + return make([]string, 0) + } + return arr } diff --git a/google/provider.go b/google/provider.go index bb94e3ad820..c37a7968c46 100644 --- a/google/provider.go +++ b/google/provider.go @@ -1133,6 +1133,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_cloudfunctions_function": resourceCloudFunctionsFunction(), "google_composer_environment": resourceComposerEnvironment(), "google_compute_attached_disk": resourceComputeAttachedDisk(), + "google_compute_firewall_policy_association": resourceComputeFirewallPolicyAssociation(), + "google_compute_firewall_policy": resourceComputeFirewallPolicy(), + "google_compute_firewall_policy_rule": resourceComputeFirewallPolicyRule(), "google_compute_instance": resourceComputeInstance(), "google_compute_instance_from_template": resourceComputeInstanceFromTemplate(), "google_compute_instance_group": resourceComputeInstanceGroup(), diff --git a/google/provider_dcl_client_creation.go b/google/provider_dcl_client_creation.go index 9cc23a655da..15974d6d1a4 100644 --- a/google/provider_dcl_client_creation.go +++ b/google/provider_dcl_client_creation.go @@ -19,6 +19,7 @@ import ( dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" assuredworkloads "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/assuredworkloads" + compute "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/compute" dataproc "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/dataproc" eventarc "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/eventarc" ) @@ -42,6 +43,25 @@ func NewDCLAssuredWorkloadsClient(config *Config, userAgent, billingProject stri return assuredworkloads.NewClient(dclConfig) } +func NewDCLComputeClient(config *Config, userAgent, billingProject string) *compute.Client { + configOptions := []dcl.ConfigOption{ + dcl.WithHTTPClient(config.client), + dcl.WithUserAgent(userAgent), + dcl.WithLogger(dclLogger{}), + dcl.WithBasePath(config.ComputeBasePath), + } + + if config.UserProjectOverride { + configOptions = append(configOptions, dcl.WithUserProjectOverride()) + if billingProject != "" { + configOptions = append(configOptions, dcl.WithBillingProject(billingProject)) + } + } + + dclConfig := dcl.NewConfig(configOptions...) + return compute.NewClient(dclConfig) +} + func NewDCLDataprocClient(config *Config, userAgent, billingProject string) *dataproc.Client { configOptions := []dcl.ConfigOption{ dcl.WithHTTPClient(config.client), diff --git a/google/provider_dcl_endpoints.go b/google/provider_dcl_endpoints.go index 8599b0978ef..e0ba1642f2a 100644 --- a/google/provider_dcl_endpoints.go +++ b/google/provider_dcl_endpoints.go @@ -31,6 +31,15 @@ var AssuredWorkloadsEndpointEntry = &schema.Schema{ }, ""), } +var ComputeEndpointEntryKey = "compute_custom_endpoint" +var ComputeEndpointEntry = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.MultiEnvDefaultFunc([]string{ + "GOOGLE_COMPUTE_CUSTOM_ENDPOINT", + }, ""), +} + var EventarcEndpointEntryKey = "eventarc_custom_endpoint" var EventarcEndpointEntry = &schema.Schema{ Type: schema.TypeString, @@ -42,12 +51,15 @@ var EventarcEndpointEntry = &schema.Schema{ //Add new values to config.go.erb config object declaration //AssuredWorkloadsBasePath string +//ComputeBasePath string //EventarcBasePath string //Add new values to provider.go.erb schema initialization // AssuredWorkloadsEndpointEntryKey: AssuredWorkloadsEndpointEntry, +// ComputeEndpointEntryKey: ComputeEndpointEntry, // EventarcEndpointEntryKey: EventarcEndpointEntry, //Add new values to provider.go.erb - provider block read // config.AssuredWorkloadsBasePath = d.Get(AssuredWorkloadsEndpointEntryKey).(string) +// config.ComputeBasePath = d.Get(ComputeEndpointEntryKey).(string) // config.EventarcBasePath = d.Get(EventarcEndpointEntryKey).(string) diff --git a/google/resource_compute_firewall_policy.go b/google/resource_compute_firewall_policy.go new file mode 100644 index 00000000000..334185f522c --- /dev/null +++ b/google/resource_compute_firewall_policy.go @@ -0,0 +1,306 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: DCL *** +// +// ---------------------------------------------------------------------------- +// +// This file is managed by Magic Modules (https://github.com/GoogleCloudPlatform/magic-modules) +// and is based on the DCL (https://github.com/GoogleCloudPlatform/declarative-resource-client-library). +// Changes will need to be made to the DCL or Magic Modules instead of here. +// +// We are not currently able to accept contributions to this file. If changes +// are required, please file an issue at https://github.com/hashicorp/terraform-provider-google/issues/new/choose +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" + compute "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/compute" +) + +func resourceComputeFirewallPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFirewallPolicyCreate, + Read: resourceComputeFirewallPolicyRead, + Update: resourceComputeFirewallPolicyUpdate, + Delete: resourceComputeFirewallPolicyDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeFirewallPolicyImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "parent": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: ``, + }, + + "short_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: ``, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Description: ``, + }, + + "creation_timestamp": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "fingerprint": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "firewall_policy_id": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "name": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "rule_tuple_count": { + Type: schema.TypeInt, + Computed: true, + Description: ``, + }, + + "self_link": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "self_link_with_id": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + }, + } +} + +func resourceComputeFirewallPolicyCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicy{ + Parent: dcl.String(d.Get("parent").(string)), + ShortName: dcl.String(d.Get("short_name").(string)), + Description: dcl.String(d.Get("description").(string)), + } + + id, err := replaceVars(d, config, "locations/global/firewallPolicies/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + createDirective := CreateDirective + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.ApplyFirewallPolicy(context.Background(), obj, createDirective...) + + if _, ok := err.(dcl.DiffAfterApplyError); ok { + log.Printf("[DEBUG] Diff after apply returned from the DCL: %s", err) + } else if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error creating FirewallPolicy: %s", err) + } + + log.Printf("[DEBUG] Finished creating FirewallPolicy %q: %#v", d.Id(), res) + + if err = d.Set("name", res.Name); err != nil { + return fmt.Errorf("error setting name in state: %s", err) + } + // Id has a server-generated value, set again after creation + id, err = replaceVars(d, config, "locations/global/firewallPolicies/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return resourceComputeFirewallPolicyRead(d, meta) +} + +func resourceComputeFirewallPolicyRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicy{ + Parent: dcl.String(d.Get("parent").(string)), + ShortName: dcl.String(d.Get("short_name").(string)), + Description: dcl.String(d.Get("description").(string)), + Name: dcl.StringOrNil(d.Get("name").(string)), + } + + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.GetFirewallPolicy(context.Background(), obj) + if err != nil { + // Resource not found + d.SetId("") + return err + } + + if err = d.Set("parent", res.Parent); err != nil { + return fmt.Errorf("error setting parent in state: %s", err) + } + if err = d.Set("short_name", res.ShortName); err != nil { + return fmt.Errorf("error setting short_name in state: %s", err) + } + if err = d.Set("description", res.Description); err != nil { + return fmt.Errorf("error setting description in state: %s", err) + } + if err = d.Set("creation_timestamp", res.CreationTimestamp); err != nil { + return fmt.Errorf("error setting creation_timestamp in state: %s", err) + } + if err = d.Set("fingerprint", res.Fingerprint); err != nil { + return fmt.Errorf("error setting fingerprint in state: %s", err) + } + if err = d.Set("firewall_policy_id", res.Id); err != nil { + return fmt.Errorf("error setting firewall_policy_id in state: %s", err) + } + if err = d.Set("name", res.Name); err != nil { + return fmt.Errorf("error setting name in state: %s", err) + } + if err = d.Set("rule_tuple_count", res.RuleTupleCount); err != nil { + return fmt.Errorf("error setting rule_tuple_count in state: %s", err) + } + if err = d.Set("self_link", res.SelfLink); err != nil { + return fmt.Errorf("error setting self_link in state: %s", err) + } + if err = d.Set("self_link_with_id", res.SelfLinkWithId); err != nil { + return fmt.Errorf("error setting self_link_with_id in state: %s", err) + } + + return nil +} +func resourceComputeFirewallPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicy{ + Parent: dcl.String(d.Get("parent").(string)), + ShortName: dcl.String(d.Get("short_name").(string)), + Description: dcl.String(d.Get("description").(string)), + Name: dcl.StringOrNil(d.Get("name").(string)), + } + directive := UpdateDirective + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.ApplyFirewallPolicy(context.Background(), obj, directive...) + + if _, ok := err.(dcl.DiffAfterApplyError); ok { + log.Printf("[DEBUG] Diff after apply returned from the DCL: %s", err) + } else if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error updating FirewallPolicy: %s", err) + } + + log.Printf("[DEBUG] Finished creating FirewallPolicy %q: %#v", d.Id(), res) + + return resourceComputeFirewallPolicyRead(d, meta) +} + +func resourceComputeFirewallPolicyDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicy{ + Parent: dcl.String(d.Get("parent").(string)), + ShortName: dcl.String(d.Get("short_name").(string)), + Description: dcl.String(d.Get("description").(string)), + Name: dcl.StringOrNil(d.Get("name").(string)), + } + + log.Printf("[DEBUG] Deleting FirewallPolicy %q", d.Id()) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + if err := client.DeleteFirewallPolicy(context.Background(), obj); err != nil { + return fmt.Errorf("Error deleting FirewallPolicy: %s", err) + } + + log.Printf("[DEBUG] Finished deleting FirewallPolicy %q", d.Id()) + return nil +} + +func resourceComputeFirewallPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "locations/global/firewallPolicies/(?P[^/]+)", + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "locations/global/firewallPolicies/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} diff --git a/google/resource_compute_firewall_policy_association.go b/google/resource_compute_firewall_policy_association.go new file mode 100644 index 00000000000..d2aee598810 --- /dev/null +++ b/google/resource_compute_firewall_policy_association.go @@ -0,0 +1,205 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: DCL *** +// +// ---------------------------------------------------------------------------- +// +// This file is managed by Magic Modules (https://github.com/GoogleCloudPlatform/magic-modules) +// and is based on the DCL (https://github.com/GoogleCloudPlatform/declarative-resource-client-library). +// Changes will need to be made to the DCL or Magic Modules instead of here. +// +// We are not currently able to accept contributions to this file. If changes +// are required, please file an issue at https://github.com/hashicorp/terraform-provider-google/issues/new/choose +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" + compute "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/compute" +) + +func resourceComputeFirewallPolicyAssociation() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFirewallPolicyAssociationCreate, + Read: resourceComputeFirewallPolicyAssociationRead, + Delete: resourceComputeFirewallPolicyAssociationDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeFirewallPolicyAssociationImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "attachment_target": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: ``, + }, + + "firewall_policy": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: ``, + }, + + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: ``, + }, + + "short_name": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + }, + } +} + +func resourceComputeFirewallPolicyAssociationCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyAssociation{ + AttachmentTarget: dcl.String(d.Get("attachment_target").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Name: dcl.String(d.Get("name").(string)), + } + + id, err := replaceVarsForId(d, config, "locations/global/firewallPolicies/{{firewall_policy}}/associations/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + createDirective := CreateDirective + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.ApplyFirewallPolicyAssociation(context.Background(), obj, createDirective...) + + if _, ok := err.(dcl.DiffAfterApplyError); ok { + log.Printf("[DEBUG] Diff after apply returned from the DCL: %s", err) + } else if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error creating FirewallPolicyAssociation: %s", err) + } + + log.Printf("[DEBUG] Finished creating FirewallPolicyAssociation %q: %#v", d.Id(), res) + + return resourceComputeFirewallPolicyAssociationRead(d, meta) +} + +func resourceComputeFirewallPolicyAssociationRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyAssociation{ + AttachmentTarget: dcl.String(d.Get("attachment_target").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Name: dcl.String(d.Get("name").(string)), + } + + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.GetFirewallPolicyAssociation(context.Background(), obj) + if err != nil { + // Resource not found + d.SetId("") + return err + } + + if err = d.Set("attachment_target", res.AttachmentTarget); err != nil { + return fmt.Errorf("error setting attachment_target in state: %s", err) + } + if err = d.Set("firewall_policy", res.FirewallPolicy); err != nil { + return fmt.Errorf("error setting firewall_policy in state: %s", err) + } + if err = d.Set("name", res.Name); err != nil { + return fmt.Errorf("error setting name in state: %s", err) + } + if err = d.Set("short_name", res.ShortName); err != nil { + return fmt.Errorf("error setting short_name in state: %s", err) + } + + return nil +} + +func resourceComputeFirewallPolicyAssociationDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyAssociation{ + AttachmentTarget: dcl.String(d.Get("attachment_target").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Name: dcl.String(d.Get("name").(string)), + } + + log.Printf("[DEBUG] Deleting FirewallPolicyAssociation %q", d.Id()) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + if err := client.DeleteFirewallPolicyAssociation(context.Background(), obj); err != nil { + return fmt.Errorf("Error deleting FirewallPolicyAssociation: %s", err) + } + + log.Printf("[DEBUG] Finished deleting FirewallPolicyAssociation %q", d.Id()) + return nil +} + +func resourceComputeFirewallPolicyAssociationImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "locations/global/firewallPolicies/(?P[^/]+)/associations/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVarsForId(d, config, "locations/global/firewallPolicies/{{firewall_policy}}/associations/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} diff --git a/google/resource_compute_firewall_policy_association_test.go b/google/resource_compute_firewall_policy_association_test.go new file mode 100644 index 00000000000..8ed2122c835 --- /dev/null +++ b/google/resource_compute_firewall_policy_association_test.go @@ -0,0 +1,60 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccComputeFirewallPolicyAssociation_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "org_name": fmt.Sprintf("organizations/%s", getTestOrgFromEnv(t)), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeFirewallPolicyAssociation_basic(context), + }, + { + ResourceName: "google_compute_firewall_policy_association.default", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + }, + }) +} + +func testAccComputeFirewallPolicyAssociation_basic(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_folder" "target_folder" { + display_name = "tf-test-target-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.id + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_association" "default" { + firewall_policy = google_compute_firewall_policy.default.id + attachment_target = google_folder.target_folder.name + name = "tf-test-association-%{random_suffix}" +} +`, context) +} diff --git a/google/resource_compute_firewall_policy_rule.go b/google/resource_compute_firewall_policy_rule.go new file mode 100644 index 00000000000..d63914d6198 --- /dev/null +++ b/google/resource_compute_firewall_policy_rule.go @@ -0,0 +1,479 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: DCL *** +// +// ---------------------------------------------------------------------------- +// +// This file is managed by Magic Modules (https://github.com/GoogleCloudPlatform/magic-modules) +// and is based on the DCL (https://github.com/GoogleCloudPlatform/declarative-resource-client-library). +// Changes will need to be made to the DCL or Magic Modules instead of here. +// +// We are not currently able to accept contributions to this file. If changes +// are required, please file an issue at https://github.com/hashicorp/terraform-provider-google/issues/new/choose +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + dcl "github.com/GoogleCloudPlatform/declarative-resource-client-library/dcl" + compute "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/compute" +) + +func resourceComputeFirewallPolicyRule() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeFirewallPolicyRuleCreate, + Read: resourceComputeFirewallPolicyRuleRead, + Update: resourceComputeFirewallPolicyRuleUpdate, + Delete: resourceComputeFirewallPolicyRuleDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeFirewallPolicyRuleImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(10 * time.Minute), + Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeString, + Required: true, + Description: ``, + }, + + "direction": { + Type: schema.TypeString, + Required: true, + Description: ``, + ValidateFunc: validation.StringInSlice([]string{"INGRESS", "EGRESS", ""}, false), + }, + + "firewall_policy": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: ``, + }, + + "match": { + Type: schema.TypeList, + Required: true, + Description: ``, + MaxItems: 1, + Elem: ComputeFirewallPolicyRuleMatchSchema(), + }, + + "priority": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + Description: ``, + }, + + "description": { + Type: schema.TypeString, + Optional: true, + Description: ``, + }, + + "disabled": { + Type: schema.TypeBool, + Optional: true, + Description: ``, + }, + + "enable_logging": { + Type: schema.TypeBool, + Optional: true, + Description: ``, + }, + + "target_resources": { + Type: schema.TypeList, + Optional: true, + DiffSuppressFunc: compareSelfLinkOrResourceName, + Description: ``, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "target_service_accounts": { + Type: schema.TypeList, + Optional: true, + Description: ``, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "kind": { + Type: schema.TypeString, + Computed: true, + Description: ``, + }, + + "rule_tuple_count": { + Type: schema.TypeInt, + Computed: true, + Description: ``, + }, + }, + } +} + +func ComputeFirewallPolicyRuleMatchSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "layer4_configs": { + Type: schema.TypeList, + Required: true, + Description: ``, + Elem: ComputeFirewallPolicyRuleMatchLayer4ConfigsSchema(), + }, + + "dest_ip_ranges": { + Type: schema.TypeList, + Optional: true, + Description: ``, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "src_ip_ranges": { + Type: schema.TypeList, + Optional: true, + Description: ``, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func ComputeFirewallPolicyRuleMatchLayer4ConfigsSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_protocol": { + Type: schema.TypeString, + Required: true, + Description: ``, + }, + + "ports": { + Type: schema.TypeList, + Optional: true, + Description: ``, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func resourceComputeFirewallPolicyRuleCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyRule{ + Action: dcl.String(d.Get("action").(string)), + Direction: compute.FirewallPolicyRuleDirectionEnumRef(d.Get("direction").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Match: expandComputeFirewallPolicyRuleMatch(d.Get("match")), + Priority: dcl.Int64(int64(d.Get("priority").(int))), + Description: dcl.String(d.Get("description").(string)), + Disabled: dcl.Bool(d.Get("disabled").(bool)), + EnableLogging: dcl.Bool(d.Get("enable_logging").(bool)), + TargetResources: expandStringArray(d.Get("target_resources")), + TargetServiceAccounts: expandStringArray(d.Get("target_service_accounts")), + } + + id, err := replaceVarsForId(d, config, "locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + createDirective := CreateDirective + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.ApplyFirewallPolicyRule(context.Background(), obj, createDirective...) + + if _, ok := err.(dcl.DiffAfterApplyError); ok { + log.Printf("[DEBUG] Diff after apply returned from the DCL: %s", err) + } else if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error creating FirewallPolicyRule: %s", err) + } + + log.Printf("[DEBUG] Finished creating FirewallPolicyRule %q: %#v", d.Id(), res) + + return resourceComputeFirewallPolicyRuleRead(d, meta) +} + +func resourceComputeFirewallPolicyRuleRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyRule{ + Action: dcl.String(d.Get("action").(string)), + Direction: compute.FirewallPolicyRuleDirectionEnumRef(d.Get("direction").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Match: expandComputeFirewallPolicyRuleMatch(d.Get("match")), + Priority: dcl.Int64(int64(d.Get("priority").(int))), + Description: dcl.String(d.Get("description").(string)), + Disabled: dcl.Bool(d.Get("disabled").(bool)), + EnableLogging: dcl.Bool(d.Get("enable_logging").(bool)), + TargetResources: expandStringArray(d.Get("target_resources")), + TargetServiceAccounts: expandStringArray(d.Get("target_service_accounts")), + } + + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.GetFirewallPolicyRule(context.Background(), obj) + if err != nil { + // Resource not found + d.SetId("") + return err + } + + if err = d.Set("action", res.Action); err != nil { + return fmt.Errorf("error setting action in state: %s", err) + } + if err = d.Set("direction", res.Direction); err != nil { + return fmt.Errorf("error setting direction in state: %s", err) + } + if err = d.Set("firewall_policy", res.FirewallPolicy); err != nil { + return fmt.Errorf("error setting firewall_policy in state: %s", err) + } + if err = d.Set("match", flattenComputeFirewallPolicyRuleMatch(res.Match)); err != nil { + return fmt.Errorf("error setting match in state: %s", err) + } + if err = d.Set("priority", res.Priority); err != nil { + return fmt.Errorf("error setting priority in state: %s", err) + } + if err = d.Set("description", res.Description); err != nil { + return fmt.Errorf("error setting description in state: %s", err) + } + if err = d.Set("disabled", res.Disabled); err != nil { + return fmt.Errorf("error setting disabled in state: %s", err) + } + if err = d.Set("enable_logging", res.EnableLogging); err != nil { + return fmt.Errorf("error setting enable_logging in state: %s", err) + } + if err = d.Set("target_resources", res.TargetResources); err != nil { + return fmt.Errorf("error setting target_resources in state: %s", err) + } + if err = d.Set("target_service_accounts", res.TargetServiceAccounts); err != nil { + return fmt.Errorf("error setting target_service_accounts in state: %s", err) + } + if err = d.Set("kind", res.Kind); err != nil { + return fmt.Errorf("error setting kind in state: %s", err) + } + if err = d.Set("rule_tuple_count", res.RuleTupleCount); err != nil { + return fmt.Errorf("error setting rule_tuple_count in state: %s", err) + } + + return nil +} +func resourceComputeFirewallPolicyRuleUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyRule{ + Action: dcl.String(d.Get("action").(string)), + Direction: compute.FirewallPolicyRuleDirectionEnumRef(d.Get("direction").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Match: expandComputeFirewallPolicyRuleMatch(d.Get("match")), + Priority: dcl.Int64(int64(d.Get("priority").(int))), + Description: dcl.String(d.Get("description").(string)), + Disabled: dcl.Bool(d.Get("disabled").(bool)), + EnableLogging: dcl.Bool(d.Get("enable_logging").(bool)), + TargetResources: expandStringArray(d.Get("target_resources")), + TargetServiceAccounts: expandStringArray(d.Get("target_service_accounts")), + } + directive := UpdateDirective + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + res, err := client.ApplyFirewallPolicyRule(context.Background(), obj, directive...) + + if _, ok := err.(dcl.DiffAfterApplyError); ok { + log.Printf("[DEBUG] Diff after apply returned from the DCL: %s", err) + } else if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error updating FirewallPolicyRule: %s", err) + } + + log.Printf("[DEBUG] Finished creating FirewallPolicyRule %q: %#v", d.Id(), res) + + return resourceComputeFirewallPolicyRuleRead(d, meta) +} + +func resourceComputeFirewallPolicyRuleDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := &compute.FirewallPolicyRule{ + Action: dcl.String(d.Get("action").(string)), + Direction: compute.FirewallPolicyRuleDirectionEnumRef(d.Get("direction").(string)), + FirewallPolicy: dcl.String(d.Get("firewall_policy").(string)), + Match: expandComputeFirewallPolicyRuleMatch(d.Get("match")), + Priority: dcl.Int64(int64(d.Get("priority").(int))), + Description: dcl.String(d.Get("description").(string)), + Disabled: dcl.Bool(d.Get("disabled").(bool)), + EnableLogging: dcl.Bool(d.Get("enable_logging").(bool)), + TargetResources: expandStringArray(d.Get("target_resources")), + TargetServiceAccounts: expandStringArray(d.Get("target_service_accounts")), + } + + log.Printf("[DEBUG] Deleting FirewallPolicyRule %q", d.Id()) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + billingProject := "" + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + client := NewDCLComputeClient(config, userAgent, billingProject) + if err := client.DeleteFirewallPolicyRule(context.Background(), obj); err != nil { + return fmt.Errorf("Error deleting FirewallPolicyRule: %s", err) + } + + log.Printf("[DEBUG] Finished deleting FirewallPolicyRule %q", d.Id()) + return nil +} + +func resourceComputeFirewallPolicyRuleImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "locations/global/firewallPolicies/(?P[^/]+)/rules/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVarsForId(d, config, "locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func expandComputeFirewallPolicyRuleMatch(o interface{}) *compute.FirewallPolicyRuleMatch { + if o == nil { + return compute.EmptyFirewallPolicyRuleMatch + } + objArr := o.([]interface{}) + if len(objArr) == 0 { + return compute.EmptyFirewallPolicyRuleMatch + } + obj := objArr[0].(map[string]interface{}) + return &compute.FirewallPolicyRuleMatch{ + Layer4Configs: expandComputeFirewallPolicyRuleMatchLayer4ConfigsArray(obj["layer4_configs"]), + DestIPRanges: expandStringArray(obj["dest_ip_ranges"]), + SrcIPRanges: expandStringArray(obj["src_ip_ranges"]), + } +} + +func flattenComputeFirewallPolicyRuleMatch(obj *compute.FirewallPolicyRuleMatch) interface{} { + if obj == nil || obj.Empty() { + return nil + } + transformed := map[string]interface{}{ + "layer4_configs": flattenComputeFirewallPolicyRuleMatchLayer4ConfigsArray(obj.Layer4Configs), + "dest_ip_ranges": obj.DestIPRanges, + "src_ip_ranges": obj.SrcIPRanges, + } + + return []interface{}{transformed} + +} +func expandComputeFirewallPolicyRuleMatchLayer4ConfigsArray(o interface{}) []compute.FirewallPolicyRuleMatchLayer4Configs { + if o == nil { + return nil + } + + objs := o.([]interface{}) + if len(objs) == 0 { + return nil + } + + items := make([]compute.FirewallPolicyRuleMatchLayer4Configs, 0, len(objs)) + for _, item := range objs { + i := expandComputeFirewallPolicyRuleMatchLayer4Configs(item) + items = append(items, *i) + } + + return items +} + +func expandComputeFirewallPolicyRuleMatchLayer4Configs(o interface{}) *compute.FirewallPolicyRuleMatchLayer4Configs { + if o == nil { + return compute.EmptyFirewallPolicyRuleMatchLayer4Configs + } + + obj := o.(map[string]interface{}) + return &compute.FirewallPolicyRuleMatchLayer4Configs{ + IPProtocol: dcl.String(obj["ip_protocol"].(string)), + Ports: expandStringArray(obj["ports"]), + } +} + +func flattenComputeFirewallPolicyRuleMatchLayer4ConfigsArray(objs []compute.FirewallPolicyRuleMatchLayer4Configs) []interface{} { + if objs == nil { + return nil + } + + items := []interface{}{} + for _, item := range objs { + i := flattenComputeFirewallPolicyRuleMatchLayer4Configs(&item) + items = append(items, i) + } + + return items +} + +func flattenComputeFirewallPolicyRuleMatchLayer4Configs(obj *compute.FirewallPolicyRuleMatchLayer4Configs) interface{} { + if obj == nil || obj.Empty() { + return nil + } + transformed := map[string]interface{}{ + "ip_protocol": obj.IPProtocol, + "ports": obj.Ports, + } + + return transformed + +} diff --git a/google/resource_compute_firewall_policy_rule_test.go b/google/resource_compute_firewall_policy_rule_test.go new file mode 100644 index 00000000000..4ed20d1ac5d --- /dev/null +++ b/google/resource_compute_firewall_policy_rule_test.go @@ -0,0 +1,437 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccComputeFirewallPolicyRule_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "org_name": fmt.Sprintf("organizations/%s", getTestOrgFromEnv(t)), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeFirewallPolicyRule_start(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.default", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + Config: testAccComputeFirewallPolicyRule_update(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.default", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + Config: testAccComputeFirewallPolicyRule_removeConfigs(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.default", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + Config: testAccComputeFirewallPolicyRule_start(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.default", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + }, + }) +} + +func testAccComputeFirewallPolicyRule_start(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-sa-%{random_suffix}" +} + +resource "google_service_account" "service_account2" { + account_id = "tf-test-sa2-%{random_suffix}" +} + +resource "google_compute_network" "network1" { + name = "tf-test-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_network" "network2" { + name = "tf-test-2-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.name + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_rule" "default" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + dest_ip_ranges = ["11.100.0.1/32"] + } +} +`, context) +} + +func testAccComputeFirewallPolicyRule_update(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-sa-%{random_suffix}" +} + +resource "google_service_account" "service_account2" { + account_id = "tf-test-sa2-%{random_suffix}" +} + +resource "google_compute_network" "network1" { + name = "tf-test-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_network" "network2" { + name = "tf-test-2-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.name + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_rule" "default" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [8080] + } + layer4_configs { + ip_protocol = "udp" + ports = [22] + } + dest_ip_ranges = ["11.100.0.1/32", "10.0.0.0/24"] + } + target_resources = [google_compute_network.network1.self_link, google_compute_network.network2.self_link] + target_service_accounts = [google_service_account.service_account.email] +} +`, context) +} + +func testAccComputeFirewallPolicyRule_removeConfigs(context map[string]interface{}) string { + return Nprintf(` +resource "google_service_account" "service_account" { + account_id = "tf-test-sa-%{random_suffix}" +} + +resource "google_service_account" "service_account2" { + account_id = "tf-test-sa2-%{random_suffix}" +} + +resource "google_compute_network" "network1" { + name = "tf-test-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_compute_network" "network2" { + name = "tf-test-2-%{random_suffix}" + auto_create_subnetworks = false +} + +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.id + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_rule" "default" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Test description" + priority = 9000 + enable_logging = false + action = "deny" + direction = "INGRESS" + disabled = true + match { + layer4_configs { + ip_protocol = "udp" + ports = [22] + } + src_ip_ranges = ["11.100.0.1/32", "10.0.0.0/24"] + } + target_resources = [google_compute_network.network1.self_link] + target_service_accounts = [google_service_account.service_account.email, google_service_account.service_account2.email] +} +`, context) +} + +func TestAccComputeFirewallPolicyRule_multipleRules(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + "org_name": fmt.Sprintf("organizations/%s", getTestOrgFromEnv(t)), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccComputeFirewallPolicyRule_multiple(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.rule1", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + ResourceName: "google_compute_firewall_policy_rule.rule2", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + Config: testAccComputeFirewallPolicyRule_multipleAdd(context), + }, + { + ResourceName: "google_compute_firewall_policy_rule.rule3", + ImportState: true, + ImportStateVerify: true, + // Referencing using ID causes import to fail + ImportStateVerifyIgnore: []string{"firewall_policy"}, + }, + { + Config: testAccComputeFirewallPolicyRule_multipleRemove(context), + }, + }, + }) +} + +func testAccComputeFirewallPolicyRule_multiple(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.name + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_rule" "rule1" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + dest_ip_ranges = ["11.100.0.1/32"] + } +} + +resource "google_compute_firewall_policy_rule" "rule2" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9001 + enable_logging = false + action = "deny" + direction = "INGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + layer4_configs { + ip_protocol = "all" + } + src_ip_ranges = ["11.100.0.1/32"] + } +} +`, context) +} + +func testAccComputeFirewallPolicyRule_multipleAdd(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.id + short_name = "tf-test-policy-%{random_suffix}" + description = "Description Update" +} + +resource "google_compute_firewall_policy_rule" "rule1" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + } + dest_ip_ranges = ["11.100.0.1/32"] + } +} + +resource "google_compute_firewall_policy_rule" "rule2" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9001 + enable_logging = false + action = "deny" + direction = "INGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + layer4_configs { + ip_protocol = "all" + } + src_ip_ranges = ["11.100.0.1/32"] + } +} + +resource "google_compute_firewall_policy_rule" "rule3" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 40 + enable_logging = true + action = "allow" + direction = "INGRESS" + disabled = true + match { + layer4_configs { + ip_protocol = "udp" + ports = [8000] + } + src_ip_ranges = ["11.100.0.1/32", "10.0.0.0/24"] + } +} +`, context) +} + +func testAccComputeFirewallPolicyRule_multipleRemove(context map[string]interface{}) string { + return Nprintf(` +resource "google_folder" "folder" { + display_name = "tf-test-folder-%{random_suffix}" + parent = "%{org_name}" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.name + short_name = "tf-test-policy-%{random_suffix}" + description = "Resource created for Terraform acceptance testing" +} + +resource "google_compute_firewall_policy_rule" "rule1" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + dest_ip_ranges = ["11.100.0.1/32"] + } +} + +resource "google_compute_firewall_policy_rule" "rule3" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Resource created for Terraform acceptance testing" + priority = 40 + enable_logging = true + action = "allow" + direction = "INGRESS" + disabled = true + match { + layer4_configs { + ip_protocol = "udp" + ports = [8000] + } + src_ip_ranges = ["11.100.0.1/32", "10.0.0.0/24"] + } +} +`, context) +} diff --git a/google/resource_compute_firewall_policy_sweeper_test.go b/google/resource_compute_firewall_policy_sweeper_test.go new file mode 100644 index 00000000000..e37d305acf3 --- /dev/null +++ b/google/resource_compute_firewall_policy_sweeper_test.go @@ -0,0 +1,72 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: DCL *** +// +// ---------------------------------------------------------------------------- +// +// This file is managed by Magic Modules (https://github.com/GoogleCloudPlatform/magic-modules) +// and is based on the DCL (https://github.com/GoogleCloudPlatform/declarative-resource-client-library). +// Changes will need to be made to the DCL or Magic Modules instead of here. +// +// We are not currently able to accept contributions to this file. If changes +// are required, please file an issue at https://github.com/hashicorp/terraform-provider-google/issues/new/choose +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "context" + "log" + "testing" + + compute "github.com/GoogleCloudPlatform/declarative-resource-client-library/services/google/compute" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func init() { + resource.AddTestSweepers("ComputeFirewall_policy", &resource.Sweeper{ + Name: "ComputeFirewall_policy", + F: testSweepComputeFirewall_policy, + }) +} + +func testSweepComputeFirewall_policy(region string) error { + resourceName := "ComputeFirewall_policy" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := getTestBillingAccountFromEnv(t) + + // Setup variables to be used for Delete arguments. + d := map[string]string{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + } + + client := NewDCLComputeClient(config, config.userAgent, "") + err = client.DeleteAllFirewallPolicy(context.Background(), d["parent"], isDeletableComputeFirewall_policy) + if err != nil { + return err + } + return nil +} + +func isDeletableComputeFirewall_policy(r *compute.FirewallPolicy) bool { + return isSweepableTestResource(*r.Name) +} diff --git a/google/resource_compute_firewall_policy_test.go b/google/resource_compute_firewall_policy_test.go new file mode 100644 index 00000000000..ce231a10e72 --- /dev/null +++ b/google/resource_compute_firewall_policy_test.go @@ -0,0 +1,78 @@ +package google + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccComputeFirewallPolicy_update(t *testing.T) { + t.Parallel() + + org := getTestOrgFromEnv(t) + policyName := fmt.Sprintf("tf-test-firewall-policy-%s", randString(t, 10)) + folderName := fmt.Sprintf("tf-test-folder-%s", randString(t, 10)) + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckComputeFirewallDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeFirewallPolicy_basic(org, policyName, folderName), + }, + { + ResourceName: "google_compute_firewall_policy.default", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeFirewallPolicy_update(org, policyName, folderName), + }, + { + ResourceName: "google_compute_firewall_policy.default", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeFirewallPolicy_update(org, policyName, folderName), + }, + { + ResourceName: "google_compute_firewall_policy.default", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeFirewallPolicy_basic(org, policyName, folderName string) string { + return fmt.Sprintf(` +resource "google_folder" "folder" { + display_name = "%s" + parent = "%s" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.name + short_name = "%s" + description = "Resource created for Terraform acceptance testing" +} +`, folderName, "organizations/"+org, policyName) +} + +func testAccComputeFirewallPolicy_update(org, policyName, folderName string) string { + return fmt.Sprintf(` +resource "google_folder" "folder" { + display_name = "%s" + parent = "%s" +} + +resource "google_compute_firewall_policy" "default" { + parent = google_folder.folder.id + short_name = "%s" + description = "An updated description" +} +`, folderName, "organizations/"+org, policyName) +} diff --git a/website/docs/r/compute_firewall_policy.html.markdown b/website/docs/r/compute_firewall_policy.html.markdown new file mode 100644 index 00000000000..f87cd9ba5f2 --- /dev/null +++ b/website/docs/r/compute_firewall_policy.html.markdown @@ -0,0 +1,110 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: DCL *** +# +# ---------------------------------------------------------------------------- +# +# This file is managed by Magic Modules (https:#github.com/GoogleCloudPlatform/magic-modules) +# and is based on the DCL (https:#github.com/GoogleCloudPlatform/declarative-resource-client-library). +# Changes will need to be made to the DCL or Magic Modules instead of here. +# +# We are not currently able to accept contributions to this file. If changes +# are required, please file an issue at https:#github.com/hashicorp/terraform-provider-google/issues/new/choose +# +# ---------------------------------------------------------------------------- +subcategory: "Compute" +layout: "google" +page_title: "Google: google_compute_firewall_policy" +sidebar_current: "docs-google-compute-firewall-policy" +description: |- + Creates a hierarchical firewall policy +--- + +# google\_compute\_firewall\_policy + +Hierarchical firewall policy rules let you create and enforce a consistent firewall policy across your organization. Rules can explicitly allow or deny connections or delegate evaluation to lower level policies. Policies can be created within organizations or folders. + +This resource should be generally be used with `google_compute_firewall_policy_association` and `google_compute_firewall_policy_rule` + +For more information see the [official documentation](https://cloud.google.com/vpc/docs/firewall-policies) + +## Example Usage + +```hcl +resource "google_compute_firewall_policy" "default" { + parent = "organizations/12345" + short_name = "my-policy" + description = "Example Resource" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `parent` - + (Required) + The parent of the firewall policy. + +* `short_name` - + (Required) + User-provided name of the Organization firewall policy. The name should be unique in the organization in which the firewall policy is created. The name must be 1-63 characters long, and comply with RFC1035. Specifically, the name must be 1-63 characters long and match the regular expression [a-z]([-a-z0-9]*[a-z0-9])? which means the first character must be a lowercase letter, and all following characters must be a dash, lowercase letter, or digit, except the last character, which cannot be a dash. + + + +- - - + +* `description` - + (Optional) + An optional description of this resource. Provide this property when you create the resource. + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `locations/global/firewallPolicies/{{name}}` + +* `creation_timestamp` - + Creation timestamp in RFC3339 text format. + +* `fingerprint` - + Fingerprint of the resource. This field is used internally during updates of this resource. + +* `id` - + The unique identifier for the resource. This identifier is defined by the server. + +* `name` - + Name of the resource. It is a numeric ID allocated by GCP which uniquely identifies the Firewall Policy. + +* `rule_tuple_count` - + Total count of all firewall policy rule tuples. A firewall policy can not exceed a set number of tuples. + +* `self_link` - + Server-defined URL for the resource. + +* `self_link_with_id` - + Server-defined URL for this resource with the resource id. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 10 minutes. +- `update` - Default is 10 minutes. +- `delete` - Default is 10 minutes. + +## Import + +FirewallPolicy can be imported using any of these accepted formats: + +``` +$ terraform import google_compute_firewall_policy.default locations/global/firewallPolicies/{{name}} +$ terraform import google_compute_firewall_policy.default {{name}} +``` + + + diff --git a/website/docs/r/compute_firewall_policy_association.html.markdown b/website/docs/r/compute_firewall_policy_association.html.markdown new file mode 100644 index 00000000000..490a9e494ee --- /dev/null +++ b/website/docs/r/compute_firewall_policy_association.html.markdown @@ -0,0 +1,96 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: DCL *** +# +# ---------------------------------------------------------------------------- +# +# This file is managed by Magic Modules (https:#github.com/GoogleCloudPlatform/magic-modules) +# and is based on the DCL (https:#github.com/GoogleCloudPlatform/declarative-resource-client-library). +# Changes will need to be made to the DCL or Magic Modules instead of here. +# +# We are not currently able to accept contributions to this file. If changes +# are required, please file an issue at https:#github.com/hashicorp/terraform-provider-google/issues/new/choose +# +# ---------------------------------------------------------------------------- +subcategory: "Compute" +layout: "google" +page_title: "Google: google_compute_firewall_policy_association" +sidebar_current: "docs-google-compute-firewall-policy-association" +description: |- + Applies a hierarchical firewall policy to a target resource +--- + +# google\_compute\_firewall\_policy\_association + +Allows associating hierarchical firewall policies with the target where they are applied. This allows creating policies and rules in a different location than they are applied. + +For more information on applying hierarchical firewall policies see the [official documentation](https://cloud.google.com/vpc/docs/firewall-policies#managing_hierarchical_firewall_policy_resources) + +## Example Usage + +```hcl +resource "google_compute_firewall_policy" "default" { + parent = "organizations/12345" + short_name = "my-policy" + description = "Example Resource" +} + +resource "google_compute_firewall_policy_association" "default" { + firewall_policy = google_compute_firewall_policy.default.id + attachment_target = google_folder.folder.name + name = "my-association" +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `attachment_target` - + (Required) + The target that the firewall policy is attached to. + +* `firewall_policy` - + (Required) + The firewall policy ID of the association. + +* `name` - + (Required) + The name for an association. + + + +- - - + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `locations/global/firewallPolicies/{{firewall_policy}}/associations/{{name}}` + +* `short_name` - + The short name of the firewall policy of the association. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 10 minutes. +- `delete` - Default is 10 minutes. + +## Import + +FirewallPolicyAssociation can be imported using any of these accepted formats: + +``` +$ terraform import google_compute_firewall_policy_association.default locations/global/firewallPolicies/{{firewall_policy}}/associations/{{name}} +$ terraform import google_compute_firewall_policy_association.default {{firewall_policy}}/{{name}} +``` + + + diff --git a/website/docs/r/compute_firewall_policy_rule.html.markdown b/website/docs/r/compute_firewall_policy_rule.html.markdown new file mode 100644 index 00000000000..910a1da8467 --- /dev/null +++ b/website/docs/r/compute_firewall_policy_rule.html.markdown @@ -0,0 +1,162 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: DCL *** +# +# ---------------------------------------------------------------------------- +# +# This file is managed by Magic Modules (https:#github.com/GoogleCloudPlatform/magic-modules) +# and is based on the DCL (https:#github.com/GoogleCloudPlatform/declarative-resource-client-library). +# Changes will need to be made to the DCL or Magic Modules instead of here. +# +# We are not currently able to accept contributions to this file. If changes +# are required, please file an issue at https:#github.com/hashicorp/terraform-provider-google/issues/new/choose +# +# ---------------------------------------------------------------------------- +subcategory: "Compute" +layout: "google" +page_title: "Google: google_compute_firewall_policy_rule" +sidebar_current: "docs-google-compute-firewall-policy-rule" +description: |- + Specific rules to add to a hierarchical firewall policy +--- + +# google\_compute\_firewall\_policy\_rule + +Hierarchical firewall policy rules let you create and enforce a consistent firewall policy across your organization. Rules can explicitly allow or deny connections or delegate evaluation to lower level policies. + +For more information see the [official documentation](https://cloud.google.com/vpc/docs/using-firewall-policies#create-rules) + +## Example Usage + +```hcl +resource "google_compute_firewall_policy" "default" { + parent = "organizations/12345" + short_name = "my-policy" + description = "Example Resource" +} + +resource "google_compute_firewall_policy_rule" "default" { + firewall_policy = google_compute_firewall_policy.default.id + description = "Example Resource" + priority = 9000 + enable_logging = true + action = "allow" + direction = "EGRESS" + disabled = false + match { + layer4_configs { + ip_protocol = "tcp" + ports = [80, 8080] + } + dest_ip_ranges = ["11.100.0.1/32"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `action` - + (Required) + The Action to perform when the client connection triggers the rule. Can currently be either "allow" or "deny()" where valid values for status are 403, 404, and 502. + +* `direction` - + (Required) + The direction in which this rule applies. Possible values: INGRESS, EGRESS + +* `firewall_policy` - + (Required) + The firewall policy of the resource. + +* `match` - + (Required) + A match condition that incoming traffic is evaluated against. If it evaluates to true, the corresponding 'action' is enforced. + +* `priority` - + (Required) + An integer indicating the priority of a rule in the list. The priority must be a positive value between 0 and 2147483647. Rules are evaluated from highest to lowest priority where 0 is the highest priority and 2147483647 is the lowest prority. + + + +The `match` block supports: + +* `dest_ip_ranges` - + (Optional) + CIDR IP address range. Maximum number of destination CIDR IP ranges allowed is 256. + +* `layer4_configs` - + (Required) + Pairs of IP protocols and ports that the rule should match. + +* `src_ip_ranges` - + (Optional) + CIDR IP address range. Maximum number of source CIDR IP ranges allowed is 256. + +The `layer4_configs` block supports: + +* `ip_protocol` - + (Required) + The IP protocol to which this rule applies. The protocol type is required when creating a firewall rule. This value can either be one of the following well known protocol strings (`tcp`, `udp`, `icmp`, `esp`, `ah`, `ipip`, `sctp`), or the IP protocol number. + +* `ports` - + (Optional) + An optional list of ports to which this rule applies. This field is only applicable for UDP or TCP protocol. Each entry must be either an integer or a range. If not specified, this rule applies to connections through any port. Example inputs include: ``. + +- - - + +* `description` - + (Optional) + An optional description for this resource. + +* `disabled` - + (Optional) + Denotes whether the firewall policy rule is disabled. When set to true, the firewall policy rule is not enforced and traffic behaves as if it did not exist. If this is unspecified, the firewall policy rule will be enabled. + +* `enable_logging` - + (Optional) + Denotes whether to enable logging for a particular rule. If logging is enabled, logs will be exported to the configured export destination in Stackdriver. Logs may be exported to BigQuery or Pub/Sub. Note: you cannot enable logging on "goto_next" rules. + +* `target_resources` - + (Optional) + A list of network resource URLs to which this rule applies. This field allows you to control which network's VMs get this rule. If this field is left blank, all VMs within the organization will receive the rule. + +* `target_service_accounts` - + (Optional) + A list of service accounts indicating the sets of instances that are applied with this rule. + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}}` + +* `kind` - + Type of the resource. Always `compute#firewallPolicyRule` for firewall policy rules + +* `rule_tuple_count` - + Calculation of the complexity of a single firewall policy rule. + +## Timeouts + +This resource provides the following +[Timeouts](/docs/configuration/resources.html#timeouts) configuration options: + +- `create` - Default is 10 minutes. +- `update` - Default is 10 minutes. +- `delete` - Default is 10 minutes. + +## Import + +FirewallPolicyRule can be imported using any of these accepted formats: + +``` +$ terraform import google_compute_firewall_policy_rule.default locations/global/firewallPolicies/{{firewall_policy}}/rules/{{priority}} +$ terraform import google_compute_firewall_policy_rule.default {{firewall_policy}}/{{priority}} +``` + + + diff --git a/website/google.erb b/website/google.erb index dc77a0ce827..4578173664a 100644 --- a/website/google.erb +++ b/website/google.erb @@ -1557,6 +1557,30 @@ +
  • + Compute + +
  • +
  • Compute Engine