Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Resources: azurerm_firewall & azurerm_firewall_network_rule_collection #1627

Merged
merged 30 commits into from
Sep 13, 2018

Conversation

hbuckle
Copy link
Contributor

@hbuckle hbuckle commented Jul 22, 2018

I'm opening this to get some feedback on the design - so far I've split network rules into a separate resource from the firewall to make them easier to manage.
This does have a couple of drawbacks, first it makes the code more complicated because there isn't a separate client for rules, everything has to be done through the AzureFirewalls client. Second it's slow, because all the rules have to be added or updated individually and one at a time.
The alternative is to implement the whole thing as one resource including rules as a property, but this could be harder to manage from the user's perspective.
Thoughts?

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @hbuckle

Thanks for this PR :)

I've taken a look through and left some comments inline but this is off to a good start. I'd agree this makes sense split out as sub-resources - whilst it takes a little longer to provision it's a better user experience; do you think it'd make sense to do the same thing with the Rule resource too? I've held off reviewing the docs/tests for the moment whilst we hash out the design, but this is looking good 👍

Thanks!

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
)

var azureFirewallResourceName = "azurerm_azure_firewall"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should be able to make this azurerm_firewall

"ip_configuration": {
Type: schema.TypeList,
Required: true,
MinItems: 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor since this is Required - MinItems is inferred so we could remove this

if resp.IPConfigurations != nil {
ipConfigs := flattenArmAzureFirewallIPConfigurations(resp.IPConfigurations)
d.Set("ip_configuration", ipConfigs)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so there's a couple of things here:

  1. we should set this all the time (even if it's nil / when the API's broken) - since that allows changes to be detected
  2. since ip_configurations is a complex object - we should check for errors when setting it

as such can we update this to be:

ipConfigs := flattenArmAzureFirewallIPConfigurations(resp.IPConfigurations)
if err := d.Set("ip_configuration", ipConfigs); err != nil {
  return fmt.Errorf("Error setting `ip_configuration`: %+v", err)
}

(we can then check if resp.IPConfigurations is nil within the flatten function, and return an empty list there if needed)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on a separate note - is IPConfigurations available within the [XX]Properties object? the API's generally return these in the Properties block, which would be better to use if it's an option


func flattenArmAzureFirewallIPConfigurations(ipConfigs *[]network.AzureFirewallIPConfiguration) []interface{} {
result := make([]interface{}, 0, len(*ipConfigs))
for _, ipConfig := range *ipConfigs {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a couple of potential crashes here; can we make this:

result := make([]interface{}, 0)
if ipConfigs == nil {
  return result
}

result := make([]interface{}, 0, len(*ipConfigs))
for _, ipConfig := range *ipConfigs {
afIPConfig := make(map[string]interface{})
props := ipConfig.AzureFirewallIPConfigurationPropertiesFormat
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given props can be nil here - I think we want a nil-check after this? e.g.

if props == nil {
  continue
}

collections := *firewall.AzureFirewallPropertiesFormat.NetworkRuleCollections
for i, collection := range collections {
if collection.Name != nil && *collection.Name == name {
index = i
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may also be possible to call delete(collections, collection) here, since we're breaking anyway?

ruleToAdd := network.AzureFirewallNetworkRule{
Name: &name,
}
if description := rule["description"].(string); description != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're safe to assign this regardless (since if the key's nil in TF, we should reset the description?)

ruleToAdd.Protocols = &protocols
}
rules = append(rules, ruleToAdd)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(same for all of these) if the lists are nil can we set them anyway, since this'll reset the values to an empty list as needed

if rules != nil {
for _, rule := range *rules {
fwRule := make(map[string]interface{})
fwRule["name"] = *rule.Name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we nil-check this?

func flattenArmAzureFirewallNetworkRules(rules *[]network.AzureFirewallNetworkRule) []map[string]interface{} {
result := make([]map[string]interface{}, 0)

if rules != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor we could probably invert this to make it more readable, but it's not a blocker by any means :)

@hbuckle
Copy link
Contributor Author

hbuckle commented Jul 23, 2018

Thanks for the review so far - I'll carry on and add the application rules as a third resource.
I did originally want to make rule collections and rules separate resources, unfortunately the API doesn't allow rule collections to be created without rules in them, so I don't think it will be possible.

Copy link
Contributor

@tombuildsstuff tombuildsstuff left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey @hbuckle

Thanks for pushing those changes - I've taken a look through and reviewed the other half of this (Network Rule Collections). Regarding the other new resource, since this PR's already quite large / whilst it's related - would you mind if we pulled that out into a separate PR?

Thanks!

@@ -138,6 +138,8 @@ func Provider() terraform.ResourceProvider {
"azurerm_automation_schedule": resourceArmAutomationSchedule(),
"azurerm_autoscale_setting": resourceArmAutoScaleSetting(),
"azurerm_availability_set": resourceArmAvailabilitySet(),
"azurerm_azure_firewall": resourceArmAzureFirewall(),
"azurerm_azure_firewall_network_rule_collection": resourceArmAzureFirewallNetworkRuleCollection(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this azurerm_firewall and azurerm_firewall_network_rule respectively? I think the extra azure is redundant in each case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although it looks a little unwieldy Azure Firewall is the name of the product, so the naming is consistent. Lots of things have firewalls in Azure so I think this makes it clear exactly which product is being managed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a fair point - but in general we ditch the word Azure from the front (e.g. Azure Kubernetes Service is azurerm_kubernetes_cluster / Azure Managed Database for MySQL becomes azurerm_mysql_server); so I think we should ditch it to be consistent with the other resources (the sole exception to this is azurerm_azuread, because that's how it's commonly referred too).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tombuildsstuff OK, i've renamed everything to azurerm_firewall

name := d.Get("name").(string)
location := azureRMNormalizeLocation(d.Get("location").(string))
tags := d.Get("tags").(map[string]interface{})
ipConfigs, subnetToLock, vnetToLock, sgErr := expandArmAzureFirewallIPConfigurations(d)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor we can rename this from sgErr to err and reuse the variable below, rather than defining a specific error type

return fmt.Errorf("Error making Read request on Azure Firewall %q (Resource Group %q): %+v", name, resourceGroup, err)
}

ipConfigs := flattenArmAzureFirewallIPConfigurations(firewall.AzureFirewallPropertiesFormat.IPConfigurations)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's possible that firewall.AzureFirewallPropertiesFormat could be nil here, either through a bad API response or invalid Swagger in future; can we make this:

if props := firewall.AzureFirewallPropertiesFormat; props != nil {
  ipConfigs := flattenArmAzureFirewallIPConfigurations(firewall.AzureFirewallPropertiesFormat.IPConfigurations)
  if err := d.Set("ip_configuration", ipConfigs); err != nil {
    return fmt.Errorf("Error setting `ip_configuration`: %+v", err)
  }
}

return err
}

func expandArmAzureFirewallIPConfigurations(d *schema.ResourceData) ([]network.AzureFirewallIPConfiguration, *[]string, *[]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can return a pointer to a list here (*[]network.AzureFirewallIPConfiguration) which would allow us to return nil below where we currently return an empty list. the benefit of doing this is it means any unexpected code paths/accesses of this returned value would crash (which will be caught during testing)

}
}

if ipAddress := ipConfig.PrivateIPAddress; ipAddress != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we access this via props instead of via ipConfig?

ruleToAdd.Description = &description
ruleToAdd.SourceAddresses = expandArmAzureFirewallSet(sourceAddresses)
ruleToAdd.DestinationAddresses = expandArmAzureFirewallSet(destinationAddresses)
ruleToAdd.DestinationPorts = expandArmAzureFirewallSet(destinationPorts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor can we in-line these? (also we can use utils.String(val) rather than & for consistency with the other resources) e.g.

ruleToAdd := network.AzureFirewallNetworkRule{
  Name: &name,
  Description: utils.String(description),
  SourceAddresses: expandArmAzureFirewallSet(sourceAddresses),
  DestinationAddresses: expandArmAzureFirewallSet(destinationAddresses),
  DestinationPorts: expandArmAzureFirewallSet(destinationPorts),
}

page_title: "Azure Resource Manager: azurerm_azure_firewall"
sidebar_current: "docs-azurerm-resource-azurefirewall-x"
description: |-
Create an Azure Firewall.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor can we make this Manages an Azure Firewall


Create an Azure Firewall.

~> **NOTE** This resource is currently in public preview.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this in invite-only preview, or can customers opt-in?


* `name` - (Required) Specifies the name of the ip configuration.
* `subnet_id` - (Required) Reference to the subnet associated with the ip configuration.
* `internal_public_ip_address_id` - (Required) Reference to the public IP address associated with the firewall.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add the above note below this resource?

~> **NOTE** The public IP must have a `Static` allocation and `Standard` sku

`ip_configuration` supports the following:

* `name` - (Required) Specifies the name of the ip configuration.
* `subnet_id` - (Required) Reference to the subnet associated with the ip configuration.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add the above note below this resource?

~> **NOTE** The firewall subnet must be called `AzureFirewallSubnet` and the subnet mask must be at least `/25`

@hbuckle
Copy link
Contributor Author

hbuckle commented Jul 25, 2018

@tombuildsstuff Thanks, think I have fixed everything up. I'll review all the tests today then look at a separate PR to add application rule collections

@tombuildsstuff
Copy link
Contributor

@hbuckle hey sorry for the delayed re-review here - I'll take another look through this today

@ghost ghost added the size/XXL label Sep 12, 2018
@tombuildsstuff tombuildsstuff changed the title [WIP] Azure Firewall Azure Firewall Sep 12, 2018
@tombuildsstuff tombuildsstuff dismissed their stale review September 12, 2018 16:43

dismissing since I've pushed changes

- Locking on the Firewall Name
- Handling resources being deleted outside of Terraform
- Removing some crash points
- Making the Protocol and Action type case-sensitive
- Refactoring the virtual resource to allow for
- Parsing the ID rather than using the config for the delete and read functions (so delete's are successful when the config's gone)
- Rewriting some of the tests for the Network Rule Collections, to check the resource's state rather than the object
- Updating the documentation (and including Import support for Network Rule Collections)
@tombuildsstuff
Copy link
Contributor

@hbuckle I thought I'd posted this message yesterday - apologies.

I ended up rebasing this yesterday and taking a look into what's needed to get this merged - and I've pushed a commit with some changes to finish this off (I hope you don't mind!) - in particular I've updated the Read and Delete functions to parse the ID of the Network Rule Collection rather than reading from the config (since this isn't always present during Import/Delete's) and updated some of the tests.

Since I've pushed some larger changes here (since this is a couple of resources) - I'm going to as @katbyte to take a look into this - but I'm going to kick off the tests now.

Thanks!

@tombuildsstuff tombuildsstuff added this to the 1.15.0 milestone Sep 12, 2018
@tombuildsstuff tombuildsstuff changed the title Azure Firewall New Resources: azurerm_firewall & azurerm_firewall_network_rule_collection Sep 12, 2018
Copy link
Collaborator

@katbyte katbyte left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hbuckle, @tombuildsstuff,

This pretty much LGTM 👍 All the comments I have left inline are pretty minor and not blockers so i'm going to approve this.

My main concern is TestAccAzureRMFirewall_importBasic being separate, while the rule collection is part of the standard tests. I think it should be merged in to the standard TestAccAzureRMFirewall tests as well.

azurerm/import_arm_azure_firewall_test.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall_network_rule_collection.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall_network_rule_collection.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall_network_rule_collection.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall_network_rule_collection.go Outdated Show resolved Hide resolved
azurerm/resource_arm_firewall_network_rule_collection.go Outdated Show resolved Hide resolved
azurerm/helpers/azure/firewall.go Outdated Show resolved Hide resolved
```
$ acctests azurerm TestValidateFirewallName

=== RUN   TestValidateFirewallName
--- PASS: TestValidateFirewallName (0.00s)
PASS
ok  	github.com/terraform-providers/terraform-provider-azurerm/azurerm	0.344s
```
@hbuckle
Copy link
Contributor Author

hbuckle commented Sep 13, 2018

@tombuildsstuff Thanks for the update, I'm sure my go skills still leave something to be desired. I'll have another look through this today. I've started the application rule collection on another branch as well.
The publicipaddress vs internalpublicipaddress has been resolved in the specs as well so we should be able to remove the workaround for that once it gets into the SDK

1 similar comment
@hbuckle
Copy link
Contributor Author

hbuckle commented Sep 13, 2018

@tombuildsstuff Thanks for the update, I'm sure my go skills still leave something to be desired. I'll have another look through this today. I've started the application rule collection on another branch as well.
The publicipaddress vs internalpublicipaddress has been resolved in the specs as well so we should be able to remove the workaround for that once it gets into the SDK

@tombuildsstuff
Copy link
Contributor

tombuildsstuff commented Sep 13, 2018

@tombuildsstuff Thanks for the update, I'm sure my go skills still leave something to be desired. I'll have another look through this today.

Not at all, thanks for getting this most of the way there - apologies this sat for a while, we're still trying to work through the open PR's.

I've started the application rule collection on another branch as well.

That's awesome 👍 - thanks!

The publicipaddress vs internalpublicipaddress has been resolved in the specs as well so we should be able to remove the workaround for that once it gets into the SDK

Cool, that's good to know - I'll keep an eye on the SDK release and add a TODO for that

Thanks!

@ghost ghost added the size/XXL label Sep 13, 2018
@tombuildsstuff
Copy link
Contributor

Tests pass:

screenshot 2018-09-13 at 22 46 34

@tombuildsstuff tombuildsstuff merged commit 08ef14e into hashicorp:master Sep 13, 2018
tombuildsstuff added a commit that referenced this pull request Sep 13, 2018
@ghost
Copy link

ghost commented Mar 6, 2019

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks!

@ghost ghost locked and limited conversation to collaborators Mar 6, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants