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