github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  		return fmt.Errorf("Error retrieving DHCP Options: %s", err)
   151  	}
   152  
   153  	if len(resp.DhcpOptions) == 0 {
   154  		return nil
   155  	}
   156  
   157  	opts := resp.DhcpOptions[0]
   158  	d.Set("tags", tagsToMap(opts.Tags))
   159  
   160  	for _, cfg := range opts.DhcpConfigurations {
   161  		tfKey := strings.Replace(*cfg.Key, "-", "_", -1)
   162  
   163  		if _, ok := d.Get(tfKey).(string); ok {
   164  			d.Set(tfKey, cfg.Values[0].Value)
   165  		} else {
   166  			values := make([]string, 0, len(cfg.Values))
   167  			for _, v := range cfg.Values {
   168  				values = append(values, *v.Value)
   169  			}
   170  
   171  			d.Set(tfKey, values)
   172  		}
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  func resourceAwsVpcDhcpOptionsUpdate(d *schema.ResourceData, meta interface{}) error {
   179  	conn := meta.(*AWSClient).ec2conn
   180  	return setTags(conn, d)
   181  }
   182  
   183  func resourceAwsVpcDhcpOptionsDelete(d *schema.ResourceData, meta interface{}) error {
   184  	conn := meta.(*AWSClient).ec2conn
   185  
   186  	return resource.Retry(3*time.Minute, func() *resource.RetryError {
   187  		log.Printf("[INFO] Deleting DHCP Options ID %s...", d.Id())
   188  		_, err := conn.DeleteDhcpOptions(&ec2.DeleteDhcpOptionsInput{
   189  			DhcpOptionsId: aws.String(d.Id()),
   190  		})
   191  
   192  		if err == nil {
   193  			return nil
   194  		}
   195  
   196  		log.Printf("[WARN] %s", err)
   197  
   198  		ec2err, ok := err.(awserr.Error)
   199  		if !ok {
   200  			return resource.RetryableError(err)
   201  		}
   202  
   203  		switch ec2err.Code() {
   204  		case "InvalidDhcpOptionsID.NotFound":
   205  			return nil
   206  		case "DependencyViolation":
   207  			// If it is a dependency violation, we want to disassociate
   208  			// all VPCs using the given DHCP Options ID, and retry deleting.
   209  			vpcs, err2 := findVPCsByDHCPOptionsID(conn, d.Id())
   210  			if err2 != nil {
   211  				log.Printf("[ERROR] %s", err2)
   212  				return resource.RetryableError(err2)
   213  			}
   214  
   215  			for _, vpc := range vpcs {
   216  				log.Printf("[INFO] Disassociating DHCP Options Set %s from VPC %s...", d.Id(), *vpc.VpcId)
   217  				if _, err := conn.AssociateDhcpOptions(&ec2.AssociateDhcpOptionsInput{
   218  					DhcpOptionsId: aws.String("default"),
   219  					VpcId:         vpc.VpcId,
   220  				}); err != nil {
   221  					return resource.RetryableError(err)
   222  				}
   223  			}
   224  			return resource.RetryableError(err)
   225  		default:
   226  			return resource.NonRetryableError(err)
   227  		}
   228  	})
   229  }
   230  
   231  func findVPCsByDHCPOptionsID(conn *ec2.EC2, id string) ([]*ec2.Vpc, error) {
   232  	req := &ec2.DescribeVpcsInput{
   233  		Filters: []*ec2.Filter{
   234  			&ec2.Filter{
   235  				Name: aws.String("dhcp-options-id"),
   236  				Values: []*string{
   237  					aws.String(id),
   238  				},
   239  			},
   240  		},
   241  	}
   242  
   243  	resp, err := conn.DescribeVpcs(req)
   244  	if err != nil {
   245  		if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidVpcID.NotFound" {
   246  			return nil, nil
   247  		}
   248  		return nil, err
   249  	}
   250  
   251  	return resp.Vpcs, nil
   252  }
   253  
   254  func resourceDHCPOptionsStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
   255  	return func() (interface{}, string, error) {
   256  		DescribeDhcpOpts := &ec2.DescribeDhcpOptionsInput{
   257  			DhcpOptionsIds: []*string{
   258  				aws.String(id),
   259  			},
   260  		}
   261  
   262  		resp, err := conn.DescribeDhcpOptions(DescribeDhcpOpts)
   263  		if err != nil {
   264  			if ec2err, ok := err.(awserr.Error); ok && ec2err.Code() == "InvalidDhcpOptionsID.NotFound" {
   265  				resp = nil
   266  			} else {
   267  				log.Printf("Error on DHCPOptionsStateRefresh: %s", err)
   268  				return nil, "", err
   269  			}
   270  		}
   271  
   272  		if resp == nil {
   273  			// Sometimes AWS just has consistency issues and doesn't see
   274  			// our instance yet. Return an empty state.
   275  			return nil, "", nil
   276  		}
   277  
   278  		dos := resp.DhcpOptions[0]
   279  		return dos, "created", nil
   280  	}
   281  }