github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/aws/resource_aws_vpc_dhcp_options.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/awserr"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  func resourceAwsVpcDhcpOptions() *schema.Resource {
    17  	return &schema.Resource{
    18  		Create: resourceAwsVpcDhcpOptionsCreate,
    19  		Read:   resourceAwsVpcDhcpOptionsRead,
    20  		Update: resourceAwsVpcDhcpOptionsUpdate,
    21  		Delete: resourceAwsVpcDhcpOptionsDelete,
    22  		Importer: &schema.ResourceImporter{
    23  			State: schema.ImportStatePassthrough,
    24  		},
    25  
    26  		Schema: map[string]*schema.Schema{
    27  			"domain_name": &schema.Schema{
    28  				Type:     schema.TypeString,
    29  				Optional: true,
    30  				ForceNew: true,
    31  			},
    32  
    33  			"domain_name_servers": &schema.Schema{
    34  				Type:     schema.TypeList,
    35  				Optional: true,
    36  				ForceNew: true,
    37  				Elem:     &schema.Schema{Type: schema.TypeString},
    38  			},
    39  
    40  			"ntp_servers": &schema.Schema{
    41  				Type:     schema.TypeList,
    42  				Optional: true,
    43  				ForceNew: true,
    44  				Elem:     &schema.Schema{Type: schema.TypeString},
    45  			},
    46  
    47  			"netbios_node_type": &schema.Schema{
    48  				Type:     schema.TypeString,
    49  				Optional: true,
    50  				ForceNew: true,
    51  			},
    52  
    53  			"netbios_name_servers": &schema.Schema{
    54  				Type:     schema.TypeList,
    55  				Optional: true,
    56  				ForceNew: true,
    57  				Elem:     &schema.Schema{Type: schema.TypeString},
    58  			},
    59  
    60  			"tags": &schema.Schema{
    61  				Type:     schema.TypeMap,
    62  				Optional: true,
    63  			},
    64  		},
    65  	}
    66  }
    67  
    68  func resourceAwsVpcDhcpOptionsCreate(d *schema.ResourceData, meta interface{}) error {
    69  	conn := meta.(*AWSClient).ec2conn
    70  
    71  	setDHCPOption := func(key string) *ec2.NewDhcpConfiguration {
    72  		log.Printf("[DEBUG] Setting DHCP option %s...", key)
    73  		tfKey := strings.Replace(key, "-", "_", -1)
    74  
    75  		value, ok := d.GetOk(tfKey)
    76  		if !ok {
    77  			return nil
    78  		}
    79  
    80  		if v, ok := value.(string); ok {
    81  			return &ec2.NewDhcpConfiguration{
    82  				Key: aws.String(key),
    83  				Values: []*string{
    84  					aws.String(v),
    85  				},
    86  			}
    87  		}
    88  
    89  		if v, ok := value.([]interface{}); ok {
    90  			var s []*string
    91  			for _, attr := range v {
    92  				s = append(s, aws.String(attr.(string)))
    93  			}
    94  
    95  			return &ec2.NewDhcpConfiguration{
    96  				Key:    aws.String(key),
    97  				Values: s,
    98  			}
    99  		}
   100  
   101  		return nil
   102  	}
   103  
   104  	createOpts := &ec2.CreateDhcpOptionsInput{
   105  		DhcpConfigurations: []*ec2.NewDhcpConfiguration{
   106  			setDHCPOption("domain-name"),
   107  			setDHCPOption("domain-name-servers"),
   108  			setDHCPOption("ntp-servers"),
   109  			setDHCPOption("netbios-node-type"),
   110  			setDHCPOption("netbios-name-servers"),
   111  		},
   112  	}
   113  
   114  	resp, err := conn.CreateDhcpOptions(createOpts)
   115  	if err != nil {
   116  		return fmt.Errorf("Error creating DHCP Options Set: %s", err)
   117  	}
   118  
   119  	dos := resp.DhcpOptions
   120  	d.SetId(*dos.DhcpOptionsId)
   121  	log.Printf("[INFO] DHCP Options Set ID: %s", d.Id())
   122  
   123  	// Wait for the DHCP Options to become available
   124  	log.Printf("[DEBUG] Waiting for DHCP Options (%s) to become available", d.Id())
   125  	stateConf := &resource.StateChangeConf{
   126  		Pending: []string{"pending"},
   127  		Target:  []string{"created"},
   128  		Refresh: resourceDHCPOptionsStateRefreshFunc(conn, d.Id()),
   129  		Timeout: 1 * time.Minute,
   130  	}
   131  	if _, err := stateConf.WaitForState(); err != nil {
   132  		return fmt.Errorf(
   133  			"Error waiting for DHCP Options (%s) to become available: %s",
   134  			d.Id(), err)
   135  	}
   136  
   137  	return resourceAwsVpcDhcpOptionsUpdate(d, meta)
   138  }
   139  
   140  func resourceAwsVpcDhcpOptionsRead(d *schema.ResourceData, meta interface{}) error {
   141  	conn := meta.(*AWSClient).ec2conn
   142  	req := &ec2.DescribeDhcpOptionsInput{
   143  		DhcpOptionsIds: []*string{
   144  			aws.String(d.Id()),
   145  		},
   146  	}
   147  
   148  	resp, err := conn.DescribeDhcpOptions(req)
   149  	if err != nil {
   150  		ec2err, ok := err.(awserr.Error)
   151  		if !ok {
   152  			return fmt.Errorf("Error retrieving DHCP Options: %s", err.Error())
   153  		}
   154  
   155  		if ec2err.Code() == "InvalidDhcpOptionID.NotFound" {
   156  			log.Printf("[WARN] DHCP Options (%s) not found, removing from state", d.Id())
   157  			d.SetId("")
   158  			return nil
   159  		}
   160  
   161  		return fmt.Errorf("Error retrieving DHCP Options: %s", err.Error())
   162  	}
   163  
   164  	if len(resp.DhcpOptions) == 0 {
   165  		return nil
   166  	}
   167  
   168  	opts := resp.DhcpOptions[0]
   169  	d.Set("tags", tagsToMap(opts.Tags))
   170  
   171  	for _, cfg := range opts.DhcpConfigurations {
   172  		tfKey := strings.Replace(*cfg.Key, "-", "_", -1)
   173  
   174  		if _, ok := d.Get(tfKey).(string); ok {
   175  			d.Set(tfKey, cfg.Values[0].Value)
   176  		} else {
   177  			values := make([]string, 0, len(cfg.Values))
   178  			for _, v := range cfg.Values {
   179  				values = append(values, *v.Value)
   180  			}
   181  
   182  			d.Set(tfKey, values)
   183  		}
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func resourceAwsVpcDhcpOptionsUpdate(d *schema.ResourceData, meta interface{}) error {
   190  	conn := meta.(*AWSClient).ec2conn
   191  	return setTags(conn, d)
   192  }
   193  
   194  func resourceAwsVpcDhcpOptionsDelete(d *schema.ResourceData, meta interface{}) error {
   195  	conn := meta.(*AWSClient).ec2conn
   196  
   197  	return resource.Retry(3*time.Minute, func() *resource.RetryError {
   198  		log.Printf("[INFO] Deleting DHCP Options ID %s...", d.Id())
   199  		_, err := conn.DeleteDhcpOptions(&ec2.DeleteDhcpOptionsInput{
   200  			DhcpOptionsId: aws.String(d.Id()),
   201  		})
   202  
   203  		if err == nil {
   204  			return nil
   205  		}
   206  
   207  		log.Printf("[WARN] %s", err)
   208  
   209  		ec2err, ok := err.(awserr.Error)
   210  		if !ok {
   211  			return resource.RetryableError(err)
   212  		}
   213  
   214  		switch ec2err.Code() {
   215  		case "InvalidDhcpOptionsID.NotFound":
   216  			return nil
   217  		case "DependencyViolation":
   218  			// If it is a dependency violation, we want to disassociate
   219  			// all VPCs using the given DHCP Options ID, and retry deleting.
   220  			vpcs, err2 := findVPCsByDHCPOptionsID(conn, d.Id())
   221  			if err2 != nil {
   222  				log.Printf("[ERROR] %s", err2)
   223  				return resource.RetryableError(err2)
   224  			}
   225  
   226  			for _, vpc := range vpcs {
   227  				log.Printf("[INFO] Disassociating DHCP Options Set %s from VPC %s...", d.Id(), *vpc.VpcId)
   228  				if _, err := conn.AssociateDhcpOptions(&ec2.AssociateDhcpOptionsInput{
   229  					DhcpOptionsId: aws.String("default"),
   230  					VpcId:         vpc.VpcId,
   231  				}); err != nil {
   232  					return resource.RetryableError(err)
   233  				}
   234  			}
   235  			return resource.RetryableError(err)
   236  		default:
   237  			return resource.NonRetryableError(err)
   238  		}
   239  	})
   240  }
   241  
   242  func findVPCsByDHCPOptionsID(conn *ec2.EC2, id string) ([]*ec2.Vpc, error) {
   243  	req := &ec2.DescribeVpcsInput{
   244  		Filters: []*ec2.Filter{
   245  			&ec2.Filter{
   246  				Name: aws.String("dhcp-options-id"),
   247  				Values: []*string{
   248  					aws.String(id),
   249  				},
   250  			},
   251  		},
   252  	}
   253  
   254  	resp, err := conn.DescribeVpcs(req)
   255  	if err != nil {
   256  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" {
   257  			return nil, nil
   258  		}
   259  		return nil, err
   260  	}
   261  
   262  	return resp.Vpcs, nil
   263  }
   264  
   265  func resourceDHCPOptionsStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   266  	return func() (interface{}, string, error) {
   267  		DescribeDhcpOpts := &ec2.DescribeDhcpOptionsInput{
   268  			DhcpOptionsIds: []*string{
   269  				aws.String(id),
   270  			},
   271  		}
   272  
   273  		resp, err := conn.DescribeDhcpOptions(DescribeDhcpOpts)
   274  		if err != nil {
   275  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidDhcpOptionsID.NotFound" {
   276  				resp = nil
   277  			} else {
   278  				log.Printf("Error on DHCPOptionsStateRefresh: %s", err)
   279  				return nil, "", err
   280  			}
   281  		}
   282  
   283  		if resp == nil {
   284  			// Sometimes AWS just has consistency issues and doesn't see
   285  			// our instance yet. Return an empty state.
   286  			return nil, "", nil
   287  		}
   288  
   289  		dos := resp.DhcpOptions[0]
   290  		return dos, "created", nil
   291  	}
   292  }