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