github.com/anfernee/terraform@v0.6.16-0.20160430000239-06e5085a92f2/builtin/providers/aws/resource_aws_route53_zone.go (about)

     1  package aws
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"sort"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  
    13  	"github.com/aws/aws-sdk-go/aws"
    14  	"github.com/aws/aws-sdk-go/aws/awserr"
    15  	"github.com/aws/aws-sdk-go/service/route53"
    16  )
    17  
    18  func resourceAwsRoute53Zone() *schema.Resource {
    19  	return &schema.Resource{
    20  		Create: resourceAwsRoute53ZoneCreate,
    21  		Read:   resourceAwsRoute53ZoneRead,
    22  		Update: resourceAwsRoute53ZoneUpdate,
    23  		Delete: resourceAwsRoute53ZoneDelete,
    24  
    25  		Schema: map[string]*schema.Schema{
    26  			"name": &schema.Schema{
    27  				Type:     schema.TypeString,
    28  				Required: true,
    29  				ForceNew: true,
    30  			},
    31  
    32  			"comment": &schema.Schema{
    33  				Type:     schema.TypeString,
    34  				Optional: true,
    35  				Default:  "Managed by Terraform",
    36  			},
    37  
    38  			"vpc_id": &schema.Schema{
    39  				Type:     schema.TypeString,
    40  				Optional: true,
    41  				ForceNew: true,
    42  			},
    43  
    44  			"vpc_region": &schema.Schema{
    45  				Type:     schema.TypeString,
    46  				Optional: true,
    47  				ForceNew: true,
    48  				Computed: true,
    49  			},
    50  
    51  			"zone_id": &schema.Schema{
    52  				Type:     schema.TypeString,
    53  				Computed: true,
    54  			},
    55  
    56  			"delegation_set_id": &schema.Schema{
    57  				Type:     schema.TypeString,
    58  				Optional: true,
    59  				ForceNew: true,
    60  			},
    61  
    62  			"name_servers": &schema.Schema{
    63  				Type:     schema.TypeList,
    64  				Elem:     &schema.Schema{Type: schema.TypeString},
    65  				Computed: true,
    66  			},
    67  
    68  			"tags": tagsSchema(),
    69  		},
    70  	}
    71  }
    72  
    73  func resourceAwsRoute53ZoneCreate(d *schema.ResourceData, meta interface{}) error {
    74  	r53 := meta.(*AWSClient).r53conn
    75  
    76  	req := &route53.CreateHostedZoneInput{
    77  		Name:             aws.String(d.Get("name").(string)),
    78  		HostedZoneConfig: &route53.HostedZoneConfig{Comment: aws.String(d.Get("comment").(string))},
    79  		CallerReference:  aws.String(time.Now().Format(time.RFC3339Nano)),
    80  	}
    81  	if v := d.Get("vpc_id"); v != "" {
    82  		req.VPC = &route53.VPC{
    83  			VPCId:     aws.String(v.(string)),
    84  			VPCRegion: aws.String(meta.(*AWSClient).region),
    85  		}
    86  		if w := d.Get("vpc_region"); w != "" {
    87  			req.VPC.VPCRegion = aws.String(w.(string))
    88  		}
    89  		d.Set("vpc_region", req.VPC.VPCRegion)
    90  	}
    91  
    92  	if v, ok := d.GetOk("delegation_set_id"); ok {
    93  		req.DelegationSetId = aws.String(v.(string))
    94  	}
    95  
    96  	log.Printf("[DEBUG] Creating Route53 hosted zone: %s", *req.Name)
    97  	var err error
    98  	resp, err := r53.CreateHostedZone(req)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	// Store the zone_id
   104  	zone := cleanZoneID(*resp.HostedZone.Id)
   105  	d.Set("zone_id", zone)
   106  	d.SetId(zone)
   107  
   108  	// Wait until we are done initializing
   109  	wait := resource.StateChangeConf{
   110  		Delay:      30 * time.Second,
   111  		Pending:    []string{"PENDING"},
   112  		Target:     []string{"INSYNC"},
   113  		Timeout:    10 * time.Minute,
   114  		MinTimeout: 2 * time.Second,
   115  		Refresh: func() (result interface{}, state string, err error) {
   116  			changeRequest := &route53.GetChangeInput{
   117  				Id: aws.String(cleanChangeID(*resp.ChangeInfo.Id)),
   118  			}
   119  			return resourceAwsGoRoute53Wait(r53, changeRequest)
   120  		},
   121  	}
   122  	_, err = wait.WaitForState()
   123  	if err != nil {
   124  		return err
   125  	}
   126  	return resourceAwsRoute53ZoneUpdate(d, meta)
   127  }
   128  
   129  func resourceAwsRoute53ZoneRead(d *schema.ResourceData, meta interface{}) error {
   130  	r53 := meta.(*AWSClient).r53conn
   131  	zone, err := r53.GetHostedZone(&route53.GetHostedZoneInput{Id: aws.String(d.Id())})
   132  	if err != nil {
   133  		// Handle a deleted zone
   134  		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
   135  			d.SetId("")
   136  			return nil
   137  		}
   138  		return err
   139  	}
   140  
   141  	// In the import case this will be empty
   142  	if _, ok := d.GetOk("zone_id"); !ok {
   143  		d.Set("zone_id", d.Id())
   144  	}
   145  	if _, ok := d.GetOk("name"); !ok {
   146  		d.Set("name", zone.HostedZone.Name)
   147  	}
   148  
   149  	if !*zone.HostedZone.Config.PrivateZone {
   150  		ns := make([]string, len(zone.DelegationSet.NameServers))
   151  		for i := range zone.DelegationSet.NameServers {
   152  			ns[i] = *zone.DelegationSet.NameServers[i]
   153  		}
   154  		sort.Strings(ns)
   155  		if err := d.Set("name_servers", ns); err != nil {
   156  			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
   157  		}
   158  	} else {
   159  		ns, err := getNameServers(d.Id(), d.Get("name").(string), r53)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		if err := d.Set("name_servers", ns); err != nil {
   164  			return fmt.Errorf("[DEBUG] Error setting name servers for: %s, error: %#v", d.Id(), err)
   165  		}
   166  
   167  		// In the import case we just associate it with the first VPC
   168  		if _, ok := d.GetOk("vpc_id"); !ok {
   169  			if len(zone.VPCs) > 1 {
   170  				return fmt.Errorf(
   171  					"Can't import a route53_zone with more than one VPC attachment")
   172  			}
   173  
   174  			if len(zone.VPCs) > 0 {
   175  				d.Set("vpc_id", zone.VPCs[0].VPCId)
   176  				d.Set("vpc_region", zone.VPCs[0].VPCRegion)
   177  			}
   178  		}
   179  
   180  		var associatedVPC *route53.VPC
   181  		for _, vpc := range zone.VPCs {
   182  			if *vpc.VPCId == d.Get("vpc_id") {
   183  				associatedVPC = vpc
   184  				break
   185  			}
   186  		}
   187  		if associatedVPC == nil {
   188  			return fmt.Errorf("[DEBUG] VPC: %v is not associated with Zone: %v", d.Get("vpc_id"), d.Id())
   189  		}
   190  	}
   191  
   192  	if zone.DelegationSet != nil && zone.DelegationSet.Id != nil {
   193  		d.Set("delegation_set_id", cleanDelegationSetId(*zone.DelegationSet.Id))
   194  	}
   195  
   196  	if zone.HostedZone != nil && zone.HostedZone.Config != nil && zone.HostedZone.Config.Comment != nil {
   197  		d.Set("comment", zone.HostedZone.Config.Comment)
   198  	}
   199  
   200  	// get tags
   201  	req := &route53.ListTagsForResourceInput{
   202  		ResourceId:   aws.String(d.Id()),
   203  		ResourceType: aws.String("hostedzone"),
   204  	}
   205  
   206  	resp, err := r53.ListTagsForResource(req)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	var tags []*route53.Tag
   212  	if resp.ResourceTagSet != nil {
   213  		tags = resp.ResourceTagSet.Tags
   214  	}
   215  
   216  	if err := d.Set("tags", tagsToMapR53(tags)); err != nil {
   217  		return err
   218  	}
   219  
   220  	return nil
   221  }
   222  
   223  func resourceAwsRoute53ZoneUpdate(d *schema.ResourceData, meta interface{}) error {
   224  	conn := meta.(*AWSClient).r53conn
   225  
   226  	d.Partial(true)
   227  
   228  	if d.HasChange("comment") {
   229  		zoneInput := route53.UpdateHostedZoneCommentInput{
   230  			Id:      aws.String(d.Id()),
   231  			Comment: aws.String(d.Get("comment").(string)),
   232  		}
   233  
   234  		_, err := conn.UpdateHostedZoneComment(&zoneInput)
   235  		if err != nil {
   236  			return err
   237  		} else {
   238  			d.SetPartial("comment")
   239  		}
   240  	}
   241  
   242  	if err := setTagsR53(conn, d, "hostedzone"); err != nil {
   243  		return err
   244  	} else {
   245  		d.SetPartial("tags")
   246  	}
   247  
   248  	d.Partial(false)
   249  
   250  	return resourceAwsRoute53ZoneRead(d, meta)
   251  }
   252  
   253  func resourceAwsRoute53ZoneDelete(d *schema.ResourceData, meta interface{}) error {
   254  	r53 := meta.(*AWSClient).r53conn
   255  
   256  	log.Printf("[DEBUG] Deleting Route53 hosted zone: %s (ID: %s)",
   257  		d.Get("name").(string), d.Id())
   258  	_, err := r53.DeleteHostedZone(&route53.DeleteHostedZoneInput{Id: aws.String(d.Id())})
   259  	if err != nil {
   260  		if r53err, ok := err.(awserr.Error); ok && r53err.Code() == "NoSuchHostedZone" {
   261  			log.Printf("[DEBUG] No matching Route 53 Zone found for: %s, removing from state file", d.Id())
   262  			d.SetId("")
   263  			return nil
   264  		}
   265  		return err
   266  	}
   267  
   268  	return nil
   269  }
   270  
   271  func resourceAwsGoRoute53Wait(r53 *route53.Route53, ref *route53.GetChangeInput) (result interface{}, state string, err error) {
   272  
   273  	status, err := r53.GetChange(ref)
   274  	if err != nil {
   275  		return nil, "UNKNOWN", err
   276  	}
   277  	return true, *status.ChangeInfo.Status, nil
   278  }
   279  
   280  // cleanChangeID is used to remove the leading /change/
   281  func cleanChangeID(ID string) string {
   282  	return cleanPrefix(ID, "/change/")
   283  }
   284  
   285  // cleanZoneID is used to remove the leading /hostedzone/
   286  func cleanZoneID(ID string) string {
   287  	return cleanPrefix(ID, "/hostedzone/")
   288  }
   289  
   290  // cleanPrefix removes a string prefix from an ID
   291  func cleanPrefix(ID, prefix string) string {
   292  	if strings.HasPrefix(ID, prefix) {
   293  		ID = strings.TrimPrefix(ID, prefix)
   294  	}
   295  	return ID
   296  }
   297  
   298  func getNameServers(zoneId string, zoneName string, r53 *route53.Route53) ([]string, error) {
   299  	resp, err := r53.ListResourceRecordSets(&route53.ListResourceRecordSetsInput{
   300  		HostedZoneId:    aws.String(zoneId),
   301  		StartRecordName: aws.String(zoneName),
   302  		StartRecordType: aws.String("NS"),
   303  	})
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	if len(resp.ResourceRecordSets) == 0 {
   308  		return nil, nil
   309  	}
   310  	ns := make([]string, len(resp.ResourceRecordSets[0].ResourceRecords))
   311  	for i := range resp.ResourceRecordSets[0].ResourceRecords {
   312  		ns[i] = *resp.ResourceRecordSets[0].ResourceRecords[i].Value
   313  	}
   314  	sort.Strings(ns)
   315  	return ns, nil
   316  }