github.com/jdextraze/terraform@v0.6.17-0.20160511153921-e33847c8a8af/builtin/providers/aws/resource_aws_network_acl.go (about)

     1  package aws
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"sort"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/aws/awserr"
    13  	"github.com/aws/aws-sdk-go/service/ec2"
    14  	"github.com/hashicorp/terraform/helper/hashcode"
    15  	"github.com/hashicorp/terraform/helper/resource"
    16  	"github.com/hashicorp/terraform/helper/schema"
    17  )
    18  
    19  func resourceAwsNetworkAcl() *schema.Resource {
    20  
    21  	return &schema.Resource{
    22  		Create: resourceAwsNetworkAclCreate,
    23  		Read:   resourceAwsNetworkAclRead,
    24  		Delete: resourceAwsNetworkAclDelete,
    25  		Update: resourceAwsNetworkAclUpdate,
    26  
    27  		Schema: map[string]*schema.Schema{
    28  			"vpc_id": &schema.Schema{
    29  				Type:     schema.TypeString,
    30  				Required: true,
    31  				ForceNew: true,
    32  				Computed: false,
    33  			},
    34  			"subnet_id": &schema.Schema{
    35  				Type:       schema.TypeString,
    36  				Optional:   true,
    37  				ForceNew:   true,
    38  				Computed:   false,
    39  				Deprecated: "Attribute subnet_id is deprecated on network_acl resources. Use subnet_ids instead",
    40  			},
    41  			"subnet_ids": &schema.Schema{
    42  				Type:          schema.TypeSet,
    43  				Optional:      true,
    44  				Computed:      true,
    45  				ConflictsWith: []string{"subnet_id"},
    46  				Elem:          &schema.Schema{Type: schema.TypeString},
    47  				Set:           schema.HashString,
    48  			},
    49  			"ingress": &schema.Schema{
    50  				Type:     schema.TypeSet,
    51  				Required: false,
    52  				Optional: true,
    53  				Computed: true,
    54  				Elem: &schema.Resource{
    55  					Schema: map[string]*schema.Schema{
    56  						"from_port": &schema.Schema{
    57  							Type:     schema.TypeInt,
    58  							Required: true,
    59  						},
    60  						"to_port": &schema.Schema{
    61  							Type:     schema.TypeInt,
    62  							Required: true,
    63  						},
    64  						"rule_no": &schema.Schema{
    65  							Type:     schema.TypeInt,
    66  							Required: true,
    67  						},
    68  						"action": &schema.Schema{
    69  							Type:     schema.TypeString,
    70  							Required: true,
    71  						},
    72  						"protocol": &schema.Schema{
    73  							Type:     schema.TypeString,
    74  							Required: true,
    75  						},
    76  						"cidr_block": &schema.Schema{
    77  							Type:     schema.TypeString,
    78  							Optional: true,
    79  						},
    80  						"icmp_type": &schema.Schema{
    81  							Type:     schema.TypeInt,
    82  							Optional: true,
    83  						},
    84  						"icmp_code": &schema.Schema{
    85  							Type:     schema.TypeInt,
    86  							Optional: true,
    87  						},
    88  					},
    89  				},
    90  				Set: resourceAwsNetworkAclEntryHash,
    91  			},
    92  			"egress": &schema.Schema{
    93  				Type:     schema.TypeSet,
    94  				Required: false,
    95  				Optional: true,
    96  				Computed: true,
    97  				Elem: &schema.Resource{
    98  					Schema: map[string]*schema.Schema{
    99  						"from_port": &schema.Schema{
   100  							Type:     schema.TypeInt,
   101  							Required: true,
   102  						},
   103  						"to_port": &schema.Schema{
   104  							Type:     schema.TypeInt,
   105  							Required: true,
   106  						},
   107  						"rule_no": &schema.Schema{
   108  							Type:     schema.TypeInt,
   109  							Required: true,
   110  						},
   111  						"action": &schema.Schema{
   112  							Type:     schema.TypeString,
   113  							Required: true,
   114  						},
   115  						"protocol": &schema.Schema{
   116  							Type:     schema.TypeString,
   117  							Required: true,
   118  						},
   119  						"cidr_block": &schema.Schema{
   120  							Type:     schema.TypeString,
   121  							Optional: true,
   122  						},
   123  						"icmp_type": &schema.Schema{
   124  							Type:     schema.TypeInt,
   125  							Optional: true,
   126  						},
   127  						"icmp_code": &schema.Schema{
   128  							Type:     schema.TypeInt,
   129  							Optional: true,
   130  						},
   131  					},
   132  				},
   133  				Set: resourceAwsNetworkAclEntryHash,
   134  			},
   135  			"tags": tagsSchema(),
   136  		},
   137  	}
   138  }
   139  
   140  func resourceAwsNetworkAclCreate(d *schema.ResourceData, meta interface{}) error {
   141  
   142  	conn := meta.(*AWSClient).ec2conn
   143  
   144  	// Create the Network Acl
   145  	createOpts := &ec2.CreateNetworkAclInput{
   146  		VpcId: aws.String(d.Get("vpc_id").(string)),
   147  	}
   148  
   149  	log.Printf("[DEBUG] Network Acl create config: %#v", createOpts)
   150  	resp, err := conn.CreateNetworkAcl(createOpts)
   151  	if err != nil {
   152  		return fmt.Errorf("Error creating network acl: %s", err)
   153  	}
   154  
   155  	// Get the ID and store it
   156  	networkAcl := resp.NetworkAcl
   157  	d.SetId(*networkAcl.NetworkAclId)
   158  	log.Printf("[INFO] Network Acl ID: %s", *networkAcl.NetworkAclId)
   159  
   160  	// Update rules and subnet association once acl is created
   161  	return resourceAwsNetworkAclUpdate(d, meta)
   162  }
   163  
   164  func resourceAwsNetworkAclRead(d *schema.ResourceData, meta interface{}) error {
   165  	conn := meta.(*AWSClient).ec2conn
   166  
   167  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   168  		NetworkAclIds: []*string{aws.String(d.Id())},
   169  	})
   170  
   171  	if err != nil {
   172  		if ec2err, ok := err.(awserr.Error); ok {
   173  			if ec2err.Code() == "InvalidNetworkAclID.NotFound" {
   174  				log.Printf("[DEBUG] Network ACL (%s) not found", d.Id())
   175  				d.SetId("")
   176  				return nil
   177  			}
   178  		}
   179  		return err
   180  	}
   181  	if resp == nil {
   182  		return nil
   183  	}
   184  
   185  	networkAcl := resp.NetworkAcls[0]
   186  	var ingressEntries []*ec2.NetworkAclEntry
   187  	var egressEntries []*ec2.NetworkAclEntry
   188  
   189  	// separate the ingress and egress rules
   190  	for _, e := range networkAcl.Entries {
   191  		// Skip the default rules added by AWS. They can be neither
   192  		// configured or deleted by users.
   193  		if *e.RuleNumber == awsDefaultAclRuleNumber {
   194  			continue
   195  		}
   196  
   197  		if *e.Egress == true {
   198  			egressEntries = append(egressEntries, e)
   199  		} else {
   200  			ingressEntries = append(ingressEntries, e)
   201  		}
   202  	}
   203  
   204  	d.Set("vpc_id", networkAcl.VpcId)
   205  	d.Set("tags", tagsToMap(networkAcl.Tags))
   206  
   207  	var s []string
   208  	for _, a := range networkAcl.Associations {
   209  		s = append(s, *a.SubnetId)
   210  	}
   211  	sort.Strings(s)
   212  	if err := d.Set("subnet_ids", s); err != nil {
   213  		return err
   214  	}
   215  
   216  	if err := d.Set("ingress", networkAclEntriesToMapList(ingressEntries)); err != nil {
   217  		return err
   218  	}
   219  	if err := d.Set("egress", networkAclEntriesToMapList(egressEntries)); err != nil {
   220  		return err
   221  	}
   222  
   223  	return nil
   224  }
   225  
   226  func resourceAwsNetworkAclUpdate(d *schema.ResourceData, meta interface{}) error {
   227  	conn := meta.(*AWSClient).ec2conn
   228  	d.Partial(true)
   229  
   230  	if d.HasChange("ingress") {
   231  		err := updateNetworkAclEntries(d, "ingress", conn)
   232  		if err != nil {
   233  			return err
   234  		}
   235  	}
   236  
   237  	if d.HasChange("egress") {
   238  		err := updateNetworkAclEntries(d, "egress", conn)
   239  		if err != nil {
   240  			return err
   241  		}
   242  	}
   243  
   244  	if d.HasChange("subnet_id") {
   245  		//associate new subnet with the acl.
   246  		_, n := d.GetChange("subnet_id")
   247  		newSubnet := n.(string)
   248  		association, err := findNetworkAclAssociation(newSubnet, conn)
   249  		if err != nil {
   250  			return fmt.Errorf("Failed to update acl %s with subnet %s: %s", d.Id(), newSubnet, err)
   251  		}
   252  		_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   253  			AssociationId: association.NetworkAclAssociationId,
   254  			NetworkAclId:  aws.String(d.Id()),
   255  		})
   256  		if err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	if d.HasChange("subnet_ids") {
   262  		o, n := d.GetChange("subnet_ids")
   263  		if o == nil {
   264  			o = new(schema.Set)
   265  		}
   266  		if n == nil {
   267  			n = new(schema.Set)
   268  		}
   269  
   270  		os := o.(*schema.Set)
   271  		ns := n.(*schema.Set)
   272  
   273  		remove := os.Difference(ns).List()
   274  		add := ns.Difference(os).List()
   275  
   276  		if len(remove) > 0 {
   277  			// A Network ACL is required for each subnet. In order to disassociate a
   278  			// subnet from this ACL, we must associate it with the default ACL.
   279  			defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn)
   280  			if err != nil {
   281  				return fmt.Errorf("Failed to find Default ACL for VPC %s", d.Get("vpc_id").(string))
   282  			}
   283  			for _, r := range remove {
   284  				association, err := findNetworkAclAssociation(r.(string), conn)
   285  				if err != nil {
   286  					return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), r, err)
   287  				}
   288  				log.Printf("DEBUG] Replacing Network Acl Association (%s) with Default Network ACL ID (%s)", *association.NetworkAclAssociationId, *defaultAcl.NetworkAclId)
   289  				_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   290  					AssociationId: association.NetworkAclAssociationId,
   291  					NetworkAclId:  defaultAcl.NetworkAclId,
   292  				})
   293  				if err != nil {
   294  					return err
   295  				}
   296  			}
   297  		}
   298  
   299  		if len(add) > 0 {
   300  			for _, a := range add {
   301  				association, err := findNetworkAclAssociation(a.(string), conn)
   302  				if err != nil {
   303  					return fmt.Errorf("Failed to find acl association: acl %s with subnet %s: %s", d.Id(), a, err)
   304  				}
   305  				_, err = conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   306  					AssociationId: association.NetworkAclAssociationId,
   307  					NetworkAclId:  aws.String(d.Id()),
   308  				})
   309  				if err != nil {
   310  					return err
   311  				}
   312  			}
   313  		}
   314  
   315  	}
   316  
   317  	if err := setTags(conn, d); err != nil {
   318  		return err
   319  	} else {
   320  		d.SetPartial("tags")
   321  	}
   322  
   323  	d.Partial(false)
   324  	return resourceAwsNetworkAclRead(d, meta)
   325  }
   326  
   327  func updateNetworkAclEntries(d *schema.ResourceData, entryType string, conn *ec2.EC2) error {
   328  	if d.HasChange(entryType) {
   329  		o, n := d.GetChange(entryType)
   330  
   331  		if o == nil {
   332  			o = new(schema.Set)
   333  		}
   334  		if n == nil {
   335  			n = new(schema.Set)
   336  		}
   337  
   338  		os := o.(*schema.Set)
   339  		ns := n.(*schema.Set)
   340  
   341  		toBeDeleted, err := expandNetworkAclEntries(os.Difference(ns).List(), entryType)
   342  		if err != nil {
   343  			return err
   344  		}
   345  		for _, remove := range toBeDeleted {
   346  			// AWS includes default rules with all network ACLs that can be
   347  			// neither modified nor destroyed. They have a custom rule
   348  			// number that is out of bounds for any other rule. If we
   349  			// encounter it, just continue. There's no work to be done.
   350  			if *remove.RuleNumber == awsDefaultAclRuleNumber {
   351  				continue
   352  			}
   353  
   354  			// Delete old Acl
   355  			log.Printf("[DEBUG] Destroying Network ACL Entry number (%d)", int(*remove.RuleNumber))
   356  			_, err := conn.DeleteNetworkAclEntry(&ec2.DeleteNetworkAclEntryInput{
   357  				NetworkAclId: aws.String(d.Id()),
   358  				RuleNumber:   remove.RuleNumber,
   359  				Egress:       remove.Egress,
   360  			})
   361  			if err != nil {
   362  				return fmt.Errorf("Error deleting %s entry: %s", entryType, err)
   363  			}
   364  		}
   365  
   366  		toBeCreated, err := expandNetworkAclEntries(ns.Difference(os).List(), entryType)
   367  		if err != nil {
   368  			return err
   369  		}
   370  		for _, add := range toBeCreated {
   371  			// Protocol -1 rules don't store ports in AWS. Thus, they'll always
   372  			// hash differently when being read out of the API. Force the user
   373  			// to set from_port and to_port to 0 for these rules, to keep the
   374  			// hashing consistent.
   375  			if *add.Protocol == "-1" {
   376  				to := *add.PortRange.To
   377  				from := *add.PortRange.From
   378  				expected := &expectedPortPair{
   379  					to_port:   0,
   380  					from_port: 0,
   381  				}
   382  				if ok := validatePorts(to, from, *expected); !ok {
   383  					return fmt.Errorf(
   384  						"to_port (%d) and from_port (%d) must both be 0 to use the the 'all' \"-1\" protocol!",
   385  						to, from)
   386  				}
   387  			}
   388  
   389  			// AWS mutates the CIDR block into a network implied by the IP and
   390  			// mask provided. This results in hashing inconsistencies between
   391  			// the local config file and the state returned by the API. Error
   392  			// if the user provides a CIDR block with an inappropriate mask
   393  			if err := validateCIDRBlock(*add.CidrBlock); err != nil {
   394  				return err
   395  			}
   396  
   397  			// Add new Acl entry
   398  			_, connErr := conn.CreateNetworkAclEntry(&ec2.CreateNetworkAclEntryInput{
   399  				NetworkAclId: aws.String(d.Id()),
   400  				CidrBlock:    add.CidrBlock,
   401  				Egress:       add.Egress,
   402  				PortRange:    add.PortRange,
   403  				Protocol:     add.Protocol,
   404  				RuleAction:   add.RuleAction,
   405  				RuleNumber:   add.RuleNumber,
   406  				IcmpTypeCode: add.IcmpTypeCode,
   407  			})
   408  			if connErr != nil {
   409  				return fmt.Errorf("Error creating %s entry: %s", entryType, connErr)
   410  			}
   411  		}
   412  	}
   413  	return nil
   414  }
   415  
   416  func resourceAwsNetworkAclDelete(d *schema.ResourceData, meta interface{}) error {
   417  	conn := meta.(*AWSClient).ec2conn
   418  
   419  	log.Printf("[INFO] Deleting Network Acl: %s", d.Id())
   420  	retryErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
   421  		_, err := conn.DeleteNetworkAcl(&ec2.DeleteNetworkAclInput{
   422  			NetworkAclId: aws.String(d.Id()),
   423  		})
   424  		if err != nil {
   425  			ec2err := err.(awserr.Error)
   426  			switch ec2err.Code() {
   427  			case "InvalidNetworkAclID.NotFound":
   428  				return nil
   429  			case "DependencyViolation":
   430  				// In case of dependency violation, we remove the association between subnet and network acl.
   431  				// This means the subnet is attached to default acl of vpc.
   432  				var associations []*ec2.NetworkAclAssociation
   433  				if v, ok := d.GetOk("subnet_id"); ok {
   434  
   435  					a, err := findNetworkAclAssociation(v.(string), conn)
   436  					if err != nil {
   437  						return resource.NonRetryableError(err)
   438  					}
   439  					associations = append(associations, a)
   440  				} else if v, ok := d.GetOk("subnet_ids"); ok {
   441  					ids := v.(*schema.Set).List()
   442  					for _, i := range ids {
   443  						a, err := findNetworkAclAssociation(i.(string), conn)
   444  						if err != nil {
   445  							return resource.NonRetryableError(err)
   446  						}
   447  						associations = append(associations, a)
   448  					}
   449  				}
   450  
   451  				log.Printf("[DEBUG] Replacing network associations for Network ACL (%s): %s", d.Id(), associations)
   452  				defaultAcl, err := getDefaultNetworkAcl(d.Get("vpc_id").(string), conn)
   453  				if err != nil {
   454  					return resource.NonRetryableError(err)
   455  				}
   456  
   457  				for _, a := range associations {
   458  					log.Printf("DEBUG] Replacing Network Acl Association (%s) with Default Network ACL ID (%s)", *a.NetworkAclAssociationId, *defaultAcl.NetworkAclId)
   459  					_, replaceErr := conn.ReplaceNetworkAclAssociation(&ec2.ReplaceNetworkAclAssociationInput{
   460  						AssociationId: a.NetworkAclAssociationId,
   461  						NetworkAclId:  defaultAcl.NetworkAclId,
   462  					})
   463  					if replaceErr != nil {
   464  						if replaceEc2err, ok := replaceErr.(awserr.Error); ok {
   465  							// It's possible that during an attempt to replace this
   466  							// association, the Subnet in question has already been moved to
   467  							// another ACL. This can happen if you're destroying a network acl
   468  							// and simultaneously re-associating it's subnet(s) with another
   469  							// ACL; Terraform may have already re-associated the subnet(s) by
   470  							// the time we attempt to destroy them, even between the time we
   471  							// list them and then try to destroy them. In this case, the
   472  							// association we're trying to replace will no longer exist and
   473  							// this call will fail. Here we trap that error and fail
   474  							// gracefully; the association we tried to replace gone, we trust
   475  							// someone else has taken ownership.
   476  							if replaceEc2err.Code() == "InvalidAssociationID.NotFound" {
   477  								log.Printf("[WARN] Network Association (%s) no longer found; Network Association likely updated or removed externally, removing from state", *a.NetworkAclAssociationId)
   478  								continue
   479  							}
   480  						}
   481  						log.Printf("[ERR] Non retry-able error in replacing associations for Network ACL (%s): %s", d.Id(), replaceErr)
   482  						return resource.NonRetryableError(replaceErr)
   483  					}
   484  				}
   485  				return resource.RetryableError(fmt.Errorf("Dependencies found and cleaned up, retrying"))
   486  			default:
   487  				// Any other error, we want to quit the retry loop immediately
   488  				return resource.NonRetryableError(err)
   489  			}
   490  		}
   491  		log.Printf("[Info] Deleted network ACL %s successfully", d.Id())
   492  		return nil
   493  	})
   494  
   495  	if retryErr != nil {
   496  		return fmt.Errorf("[ERR] Error destroying Network ACL (%s): %s", d.Id(), retryErr)
   497  	}
   498  	return nil
   499  }
   500  
   501  func resourceAwsNetworkAclEntryHash(v interface{}) int {
   502  	var buf bytes.Buffer
   503  	m := v.(map[string]interface{})
   504  	buf.WriteString(fmt.Sprintf("%d-", m["from_port"].(int)))
   505  	buf.WriteString(fmt.Sprintf("%d-", m["to_port"].(int)))
   506  	buf.WriteString(fmt.Sprintf("%d-", m["rule_no"].(int)))
   507  	buf.WriteString(fmt.Sprintf("%s-", m["action"].(string)))
   508  
   509  	// The AWS network ACL API only speaks protocol numbers, and that's
   510  	// all we store. Never hash a protocol name.
   511  	protocol := m["protocol"].(string)
   512  	if _, err := strconv.Atoi(m["protocol"].(string)); err != nil {
   513  		// We're a protocol name. Look up the number.
   514  		buf.WriteString(fmt.Sprintf("%d-", protocolIntegers()[protocol]))
   515  	} else {
   516  		// We're a protocol number. Pass the value through.
   517  		buf.WriteString(fmt.Sprintf("%s-", protocol))
   518  	}
   519  
   520  	buf.WriteString(fmt.Sprintf("%s-", m["cidr_block"].(string)))
   521  
   522  	if v, ok := m["ssl_certificate_id"]; ok {
   523  		buf.WriteString(fmt.Sprintf("%s-", v.(string)))
   524  	}
   525  
   526  	if v, ok := m["icmp_type"]; ok {
   527  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   528  	}
   529  	if v, ok := m["icmp_code"]; ok {
   530  		buf.WriteString(fmt.Sprintf("%d-", v.(int)))
   531  	}
   532  
   533  	return hashcode.String(buf.String())
   534  }
   535  
   536  func getDefaultNetworkAcl(vpc_id string, conn *ec2.EC2) (defaultAcl *ec2.NetworkAcl, err error) {
   537  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   538  		Filters: []*ec2.Filter{
   539  			&ec2.Filter{
   540  				Name:   aws.String("default"),
   541  				Values: []*string{aws.String("true")},
   542  			},
   543  			&ec2.Filter{
   544  				Name:   aws.String("vpc-id"),
   545  				Values: []*string{aws.String(vpc_id)},
   546  			},
   547  		},
   548  	})
   549  
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	return resp.NetworkAcls[0], nil
   554  }
   555  
   556  func findNetworkAclAssociation(subnetId string, conn *ec2.EC2) (networkAclAssociation *ec2.NetworkAclAssociation, err error) {
   557  	resp, err := conn.DescribeNetworkAcls(&ec2.DescribeNetworkAclsInput{
   558  		Filters: []*ec2.Filter{
   559  			&ec2.Filter{
   560  				Name:   aws.String("association.subnet-id"),
   561  				Values: []*string{aws.String(subnetId)},
   562  			},
   563  		},
   564  	})
   565  
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	if resp.NetworkAcls != nil && len(resp.NetworkAcls) > 0 {
   570  		for _, association := range resp.NetworkAcls[0].Associations {
   571  			if *association.SubnetId == subnetId {
   572  				return association, nil
   573  			}
   574  		}
   575  	}
   576  	return nil, fmt.Errorf("could not find association for subnet: %s ", subnetId)
   577  }
   578  
   579  // networkAclEntriesToMapList turns ingress/egress rules read from AWS into a list
   580  // of maps.
   581  func networkAclEntriesToMapList(networkAcls []*ec2.NetworkAclEntry) []map[string]interface{} {
   582  	result := make([]map[string]interface{}, 0, len(networkAcls))
   583  	for _, entry := range networkAcls {
   584  		acl := make(map[string]interface{})
   585  		acl["rule_no"] = *entry.RuleNumber
   586  		acl["action"] = *entry.RuleAction
   587  		acl["cidr_block"] = *entry.CidrBlock
   588  
   589  		// The AWS network ACL API only speaks protocol numbers, and
   590  		// that's all we record.
   591  		if _, err := strconv.Atoi(*entry.Protocol); err != nil {
   592  			// We're a protocol name. Look up the number.
   593  			acl["protocol"] = protocolIntegers()[*entry.Protocol]
   594  		} else {
   595  			// We're a protocol number. Pass through.
   596  			acl["protocol"] = *entry.Protocol
   597  		}
   598  
   599  		acl["protocol"] = *entry.Protocol
   600  		if entry.PortRange != nil {
   601  			acl["from_port"] = *entry.PortRange.From
   602  			acl["to_port"] = *entry.PortRange.To
   603  		}
   604  
   605  		if entry.IcmpTypeCode != nil {
   606  			acl["icmp_type"] = *entry.IcmpTypeCode.Type
   607  			acl["icmp_code"] = *entry.IcmpTypeCode.Code
   608  		}
   609  
   610  		result = append(result, acl)
   611  	}
   612  
   613  	return result
   614  }