Merge branch 'master' of github.com:hashicorp/terraform
This commit is contained in:
commit
f4092c2bfe
|
@ -39,6 +39,14 @@ func init() {
|
||||||
Update: resource_aws_db_security_group_update,
|
Update: resource_aws_db_security_group_update,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"aws_eip": resource.Resource{
|
||||||
|
ConfigValidator: resource_aws_eip_validation(),
|
||||||
|
Create: resource_aws_eip_create,
|
||||||
|
Destroy: resource_aws_eip_destroy,
|
||||||
|
Diff: resource_aws_eip_diff,
|
||||||
|
Refresh: resource_aws_eip_refresh,
|
||||||
|
},
|
||||||
|
|
||||||
"aws_elb": resource.Resource{
|
"aws_elb": resource.Resource{
|
||||||
ConfigValidator: resource_aws_elb_validation(),
|
ConfigValidator: resource_aws_elb_validation(),
|
||||||
Create: resource_aws_elb_create,
|
Create: resource_aws_elb_create,
|
||||||
|
@ -48,14 +56,6 @@ func init() {
|
||||||
Refresh: resource_aws_elb_refresh,
|
Refresh: resource_aws_elb_refresh,
|
||||||
},
|
},
|
||||||
|
|
||||||
"aws_eip": resource.Resource{
|
|
||||||
ConfigValidator: resource_aws_eip_validation(),
|
|
||||||
Create: resource_aws_eip_create,
|
|
||||||
Destroy: resource_aws_eip_destroy,
|
|
||||||
Diff: resource_aws_eip_diff,
|
|
||||||
Refresh: resource_aws_eip_refresh,
|
|
||||||
},
|
|
||||||
|
|
||||||
"aws_instance": resource.Resource{
|
"aws_instance": resource.Resource{
|
||||||
Create: resource_aws_instance_create,
|
Create: resource_aws_instance_create,
|
||||||
Destroy: resource_aws_instance_destroy,
|
Destroy: resource_aws_instance_destroy,
|
||||||
|
@ -112,14 +112,6 @@ func init() {
|
||||||
Update: resource_aws_route_table_association_update,
|
Update: resource_aws_route_table_association_update,
|
||||||
},
|
},
|
||||||
|
|
||||||
"aws_route53_zone": resource.Resource{
|
|
||||||
ConfigValidator: resource_aws_r53_zone_validation(),
|
|
||||||
Create: resource_aws_r53_zone_create,
|
|
||||||
Destroy: resource_aws_r53_zone_destroy,
|
|
||||||
Diff: resource_aws_r53_zone_diff,
|
|
||||||
Refresh: resource_aws_r53_zone_refresh,
|
|
||||||
},
|
|
||||||
|
|
||||||
"aws_route53_record": resource.Resource{
|
"aws_route53_record": resource.Resource{
|
||||||
ConfigValidator: resource_aws_r53_record_validation(),
|
ConfigValidator: resource_aws_r53_record_validation(),
|
||||||
Create: resource_aws_r53_record_create,
|
Create: resource_aws_r53_record_create,
|
||||||
|
@ -129,6 +121,14 @@ func init() {
|
||||||
Update: resource_aws_r53_record_create,
|
Update: resource_aws_r53_record_create,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"aws_route53_zone": resource.Resource{
|
||||||
|
ConfigValidator: resource_aws_r53_zone_validation(),
|
||||||
|
Create: resource_aws_r53_zone_create,
|
||||||
|
Destroy: resource_aws_r53_zone_destroy,
|
||||||
|
Diff: resource_aws_r53_zone_diff,
|
||||||
|
Refresh: resource_aws_r53_zone_refresh,
|
||||||
|
},
|
||||||
|
|
||||||
"aws_s3_bucket": resource.Resource{
|
"aws_s3_bucket": resource.Resource{
|
||||||
ConfigValidator: resource_aws_s3_bucket_validation(),
|
ConfigValidator: resource_aws_s3_bucket_validation(),
|
||||||
Create: resource_aws_s3_bucket_create,
|
Create: resource_aws_s3_bucket_create,
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/config"
|
||||||
|
"github.com/hashicorp/terraform/helper/diff"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/pearkes/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resource_digitalocean_domain_create(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
d *terraform.ResourceDiff,
|
||||||
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
// Merge the diff into the state so that we have all the attributes
|
||||||
|
// properly.
|
||||||
|
rs := s.MergeDiff(d)
|
||||||
|
|
||||||
|
// Build up our creation options
|
||||||
|
opts := digitalocean.CreateDomain{
|
||||||
|
Name: rs.Attributes["name"],
|
||||||
|
IPAddress: rs.Attributes["ip_address"],
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Domain create configuration: %#v", opts)
|
||||||
|
|
||||||
|
name, err := client.CreateDomain(&opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error creating Domain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.ID = name
|
||||||
|
log.Printf("[INFO] Domain Name: %s", name)
|
||||||
|
|
||||||
|
return rs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_domain_destroy(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
meta interface{}) error {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
|
||||||
|
log.Printf("[INFO] Deleting Domain: %s", s.ID)
|
||||||
|
|
||||||
|
err := client.DestroyDomain(s.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting Domain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_domain_refresh(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
|
||||||
|
domain, err := client.RetrieveDomain(s.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return s, fmt.Errorf("Error retrieving domain: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Attributes["name"] = domain.Name
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_domain_diff(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
c *terraform.ResourceConfig,
|
||||||
|
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||||
|
|
||||||
|
b := &diff.ResourceBuilder{
|
||||||
|
Attrs: map[string]diff.AttrType{
|
||||||
|
"name": diff.AttrTypeCreate,
|
||||||
|
"ip_address": diff.AttrTypeCreate,
|
||||||
|
},
|
||||||
|
|
||||||
|
ComputedAttrs: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Diff(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_domain_validation() *config.Validator {
|
||||||
|
return &config.Validator{
|
||||||
|
Required: []string{
|
||||||
|
"name",
|
||||||
|
"ip_address",
|
||||||
|
},
|
||||||
|
Optional: []string{},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/pearkes/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDigitalOceanDomain_Basic(t *testing.T) {
|
||||||
|
var domain digitalocean.Domain
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDigitalOceanDomainDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccCheckDigitalOceanDomainConfig_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDigitalOceanDomainExists("digitalocean_domain.foobar", &domain),
|
||||||
|
testAccCheckDigitalOceanDomainAttributes(&domain),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_domain.foobar", "name", "foobar-test-terraform.com"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_domain.foobar", "ip_address", "192.168.0.10"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanDomainDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.client
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "digitalocean_domain" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to find the domain
|
||||||
|
_, err := client.RetrieveDomain(rs.ID)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
fmt.Errorf("Domain still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanDomainAttributes(domain *digitalocean.Domain) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if domain.Name != "foobar-test-terraform.com" {
|
||||||
|
return fmt.Errorf("Bad name: %s", domain.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanDomainExists(n string, domain *digitalocean.Domain) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.Resources[n]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.ID == "" {
|
||||||
|
return fmt.Errorf("No Record ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.client
|
||||||
|
|
||||||
|
foundDomain, err := client.RetrieveDomain(rs.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundDomain.Name != rs.ID {
|
||||||
|
return fmt.Errorf("Record not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*domain = foundDomain
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccCheckDigitalOceanDomainConfig_basic = `
|
||||||
|
resource "digitalocean_domain" "foobar" {
|
||||||
|
name = "foobar-test-terraform.com"
|
||||||
|
ip_address = "192.168.0.10"
|
||||||
|
}`
|
|
@ -81,6 +81,10 @@ func resource_digitalocean_droplet_create(
|
||||||
|
|
||||||
droplet := dropletRaw.(*digitalocean.Droplet)
|
droplet := dropletRaw.(*digitalocean.Droplet)
|
||||||
|
|
||||||
|
// Initialize the connection info
|
||||||
|
rs.ConnInfo["type"] = "ssh"
|
||||||
|
rs.ConnInfo["host"] = droplet.IPV4Address()
|
||||||
|
|
||||||
return resource_digitalocean_droplet_update_state(rs, droplet)
|
return resource_digitalocean_droplet_update_state(rs, droplet)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/config"
|
||||||
|
"github.com/hashicorp/terraform/helper/diff"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/pearkes/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resource_digitalocean_record_create(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
d *terraform.ResourceDiff,
|
||||||
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
|
||||||
|
// Merge the diff into the state so that we have all the attributes
|
||||||
|
// properly.
|
||||||
|
rs := s.MergeDiff(d)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
newRecord := digitalocean.CreateRecord{
|
||||||
|
Type: rs.Attributes["type"],
|
||||||
|
Name: rs.Attributes["name"],
|
||||||
|
Data: rs.Attributes["value"],
|
||||||
|
Priority: rs.Attributes["priority"],
|
||||||
|
Port: rs.Attributes["port"],
|
||||||
|
Weight: rs.Attributes["weight"],
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
|
||||||
|
|
||||||
|
recId, err := client.CreateRecord(rs.Attributes["domain"], &newRecord)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to create record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.ID = recId
|
||||||
|
log.Printf("[INFO] Record ID: %s", rs.ID)
|
||||||
|
|
||||||
|
record, err := resource_digitalocean_record_retrieve(rs.Attributes["domain"], rs.ID, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't find record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource_digitalocean_record_update_state(rs, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_update(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
d *terraform.ResourceDiff,
|
||||||
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
rs := s.MergeDiff(d)
|
||||||
|
|
||||||
|
updateRecord := digitalocean.UpdateRecord{}
|
||||||
|
|
||||||
|
if attr, ok := d.Attributes["name"]; ok {
|
||||||
|
updateRecord.Name = attr.New
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] record update configuration: %#v", updateRecord)
|
||||||
|
|
||||||
|
err := client.UpdateRecord(rs.Attributes["domain"], rs.ID, &updateRecord)
|
||||||
|
if err != nil {
|
||||||
|
return rs, fmt.Errorf("Failed to update record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := resource_digitalocean_record_retrieve(rs.Attributes["domain"], rs.ID, client)
|
||||||
|
if err != nil {
|
||||||
|
return rs, fmt.Errorf("Couldn't find record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource_digitalocean_record_update_state(rs, record)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_destroy(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
meta interface{}) error {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
|
||||||
|
log.Printf("[INFO] Deleting record: %s, %s", s.Attributes["domain"], s.ID)
|
||||||
|
|
||||||
|
err := client.DestroyRecord(s.Attributes["domain"], s.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_refresh(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
|
||||||
|
rec, err := resource_digitalocean_record_retrieve(s.Attributes["domain"], s.ID, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource_digitalocean_record_update_state(s, rec)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_diff(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
c *terraform.ResourceConfig,
|
||||||
|
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||||
|
|
||||||
|
b := &diff.ResourceBuilder{
|
||||||
|
Attrs: map[string]diff.AttrType{
|
||||||
|
"domain": diff.AttrTypeCreate,
|
||||||
|
"name": diff.AttrTypeUpdate,
|
||||||
|
"type": diff.AttrTypeCreate,
|
||||||
|
"value": diff.AttrTypeCreate,
|
||||||
|
"priority": diff.AttrTypeCreate,
|
||||||
|
"port": diff.AttrTypeCreate,
|
||||||
|
"weight": diff.AttrTypeCreate,
|
||||||
|
},
|
||||||
|
|
||||||
|
ComputedAttrs: []string{
|
||||||
|
"value",
|
||||||
|
"priority",
|
||||||
|
"weight",
|
||||||
|
"port",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Diff(s, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_update_state(
|
||||||
|
s *terraform.ResourceState,
|
||||||
|
rec *digitalocean.Record) (*terraform.ResourceState, error) {
|
||||||
|
|
||||||
|
s.Attributes["name"] = rec.Name
|
||||||
|
s.Attributes["type"] = rec.Type
|
||||||
|
s.Attributes["value"] = rec.Data
|
||||||
|
s.Attributes["weight"] = rec.StringWeight()
|
||||||
|
s.Attributes["priority"] = rec.StringPriority()
|
||||||
|
s.Attributes["port"] = rec.StringPort()
|
||||||
|
|
||||||
|
|
||||||
|
// We belong to a Domain
|
||||||
|
s.Dependencies = []terraform.ResourceDependency{
|
||||||
|
terraform.ResourceDependency{ID: s.Attributes["domain"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_retrieve(domain string, id string, client *digitalocean.Client) (*digitalocean.Record, error) {
|
||||||
|
record, err := client.RetrieveRecord(domain, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resource_digitalocean_record_validation() *config.Validator {
|
||||||
|
return &config.Validator{
|
||||||
|
Required: []string{
|
||||||
|
"type",
|
||||||
|
"domain",
|
||||||
|
},
|
||||||
|
Optional: []string{
|
||||||
|
"value",
|
||||||
|
"name",
|
||||||
|
"weight",
|
||||||
|
"port",
|
||||||
|
"priority",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/pearkes/digitalocean"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDigitalOceanRecord_Basic(t *testing.T) {
|
||||||
|
var record digitalocean.Record
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDigitalOceanRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccCheckDigitalOceanRecordConfig_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record),
|
||||||
|
testAccCheckDigitalOceanRecordAttributes(&record),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "name", "terraform"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "domain", "foobar-test-terraform.com"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "value", "192.168.0.10"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDigitalOceanRecord_Updated(t *testing.T) {
|
||||||
|
var record digitalocean.Record
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDigitalOceanRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccCheckDigitalOceanRecordConfig_basic,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record),
|
||||||
|
testAccCheckDigitalOceanRecordAttributes(&record),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "name", "terraform"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "domain", "foobar-test-terraform.com"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "value", "192.168.0.10"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "type", "A"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: testAccCheckDigitalOceanRecordConfig_new_value,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDigitalOceanRecordExists("digitalocean_record.foobar", &record),
|
||||||
|
testAccCheckDigitalOceanRecordAttributesUpdated(&record),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "name", "terraform"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "domain", "foobar-test-terraform.com"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "value", "192.168.0.11"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"digitalocean_record.foobar", "type", "A"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanRecordDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.client
|
||||||
|
|
||||||
|
for _, rs := range s.Resources {
|
||||||
|
if rs.Type != "digitalocean_record" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := client.RetrieveRecord(rs.Attributes["domain"], rs.ID)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("Record still exists")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanRecordAttributes(record *digitalocean.Record) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if record.Data != "192.168.0.10" {
|
||||||
|
return fmt.Errorf("Bad value: %s", record.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanRecordAttributesUpdated(record *digitalocean.Record) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if record.Data != "192.168.0.11" {
|
||||||
|
return fmt.Errorf("Bad value: %s", record.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckDigitalOceanRecordExists(n string, record *digitalocean.Record) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.Resources[n]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.ID == "" {
|
||||||
|
return fmt.Errorf("No Record ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.client
|
||||||
|
|
||||||
|
foundRecord, err := client.RetrieveRecord(rs.Attributes["domain"], rs.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if foundRecord.StringId() != rs.ID {
|
||||||
|
return fmt.Errorf("Record not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
*record = foundRecord
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testAccCheckDigitalOceanRecordConfig_basic = `
|
||||||
|
resource "digitalocean_domain" "foobar" {
|
||||||
|
name = "foobar-test-terraform.com"
|
||||||
|
ip_address = "192.168.0.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_record" "foobar" {
|
||||||
|
domain = "${digitalocean_domain.foobar.name}"
|
||||||
|
|
||||||
|
name = "terraform"
|
||||||
|
value = "192.168.0.10"
|
||||||
|
type = "A"
|
||||||
|
}`
|
||||||
|
|
||||||
|
const testAccCheckDigitalOceanRecordConfig_new_value = `
|
||||||
|
resource "digitalocean_domain" "foobar" {
|
||||||
|
name = "foobar-test-terraform.com"
|
||||||
|
ip_address = "192.168.0.10"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "digitalocean_record" "foobar" {
|
||||||
|
domain = "${digitalocean_domain.foobar.name}"
|
||||||
|
|
||||||
|
name = "terraform"
|
||||||
|
value = "192.168.0.11"
|
||||||
|
type = "A"
|
||||||
|
}`
|
|
@ -11,6 +11,14 @@ var resourceMap *resource.Map
|
||||||
func init() {
|
func init() {
|
||||||
resourceMap = &resource.Map{
|
resourceMap = &resource.Map{
|
||||||
Mapping: map[string]resource.Resource{
|
Mapping: map[string]resource.Resource{
|
||||||
|
"digitalocean_domain": resource.Resource{
|
||||||
|
ConfigValidator: resource_digitalocean_domain_validation(),
|
||||||
|
Create: resource_digitalocean_domain_create,
|
||||||
|
Destroy: resource_digitalocean_domain_destroy,
|
||||||
|
Diff: resource_digitalocean_domain_diff,
|
||||||
|
Refresh: resource_digitalocean_domain_refresh,
|
||||||
|
},
|
||||||
|
|
||||||
"digitalocean_droplet": resource.Resource{
|
"digitalocean_droplet": resource.Resource{
|
||||||
ConfigValidator: resource_digitalocean_droplet_validation(),
|
ConfigValidator: resource_digitalocean_droplet_validation(),
|
||||||
Create: resource_digitalocean_droplet_create,
|
Create: resource_digitalocean_droplet_create,
|
||||||
|
@ -19,6 +27,15 @@ func init() {
|
||||||
Refresh: resource_digitalocean_droplet_refresh,
|
Refresh: resource_digitalocean_droplet_refresh,
|
||||||
Update: resource_digitalocean_droplet_update,
|
Update: resource_digitalocean_droplet_update,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"digitalocean_record": resource.Resource{
|
||||||
|
ConfigValidator: resource_digitalocean_record_validation(),
|
||||||
|
Create: resource_digitalocean_record_create,
|
||||||
|
Destroy: resource_digitalocean_record_destroy,
|
||||||
|
Update: resource_digitalocean_record_update,
|
||||||
|
Diff: resource_digitalocean_record_diff,
|
||||||
|
Refresh: resource_digitalocean_record_refresh,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
package dnsimple
|
package dnsimple
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rubyist/go-dnsimple"
|
"github.com/pearkes/dnsimple"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -14,7 +15,7 @@ type Config struct {
|
||||||
|
|
||||||
// Client() returns a new client for accessing heroku.
|
// Client() returns a new client for accessing heroku.
|
||||||
//
|
//
|
||||||
func (c *Config) Client() (*dnsimple.DNSimpleClient, error) {
|
func (c *Config) Client() (*dnsimple.Client, error) {
|
||||||
|
|
||||||
// If we have env vars set (like in the acc) tests,
|
// If we have env vars set (like in the acc) tests,
|
||||||
// we need to override the values passed in here.
|
// we need to override the values passed in here.
|
||||||
|
@ -25,7 +26,11 @@ func (c *Config) Client() (*dnsimple.DNSimpleClient, error) {
|
||||||
c.Token = v
|
c.Token = v
|
||||||
}
|
}
|
||||||
|
|
||||||
client := dnsimple.NewClient(c.Token, c.Email)
|
client, err := dnsimple.NewClient(c.Email, c.Token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] DNSimple Client configured for user: %s", client.Email)
|
log.Printf("[INFO] DNSimple Client configured for user: %s", client.Email)
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@ package dnsimple
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/config"
|
"github.com/hashicorp/terraform/helper/config"
|
||||||
"github.com/hashicorp/terraform/helper/diff"
|
"github.com/hashicorp/terraform/helper/diff"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/rubyist/go-dnsimple"
|
"github.com/pearkes/dnsimple"
|
||||||
)
|
)
|
||||||
|
|
||||||
func resource_dnsimple_record_create(
|
func resource_dnsimple_record_create(
|
||||||
|
@ -24,42 +23,74 @@ func resource_dnsimple_record_create(
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
newRecord := dnsimple.Record{
|
newRecord := dnsimple.ChangeRecord{
|
||||||
Name: rs.Attributes["name"],
|
Name: rs.Attributes["name"],
|
||||||
Content: rs.Attributes["value"],
|
Value: rs.Attributes["value"],
|
||||||
RecordType: rs.Attributes["type"],
|
Type: rs.Attributes["type"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if attr, ok := rs.Attributes["ttl"]; ok {
|
if attr, ok := rs.Attributes["ttl"]; ok {
|
||||||
newRecord.TTL, err = strconv.Atoi(attr)
|
newRecord.Ttl = attr
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
|
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
|
||||||
|
|
||||||
rec, err := client.CreateRecord(rs.Attributes["domain"], newRecord)
|
recId, err := client.CreateRecord(rs.Attributes["domain"], &newRecord)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to create record: %s", err)
|
return nil, fmt.Errorf("Failed to create record: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
rs.ID = strconv.Itoa(rec.Id)
|
rs.ID = recId
|
||||||
|
|
||||||
log.Printf("[INFO] record ID: %s", rs.ID)
|
log.Printf("[INFO] record ID: %s", rs.ID)
|
||||||
|
|
||||||
return resource_dnsimple_record_update_state(rs, &rec)
|
record, err := resource_dnsimple_record_retrieve(rs.Attributes["domain"], rs.ID, client)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Couldn't find record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource_dnsimple_record_update_state(rs, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_dnsimple_record_update(
|
func resource_dnsimple_record_update(
|
||||||
s *terraform.ResourceState,
|
s *terraform.ResourceState,
|
||||||
d *terraform.ResourceDiff,
|
d *terraform.ResourceDiff,
|
||||||
meta interface{}) (*terraform.ResourceState, error) {
|
meta interface{}) (*terraform.ResourceState, error) {
|
||||||
|
p := meta.(*ResourceProvider)
|
||||||
|
client := p.client
|
||||||
|
rs := s.MergeDiff(d)
|
||||||
|
|
||||||
panic("Cannot update record")
|
updateRecord := dnsimple.ChangeRecord{}
|
||||||
|
|
||||||
return nil, nil
|
if attr, ok := d.Attributes["name"]; ok {
|
||||||
|
updateRecord.Name = attr.New
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr, ok := d.Attributes["value"]; ok {
|
||||||
|
updateRecord.Value = attr.New
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr, ok := d.Attributes["type"]; ok {
|
||||||
|
updateRecord.Type = attr.New
|
||||||
|
}
|
||||||
|
|
||||||
|
if attr, ok := d.Attributes["ttl"]; ok {
|
||||||
|
updateRecord.Ttl = attr.New
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] record update configuration: %#v", updateRecord)
|
||||||
|
|
||||||
|
_, err := client.UpdateRecord(rs.Attributes["domain"], rs.ID, &updateRecord)
|
||||||
|
if err != nil {
|
||||||
|
return rs, fmt.Errorf("Failed to update record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
record, err := resource_dnsimple_record_retrieve(rs.Attributes["domain"], rs.ID, client)
|
||||||
|
if err != nil {
|
||||||
|
return rs, fmt.Errorf("Couldn't find record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resource_dnsimple_record_update_state(rs, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_dnsimple_record_destroy(
|
func resource_dnsimple_record_destroy(
|
||||||
|
@ -68,14 +99,10 @@ func resource_dnsimple_record_destroy(
|
||||||
p := meta.(*ResourceProvider)
|
p := meta.(*ResourceProvider)
|
||||||
client := p.client
|
client := p.client
|
||||||
|
|
||||||
log.Printf("[INFO] Deleting record: %s", s.ID)
|
log.Printf("[INFO] Deleting record: %s, %s", s.Attributes["domain"], s.ID)
|
||||||
|
|
||||||
rec, err := resource_dnsimple_record_retrieve(s.Attributes["domain"], s.ID, client)
|
err := client.DestroyRecord(s.Attributes["domain"], s.ID)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rec.Delete(client)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Error deleting record: %s", err)
|
return fmt.Errorf("Error deleting record: %s", err)
|
||||||
}
|
}
|
||||||
|
@ -89,7 +116,7 @@ func resource_dnsimple_record_refresh(
|
||||||
p := meta.(*ResourceProvider)
|
p := meta.(*ResourceProvider)
|
||||||
client := p.client
|
client := p.client
|
||||||
|
|
||||||
rec, err := resource_dnsimple_record_retrieve(s.Attributes["app"], s.ID, client)
|
rec, err := resource_dnsimple_record_retrieve(s.Attributes["domain"], s.ID, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -105,15 +132,16 @@ func resource_dnsimple_record_diff(
|
||||||
b := &diff.ResourceBuilder{
|
b := &diff.ResourceBuilder{
|
||||||
Attrs: map[string]diff.AttrType{
|
Attrs: map[string]diff.AttrType{
|
||||||
"domain": diff.AttrTypeCreate,
|
"domain": diff.AttrTypeCreate,
|
||||||
"name": diff.AttrTypeCreate,
|
"name": diff.AttrTypeUpdate,
|
||||||
"value": diff.AttrTypeUpdate,
|
"value": diff.AttrTypeUpdate,
|
||||||
"ttl": diff.AttrTypeCreate,
|
"ttl": diff.AttrTypeUpdate,
|
||||||
"type": diff.AttrTypeUpdate,
|
"type": diff.AttrTypeUpdate,
|
||||||
},
|
},
|
||||||
|
|
||||||
ComputedAttrs: []string{
|
ComputedAttrs: []string{
|
||||||
"priority",
|
"priority",
|
||||||
"domain_id",
|
"domain_id",
|
||||||
|
"ttl",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,25 +155,20 @@ func resource_dnsimple_record_update_state(
|
||||||
s.Attributes["name"] = rec.Name
|
s.Attributes["name"] = rec.Name
|
||||||
s.Attributes["value"] = rec.Content
|
s.Attributes["value"] = rec.Content
|
||||||
s.Attributes["type"] = rec.RecordType
|
s.Attributes["type"] = rec.RecordType
|
||||||
s.Attributes["ttl"] = strconv.Itoa(rec.TTL)
|
s.Attributes["ttl"] = rec.StringTtl()
|
||||||
s.Attributes["priority"] = strconv.Itoa(rec.Priority)
|
s.Attributes["priority"] = rec.StringPrio()
|
||||||
s.Attributes["domain_id"] = strconv.Itoa(rec.DomainId)
|
s.Attributes["domain_id"] = rec.StringDomainId()
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_dnsimple_record_retrieve(domain string, id string, client *dnsimple.DNSimpleClient) (*dnsimple.Record, error) {
|
func resource_dnsimple_record_retrieve(domain string, id string, client *dnsimple.Client) (*dnsimple.Record, error) {
|
||||||
intId, err := strconv.Atoi(id)
|
record, err := client.RetrieveRecord(domain, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
record, err := client.RetrieveRecord(domain, intId)
|
return record, nil
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Error retrieving record: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &record, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resource_dnsimple_record_validation() *config.Validator {
|
func resource_dnsimple_record_validation() *config.Validator {
|
||||||
|
|
|
@ -3,12 +3,11 @@ package dnsimple
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/resource"
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/rubyist/go-dnsimple"
|
"github.com/pearkes/dnsimple"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAccDNSimpleRecord_Basic(t *testing.T) {
|
func TestAccDNSimpleRecord_Basic(t *testing.T) {
|
||||||
|
@ -37,6 +36,45 @@ func TestAccDNSimpleRecord_Basic(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccDNSimpleRecord_Updated(t *testing.T) {
|
||||||
|
var record dnsimple.Record
|
||||||
|
domain := os.Getenv("DNSIMPLE_DOMAIN")
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckDNSimpleRecordDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
resource.TestStep{
|
||||||
|
Config: fmt.Sprintf(testAccCheckDNSimpleRecordConfig_basic, domain),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDNSimpleRecordExists("dnsimple_record.foobar", &record),
|
||||||
|
testAccCheckDNSimpleRecordAttributes(&record),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "name", "terraform"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "domain", domain),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "value", "192.168.0.10"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
resource.TestStep{
|
||||||
|
Config: fmt.Sprintf(testAccCheckDNSimpleRecordConfig_new_value, domain),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckDNSimpleRecordExists("dnsimple_record.foobar", &record),
|
||||||
|
testAccCheckDNSimpleRecordAttributesUpdated(&record),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "name", "terraform"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "domain", domain),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"dnsimple_record.foobar", "value", "192.168.0.11"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckDNSimpleRecordDestroy(s *terraform.State) error {
|
func testAccCheckDNSimpleRecordDestroy(s *terraform.State) error {
|
||||||
client := testAccProvider.client
|
client := testAccProvider.client
|
||||||
|
|
||||||
|
@ -45,12 +83,7 @@ func testAccCheckDNSimpleRecordDestroy(s *terraform.State) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
intId, err := strconv.Atoi(rs.ID)
|
_, err := client.RetrieveRecord(rs.Attributes["domain"], rs.ID)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = client.RetrieveRecord(rs.Attributes["domain"], intId)
|
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("Record still exists")
|
return fmt.Errorf("Record still exists")
|
||||||
|
@ -71,6 +104,17 @@ func testAccCheckDNSimpleRecordAttributes(record *dnsimple.Record) resource.Test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAccCheckDNSimpleRecordAttributesUpdated(record *dnsimple.Record) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
|
||||||
|
if record.Content != "192.168.0.11" {
|
||||||
|
return fmt.Errorf("Bad content: %s", record.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testAccCheckDNSimpleRecordExists(n string, record *dnsimple.Record) resource.TestCheckFunc {
|
func testAccCheckDNSimpleRecordExists(n string, record *dnsimple.Record) resource.TestCheckFunc {
|
||||||
return func(s *terraform.State) error {
|
return func(s *terraform.State) error {
|
||||||
rs, ok := s.Resources[n]
|
rs, ok := s.Resources[n]
|
||||||
|
@ -85,22 +129,17 @@ func testAccCheckDNSimpleRecordExists(n string, record *dnsimple.Record) resourc
|
||||||
|
|
||||||
client := testAccProvider.client
|
client := testAccProvider.client
|
||||||
|
|
||||||
intId, err := strconv.Atoi(rs.ID)
|
foundRecord, err := client.RetrieveRecord(rs.Attributes["domain"], rs.ID)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
foundRecord, err := client.RetrieveRecord(rs.Attributes["domain"], intId)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if strconv.Itoa(foundRecord.Id) != rs.ID {
|
if foundRecord.StringId() != rs.ID {
|
||||||
return fmt.Errorf("Record not found")
|
return fmt.Errorf("Record not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
*record = foundRecord
|
*record = *foundRecord
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -115,3 +154,13 @@ resource "dnsimple_record" "foobar" {
|
||||||
type = "A"
|
type = "A"
|
||||||
ttl = 3600
|
ttl = 3600
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
const testAccCheckDNSimpleRecordConfig_new_value = `
|
||||||
|
resource "dnsimple_record" "foobar" {
|
||||||
|
domain = "%s"
|
||||||
|
|
||||||
|
name = "terraform"
|
||||||
|
value = "192.168.0.11"
|
||||||
|
type = "A"
|
||||||
|
ttl = 3600
|
||||||
|
}`
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/helper/config"
|
"github.com/hashicorp/terraform/helper/config"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
"github.com/rubyist/go-dnsimple"
|
"github.com/pearkes/dnsimple"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ResourceProvider struct {
|
type ResourceProvider struct {
|
||||||
Config Config
|
Config Config
|
||||||
|
|
||||||
client *dnsimple.DNSimpleClient
|
client *dnsimple.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||||
|
|
|
@ -75,6 +75,6 @@ func testAccPreCheck(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := os.Getenv("DNSIMPLE_DOMAIN"); v == "" {
|
if v := os.Getenv("DNSIMPLE_DOMAIN"); v == "" {
|
||||||
t.Fatal("DNSIMPLE_DOMAIN must be set for acceptance tests. The domain is used to create and destroy record against.")
|
t.Fatal("DNSIMPLE_DOMAIN must be set for acceptance tests. The domain is used to ` and destroy record against.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ func FormatState(s *terraform.State, c *colorstring.Colorize) string {
|
||||||
|
|
||||||
taintStr := ""
|
taintStr := ""
|
||||||
if s.Tainted != nil {
|
if s.Tainted != nil {
|
||||||
if _, ok := s.Tainted[id]; ok {
|
if _, ok := s.Tainted[k]; ok {
|
||||||
taintStr = " (tainted)"
|
taintStr = " (tainted)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,6 @@ func (m *Meta) contextOpts() *terraform.ContextOpts {
|
||||||
opts.Hooks[0] = m.uiHook()
|
opts.Hooks[0] = m.uiHook()
|
||||||
copy(opts.Hooks[1:], m.ContextOpts.Hooks)
|
copy(opts.Hooks[1:], m.ContextOpts.Hooks)
|
||||||
copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks)
|
copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks)
|
||||||
println(fmt.Sprintf("%#v", opts.Hooks))
|
|
||||||
|
|
||||||
if len(m.variables) > 0 {
|
if len(m.variables) > 0 {
|
||||||
vs := make(map[string]string)
|
vs := make(map[string]string)
|
||||||
|
|
72
config.go
72
config.go
|
@ -108,31 +108,10 @@ func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
|
func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
|
||||||
originalPath := path
|
|
||||||
|
|
||||||
return func() (terraform.ResourceProvider, error) {
|
return func() (terraform.ResourceProvider, error) {
|
||||||
// First look for the provider on the PATH.
|
|
||||||
path, err := exec.LookPath(path)
|
|
||||||
if err != nil {
|
|
||||||
// If that doesn't work, look for it in the same directory
|
|
||||||
// as the executable that is running.
|
|
||||||
exePath, err := osext.Executable()
|
|
||||||
if err == nil {
|
|
||||||
path = filepath.Join(
|
|
||||||
filepath.Dir(exePath),
|
|
||||||
filepath.Base(originalPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we still don't have a path set, then set it to the
|
|
||||||
// original path and let any errors that happen bubble out.
|
|
||||||
if path == "" {
|
|
||||||
path = originalPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the plugin client configuration and init the plugin
|
// Build the plugin client configuration and init the plugin
|
||||||
var config plugin.ClientConfig
|
var config plugin.ClientConfig
|
||||||
config.Cmd = exec.Command(path)
|
config.Cmd = pluginCmd(path)
|
||||||
config.Managed = true
|
config.Managed = true
|
||||||
client := plugin.NewClient(&config)
|
client := plugin.NewClient(&config)
|
||||||
|
|
||||||
|
@ -168,31 +147,10 @@ func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisioner
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
|
func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
|
||||||
originalPath := path
|
|
||||||
|
|
||||||
return func() (terraform.ResourceProvisioner, error) {
|
return func() (terraform.ResourceProvisioner, error) {
|
||||||
// First look for the provider on the PATH.
|
|
||||||
path, err := exec.LookPath(path)
|
|
||||||
if err != nil {
|
|
||||||
// If that doesn't work, look for it in the same directory
|
|
||||||
// as the executable that is running.
|
|
||||||
exePath, err := osext.Executable()
|
|
||||||
if err == nil {
|
|
||||||
path = filepath.Join(
|
|
||||||
filepath.Dir(exePath),
|
|
||||||
filepath.Base(originalPath))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we still don't have a path set, then set it to the
|
|
||||||
// original path and let any errors that happen bubble out.
|
|
||||||
if path == "" {
|
|
||||||
path = originalPath
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the plugin client configuration and init the plugin
|
// Build the plugin client configuration and init the plugin
|
||||||
var config plugin.ClientConfig
|
var config plugin.ClientConfig
|
||||||
config.Cmd = exec.Command(path)
|
config.Cmd = pluginCmd(path)
|
||||||
config.Managed = true
|
config.Managed = true
|
||||||
client := plugin.NewClient(&config)
|
client := plugin.NewClient(&config)
|
||||||
|
|
||||||
|
@ -214,3 +172,29 @@ func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFa
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pluginCmd(path string) *exec.Cmd {
|
||||||
|
originalPath := path
|
||||||
|
|
||||||
|
// First look for the provider on the PATH.
|
||||||
|
path, err := exec.LookPath(path)
|
||||||
|
if err != nil {
|
||||||
|
// If that doesn't work, look for it in the same directory
|
||||||
|
// as the executable that is running.
|
||||||
|
exePath, err := osext.Executable()
|
||||||
|
if err == nil {
|
||||||
|
path = filepath.Join(
|
||||||
|
filepath.Dir(exePath),
|
||||||
|
filepath.Base(originalPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we still don't have a path set, then set it to the
|
||||||
|
// original path and let any errors that happen bubble out.
|
||||||
|
if path == "" {
|
||||||
|
path = originalPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the command to execute the plugin
|
||||||
|
return exec.Command(path)
|
||||||
|
}
|
||||||
|
|
|
@ -232,6 +232,15 @@ CHECK_CYCLES:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for loops to yourself
|
||||||
|
for _, n := range g.Nouns {
|
||||||
|
for _, d := range n.Deps {
|
||||||
|
if d.Source == d.Target {
|
||||||
|
vErr.Cycles = append(vErr.Cycles, []*Noun{n})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return the detailed error
|
// Return the detailed error
|
||||||
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
|
if vErr.MissingRoot || vErr.Unreachable != nil || vErr.Cycles != nil {
|
||||||
return vErr
|
return vErr
|
||||||
|
|
|
@ -11,9 +11,7 @@ import (
|
||||||
func Expand(m map[string]string, key string) interface{} {
|
func Expand(m map[string]string, key string) interface{} {
|
||||||
// If the key is exactly a key in the map, just return it
|
// If the key is exactly a key in the map, just return it
|
||||||
if v, ok := m[key]; ok {
|
if v, ok := m[key]; ok {
|
||||||
if num, err := strconv.ParseInt(v, 0, 0); err == nil {
|
if v == "true" {
|
||||||
return int(num)
|
|
||||||
} else if v == "true" {
|
|
||||||
return true
|
return true
|
||||||
} else if v == "false" {
|
} else if v == "false" {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -44,7 +44,7 @@ func TestExpand(t *testing.T) {
|
||||||
Output: []interface{}{
|
Output: []interface{}{
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
"port": 3000,
|
"port": "3000",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -63,8 +63,8 @@ func TestExpand(t *testing.T) {
|
||||||
map[string]interface{}{
|
map[string]interface{}{
|
||||||
"name": "bar",
|
"name": "bar",
|
||||||
"ports": []interface{}{
|
"ports": []interface{}{
|
||||||
1,
|
"1",
|
||||||
2,
|
"2",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"net/rpc"
|
"net/rpc"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -217,7 +218,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
|
||||||
cmd.Stderr = stderr_w
|
cmd.Stderr = stderr_w
|
||||||
cmd.Stdout = stdout_w
|
cmd.Stdout = stdout_w
|
||||||
|
|
||||||
log.Printf("Starting plugin: %s %#v", cmd.Path, cmd.Args)
|
log.Printf("[DEBUG] Starting plugin: %s %#v", cmd.Path, cmd.Args)
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
@ -248,7 +249,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
|
||||||
cmd.Wait()
|
cmd.Wait()
|
||||||
|
|
||||||
// Log and make sure to flush the logs write away
|
// Log and make sure to flush the logs write away
|
||||||
log.Printf("%s: plugin process exited\n", cmd.Path)
|
log.Printf("[DEBUG] %s: plugin process exited\n", cmd.Path)
|
||||||
os.Stderr.Sync()
|
os.Stderr.Sync()
|
||||||
|
|
||||||
// Mark that we exited
|
// Mark that we exited
|
||||||
|
@ -295,7 +296,7 @@ func (c *Client) Start() (addr net.Addr, err error) {
|
||||||
timeout := time.After(c.config.StartTimeout)
|
timeout := time.After(c.config.StartTimeout)
|
||||||
|
|
||||||
// Start looking for the address
|
// Start looking for the address
|
||||||
log.Printf("Waiting for RPC address for: %s", cmd.Path)
|
log.Printf("[DEBUG] Waiting for RPC address for: %s", cmd.Path)
|
||||||
select {
|
select {
|
||||||
case <-timeout:
|
case <-timeout:
|
||||||
err = errors.New("timeout while waiting for plugin to start")
|
err = errors.New("timeout while waiting for plugin to start")
|
||||||
|
@ -343,7 +344,7 @@ func (c *Client) logStderr(r io.Reader) {
|
||||||
c.config.Stderr.Write([]byte(line))
|
c.config.Stderr.Write([]byte(line))
|
||||||
|
|
||||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||||
log.Printf("%s: %s", c.config.Cmd.Path, line)
|
log.Printf("%s: %s", filepath.Base(c.config.Cmd.Path), line)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
|
|
|
@ -121,7 +121,9 @@ func (c *Context) Apply() (*State, error) {
|
||||||
c.state = c.state.deepcopy()
|
c.state = c.state.deepcopy()
|
||||||
|
|
||||||
// Walk
|
// Walk
|
||||||
|
log.Printf("[INFO] Apply walk starting")
|
||||||
err = g.Walk(c.applyWalkFn())
|
err = g.Walk(c.applyWalkFn())
|
||||||
|
log.Printf("[INFO] Apply walk complete")
|
||||||
|
|
||||||
// Prune the state so that we have as clean a state as possible
|
// Prune the state so that we have as clean a state as possible
|
||||||
c.state.prune()
|
c.state.prune()
|
||||||
|
@ -565,6 +567,16 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update the resulting diff
|
||||||
|
c.sl.Lock()
|
||||||
|
if rs.ID == "" {
|
||||||
|
delete(c.state.Resources, r.Id)
|
||||||
|
delete(c.state.Tainted, r.Id)
|
||||||
|
} else {
|
||||||
|
c.state.Resources[r.Id] = rs
|
||||||
|
}
|
||||||
|
c.sl.Unlock()
|
||||||
|
|
||||||
// Invoke any provisioners we have defined. This is only done
|
// Invoke any provisioners we have defined. This is only done
|
||||||
// if the resource was created, as updates or deletes do not
|
// if the resource was created, as updates or deletes do not
|
||||||
// invoke provisioners.
|
// invoke provisioners.
|
||||||
|
@ -579,21 +591,17 @@ func (c *Context) applyWalkFn() depgraph.WalkFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the resulting diff
|
if tainted {
|
||||||
c.sl.Lock()
|
log.Printf("[DEBUG] %s: Marking as tainted", r.Id)
|
||||||
if rs.ID == "" {
|
|
||||||
delete(c.state.Resources, r.Id)
|
|
||||||
} else {
|
|
||||||
c.state.Resources[r.Id] = rs
|
|
||||||
|
|
||||||
if tainted {
|
c.sl.Lock()
|
||||||
c.state.Tainted[r.Id] = struct{}{}
|
c.state.Tainted[r.Id] = struct{}{}
|
||||||
}
|
c.sl.Unlock()
|
||||||
}
|
}
|
||||||
c.sl.Unlock()
|
|
||||||
|
|
||||||
// Update the state for the resource itself
|
// Update the state for the resource itself
|
||||||
r.State = rs
|
r.State = rs
|
||||||
|
r.Tainted = tainted
|
||||||
|
|
||||||
for _, h := range c.hooks {
|
for _, h := range c.hooks {
|
||||||
handleHook(h.PostApply(r.Id, r.State, applyerr))
|
handleHook(h.PostApply(r.Id, r.State, applyerr))
|
||||||
|
@ -716,6 +724,7 @@ func (c *Context) planWalkFn(result *Plan) depgraph.WalkFunc {
|
||||||
|
|
||||||
if r.Tainted {
|
if r.Tainted {
|
||||||
// Tainted resources must also be destroyed
|
// Tainted resources must also be destroyed
|
||||||
|
log.Printf("[DEBUG] %s: Tainted, marking for destroy", r.Id)
|
||||||
diff.Destroy = true
|
diff.Destroy = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -550,6 +550,52 @@ func TestContextApply_provisionerFail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestContextApply_provisionerResourceRef(t *testing.T) {
|
||||||
|
c := testConfig(t, "apply-provisioner-resource-ref")
|
||||||
|
p := testProvider("aws")
|
||||||
|
pr := testProvisioner()
|
||||||
|
p.ApplyFn = testApplyFn
|
||||||
|
p.DiffFn = testDiffFn
|
||||||
|
pr.ApplyFn = func(rs *ResourceState, c *ResourceConfig) error {
|
||||||
|
val, ok := c.Config["foo"]
|
||||||
|
if !ok || val != "2" {
|
||||||
|
t.Fatalf("bad value for foo: %v %#v", val, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testContext(t, &ContextOpts{
|
||||||
|
Config: c,
|
||||||
|
Providers: map[string]ResourceProviderFactory{
|
||||||
|
"aws": testProviderFuncFixed(p),
|
||||||
|
},
|
||||||
|
Provisioners: map[string]ResourceProvisionerFactory{
|
||||||
|
"shell": testProvisionerFuncFixed(pr),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if _, err := ctx.Plan(nil); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
state, err := ctx.Apply()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(state.String())
|
||||||
|
expected := strings.TrimSpace(testTerraformApplyProvisionerResourceRefStr)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("bad: \n%s", actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify apply was invoked
|
||||||
|
if !pr.ApplyCalled {
|
||||||
|
t.Fatalf("provisioner not invoked")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestContextApply_outputDiffVars(t *testing.T) {
|
func TestContextApply_outputDiffVars(t *testing.T) {
|
||||||
c := testConfig(t, "apply-good")
|
c := testConfig(t, "apply-good")
|
||||||
p := testProvider("aws")
|
p := testProvider("aws")
|
||||||
|
|
|
@ -3,6 +3,7 @@ package terraform
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -100,6 +101,8 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
return nil, errors.New("Config is required for Graph")
|
return nil, errors.New("Config is required for Graph")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Creating graph...")
|
||||||
|
|
||||||
g := new(depgraph.Graph)
|
g := new(depgraph.Graph)
|
||||||
|
|
||||||
// First, build the initial resource graph. This only has the resources
|
// First, build the initial resource graph. This only has the resources
|
||||||
|
@ -160,6 +163,10 @@ func Graph(opts *GraphOpts) (*depgraph.Graph, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"[DEBUG] Graph created and valid. %d nouns.",
|
||||||
|
len(g.Nouns))
|
||||||
|
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -604,20 +611,20 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||||
|
|
||||||
// Handle the resource variables
|
// Handle the resource variables
|
||||||
vars = m.Config.RawConfig.Variables
|
vars = m.Config.RawConfig.Variables
|
||||||
nounAddVariableDeps(g, n, vars)
|
nounAddVariableDeps(g, n, vars, false)
|
||||||
|
|
||||||
// Handle the variables of the resource provisioners
|
// Handle the variables of the resource provisioners
|
||||||
for _, p := range m.Resource.Provisioners {
|
for _, p := range m.Resource.Provisioners {
|
||||||
vars = p.RawConfig.Variables
|
vars = p.RawConfig.Variables
|
||||||
nounAddVariableDeps(g, n, vars)
|
nounAddVariableDeps(g, n, vars, true)
|
||||||
|
|
||||||
vars = p.ConnInfo.Variables
|
vars = p.ConnInfo.Variables
|
||||||
nounAddVariableDeps(g, n, vars)
|
nounAddVariableDeps(g, n, vars, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
case *GraphNodeResourceProvider:
|
case *GraphNodeResourceProvider:
|
||||||
vars = m.Config.RawConfig.Variables
|
vars = m.Config.RawConfig.Variables
|
||||||
nounAddVariableDeps(g, n, vars)
|
nounAddVariableDeps(g, n, vars, false)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
|
@ -627,7 +634,11 @@ func graphAddVariableDeps(g *depgraph.Graph) {
|
||||||
|
|
||||||
// nounAddVariableDeps updates the dependencies of a noun given
|
// nounAddVariableDeps updates the dependencies of a noun given
|
||||||
// a set of associated variable values
|
// a set of associated variable values
|
||||||
func nounAddVariableDeps(g *depgraph.Graph, n *depgraph.Noun, vars map[string]config.InterpolatedVariable) {
|
func nounAddVariableDeps(
|
||||||
|
g *depgraph.Graph,
|
||||||
|
n *depgraph.Noun,
|
||||||
|
vars map[string]config.InterpolatedVariable,
|
||||||
|
removeSelf bool) {
|
||||||
for _, v := range vars {
|
for _, v := range vars {
|
||||||
// Only resource variables impose dependencies
|
// Only resource variables impose dependencies
|
||||||
rv, ok := v.(*config.ResourceVariable)
|
rv, ok := v.(*config.ResourceVariable)
|
||||||
|
@ -641,6 +652,12 @@ func nounAddVariableDeps(g *depgraph.Graph, n *depgraph.Noun, vars map[string]co
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're ignoring self-references, then don't add that
|
||||||
|
// dependency.
|
||||||
|
if removeSelf && n == target {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Build the dependency
|
// Build the dependency
|
||||||
dep := &depgraph.Dependency{
|
dep := &depgraph.Dependency{
|
||||||
Name: rv.ResourceId(),
|
Name: rv.ResourceId(),
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
func init() {
|
func init() {
|
||||||
gob.Register(make([]interface{}, 0))
|
gob.Register(make([]interface{}, 0))
|
||||||
gob.Register(make([]map[string]interface{}, 0))
|
gob.Register(make([]map[string]interface{}, 0))
|
||||||
|
gob.Register(make(map[string]string))
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlanOpts are the options used to generate an execution plan for
|
// PlanOpts are the options used to generate an execution plan for
|
||||||
|
|
|
@ -42,6 +42,9 @@ func (s *State) deepcopy() *State {
|
||||||
for k, v := range s.Resources {
|
for k, v := range s.Resources {
|
||||||
result.Resources[k] = v
|
result.Resources[k] = v
|
||||||
}
|
}
|
||||||
|
for k, v := range s.Tainted {
|
||||||
|
result.Tainted[k] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -139,6 +139,13 @@ aws_instance.foo:
|
||||||
type = aws_instance
|
type = aws_instance
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const testTerraformApplyProvisionerResourceRefStr = `
|
||||||
|
aws_instance.bar:
|
||||||
|
ID = foo
|
||||||
|
num = 2
|
||||||
|
type = aws_instance
|
||||||
|
`
|
||||||
|
|
||||||
const testTerraformApplyDestroyStr = `
|
const testTerraformApplyDestroyStr = `
|
||||||
<no state>
|
<no state>
|
||||||
`
|
`
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
resource "aws_instance" "bar" {
|
||||||
|
num = "2"
|
||||||
|
|
||||||
|
provisioner "shell" {
|
||||||
|
foo = "${aws_instance.bar.num}"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
---
|
|
||||||
layout: "docs"
|
|
||||||
page_title: "Commands: Agent"
|
|
||||||
sidebar_current: "docs-commands-agent"
|
|
||||||
---
|
|
||||||
|
|
||||||
# Terraform Agent
|
|
||||||
|
|
||||||
The `terraform agent` command is the heart of Terraform: it runs the agent that
|
|
||||||
performs the important task of maintaining membership information,
|
|
||||||
running checks, announcing services, handling queries, etc.
|
|
||||||
|
|
||||||
Due to the power and flexibility of this command, the Terraform agent
|
|
||||||
is documented in its own section. See the [Terraform Agent](/docs/agent/basics.html)
|
|
||||||
section for more information on how to use this command and the
|
|
||||||
options it has.
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Commands"
|
||||||
|
sidebar_current: "docs-commands"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Terraform Commands (CLI)
|
||||||
|
|
||||||
|
Terraform is controlled via a very easy to use command-line interface (CLI).
|
||||||
|
Terraform is only a single command-line application: terraform. This application
|
||||||
|
then takes a subcommand such as "apply" or "plan". The complete list of subcommands
|
||||||
|
is in the navigation to the left.
|
||||||
|
|
||||||
|
The terraform CLI is a well-behaved command line application. In erroneous cases,
|
||||||
|
a non-zero exit status will be returned. It also responds to -h and --help as you'd
|
||||||
|
most likely expect.
|
||||||
|
|
||||||
|
To view a list of the available commands at any time, just run terraform with no arguments:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform
|
||||||
|
usage: terraform [--version] [--help] <command> [<args>]
|
||||||
|
|
||||||
|
Available commands are:
|
||||||
|
apply Builds or changes infrastructure
|
||||||
|
graph Create a visual graph of Terraform resources
|
||||||
|
output Read an output from a state file
|
||||||
|
plan Generate and show an execution plan
|
||||||
|
refresh Update local state file against real resources
|
||||||
|
show Inspect Terraform state or plan
|
||||||
|
version Prints the Terraform version
|
||||||
|
```
|
||||||
|
|
||||||
|
To get help for any specific command, pass the -h flag to the relevant subcommand. For example,
|
||||||
|
to see help about the members subcommand:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform graph -h
|
||||||
|
Usage: terraform graph [options] PATH
|
||||||
|
|
||||||
|
Outputs the visual graph of Terraform resources. If the path given is
|
||||||
|
the path to a configuration, the dependency graph of the resources are
|
||||||
|
shown. If the path is a plan file, then the dependency graph of the
|
||||||
|
plan itself is shown.
|
||||||
|
|
||||||
|
The graph is outputted in DOT format. The typical program that can
|
||||||
|
read this format is GraphViz, but many web services are also available
|
||||||
|
to read this format.
|
||||||
|
```
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Command: refresh"
|
||||||
|
sidebar_current: "docs-commands-refresh"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: refresh
|
||||||
|
|
||||||
|
The `terraform refresh` command is used to reconcile the state Terraform
|
||||||
|
knows about (via it's state file) with the real-world infrastructure.
|
||||||
|
The can be used to detect any drift from the last-known state, and to
|
||||||
|
update the state file.
|
||||||
|
|
||||||
|
This does not modify infrastructure, but does modify the state file.
|
||||||
|
If the state is changed, this may cause changes to occur during the next
|
||||||
|
plan or apply.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform refresh [options] [dir]`
|
||||||
|
|
||||||
|
By default, `refresh` requires no flags and looks in the current directory
|
||||||
|
for the configuration and state file to refresh.
|
||||||
|
|
||||||
|
The command-line flags are all optional. The list of available flags are:
|
||||||
|
|
||||||
|
* `-no-color` - Disables output with coloring
|
||||||
|
|
||||||
|
* `-state=path` - Path to read and write the state file to. Defaults to "terraform.tfstate".
|
||||||
|
|
||||||
|
* `-state-out=path` - Path to write updated state file. By default, the
|
||||||
|
`-state` path will be used.
|
||||||
|
|
||||||
|
* `-var 'foo=bar'` - Set a variable in the Terraform configuration. This
|
||||||
|
flag can be set multiple times.
|
||||||
|
|
||||||
|
* `-var-file=foo` - Set variables in the Terraform configuration from
|
||||||
|
a file. If "terraform.tfvars" is present, it will be automatically
|
||||||
|
loaded if this flag is not specified.
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Command: show"
|
||||||
|
sidebar_current: "docs-commands-show"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Command: show
|
||||||
|
|
||||||
|
The `terraform show` command is used to provide human-readable output
|
||||||
|
from a state or plan file. This can be used to inspect a plan to ensure
|
||||||
|
that the planned operations are expected, or to inspect the current state
|
||||||
|
as terraform sees it.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Usage: `terraform show [options] <path>`
|
||||||
|
|
||||||
|
You must call `show` with a path to either a Terraform state file or plan
|
||||||
|
file.
|
||||||
|
|
||||||
|
The command-line flags are all optional. The list of available flags are:
|
||||||
|
|
||||||
|
* `-no-color` - Disables output with coloring
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_autoscaling_group"
|
||||||
|
sidebar_current: "docs-aws-resource-autoscale"
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_autoscaling\_group
|
||||||
|
|
||||||
|
Provides an AutoScaling Group resource.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_autoscaling_group" "bar" {
|
||||||
|
availability_zones = ["us-east-1a"]
|
||||||
|
name = "foobar3-terraform-test"
|
||||||
|
max_size = 5
|
||||||
|
min_size = 2
|
||||||
|
health_check_grace_period = 300
|
||||||
|
health_check_type = "ELB"
|
||||||
|
desired_capicity = 4
|
||||||
|
force_delete = true
|
||||||
|
launch_configuration = "${aws_launch_configuration.foobar.name}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The name of the auto scale group.
|
||||||
|
* `max_size` - (Required) The maximum size of the auto scale group.
|
||||||
|
* `min_size` - (Required) The minimum size of the auto scale group.
|
||||||
|
* `availability_zones` - (Required) A list of AZs to launch resources in.
|
||||||
|
* `launch_configuration` - (Required) The ID of the launch configuration to use.
|
||||||
|
* `health_check_grace_period` - (Optional) Time after instance comes into service before checking health.
|
||||||
|
* `health_check_type` - (Optional) "EC2" or "ELB". Controls how health checking is done.
|
||||||
|
* `desired_capicity` - (Optional) The number of Amazon EC2 instances that should be running in the group.
|
||||||
|
* `force_delete` - (Optional) Allows deleting the autoscaling group without waiting
|
||||||
|
for all instances in the pool to terminate.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The autoscaling group name.
|
||||||
|
* `availability_zones` - The availability zones of the autoscale group.
|
||||||
|
* `min_size` - The minimum size of the autoscale group
|
||||||
|
* `max_size` - The maximum size of the autoscale group
|
||||||
|
* `default_cooldown` - Time between a scaling activity and the succeeding scaling activity.
|
||||||
|
* `name` - The name of the autoscale group
|
||||||
|
* `health_check_grace_period` - Time after instance comes into service before checking health.
|
||||||
|
* `health_check_type` - "EC2" or "ELB". Controls how health checking is done.
|
||||||
|
* `desired_capicity` -The number of Amazon EC2 instances that should be running in the group.
|
||||||
|
* `launch_configuration` - The launch configuration of the autoscale group
|
||||||
|
* `vpc_zone_identifier` - The VPC zone identifier
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_db_instance"
|
||||||
|
sidebar_current: "docs-aws-resource-db-instance"
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_db\_instance
|
||||||
|
|
||||||
|
Provides an RDS instance resource.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_db_instance" "default" {
|
||||||
|
identifier = "mydb-rds"
|
||||||
|
allocated_storage = 10
|
||||||
|
engine = "mysql"
|
||||||
|
engine_version = "5.6.17"
|
||||||
|
instance_class = "db.t1.micro"
|
||||||
|
name = "mydb"
|
||||||
|
username = "foo"
|
||||||
|
password = "bar"
|
||||||
|
security_group_names = ["${aws_db_security_group.bar.name}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `allocated_storage` - (Required) The allocated storage in gigabytes.
|
||||||
|
* `engine` - (Required) The database engine to use.
|
||||||
|
* `engine_version` - (Required) The engine version to use.
|
||||||
|
* `identifier` - (Required) The name of the RDS instance
|
||||||
|
* `instance_class` - (Required) The instance type of the RDS instance.
|
||||||
|
* `name` - (Required) The DB name to create.
|
||||||
|
* `password` - (Required) Password for the master DB user.
|
||||||
|
* `username` - (Required) Username for the master DB user.
|
||||||
|
* `availability_zone` - (Optional) The AZ for the RDS instance.
|
||||||
|
* `backup_retention_period` - (Optional) The days to retain backups for.
|
||||||
|
* `backup_window` - (Optional) The backup window.
|
||||||
|
* `iops` - (Optional) The amount of provisioned IOPS
|
||||||
|
* `maintenance_window` - (Optional) The window to perform maintanence in.
|
||||||
|
* `multi_az` - (Optional) Specifies if the RDS instance is multi-AZ
|
||||||
|
* `port` - (Optional) The port on which the DB accepts connetions.
|
||||||
|
* `publicly_accessible` - (Optional) Bool to control if instance is publically accessible.
|
||||||
|
* `vpc_security_group_ids` - (Optional) List of VPC security groups to associate.
|
||||||
|
* `skip_final_snapshot` - (Optional) Enables skipping the final snapshot on deletion.
|
||||||
|
* `security_group_names` - (Optional) List of DB Security Groups to associate.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The RDS instance ID.
|
||||||
|
* `address` - The address of the RDS instance.
|
||||||
|
* `allocated_storage` - The amount of allocated storage
|
||||||
|
* `availability_zone` - The availability zone of the instance
|
||||||
|
* `backup_retention_period` - The backup retention period
|
||||||
|
* `backup_window` - The backup window
|
||||||
|
* `endpoint` - The connection endpoint
|
||||||
|
* `engine` - The database engine
|
||||||
|
* `engine_version` - The database engine version
|
||||||
|
* `instance_class`- The RDS instance class
|
||||||
|
* `maintenance_window` - The instance maintenance window
|
||||||
|
* `multi_az` - If the RDS instance is multi AZ enabled
|
||||||
|
* `name` - The database name
|
||||||
|
* `port` - The database port
|
||||||
|
* `status` - The RDS instance status
|
||||||
|
* `username` - The master username for the database
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
---
|
||||||
|
layout: "aws"
|
||||||
|
page_title: "AWS: aws_db_security_group"
|
||||||
|
sidebar_current: "docs-aws-resource-db-security-group"
|
||||||
|
---
|
||||||
|
|
||||||
|
# aws\_db\_security\_group
|
||||||
|
|
||||||
|
Provides an RDS security group resource.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_db_security_group" "default" {
|
||||||
|
name = "RDS default security group"
|
||||||
|
ingress {
|
||||||
|
cidr = "10.0.0.1/24"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Required) The name of the DB security group.
|
||||||
|
* `description` - (Required) The description of the DB security group.
|
||||||
|
* `ingress` - (Optional) A list of ingress rules.
|
||||||
|
|
||||||
|
Ingress blocks support the following:
|
||||||
|
|
||||||
|
* `cidr` - The CIDR block to accept
|
||||||
|
* `security_group_name` - The name of the security group to authorize
|
||||||
|
* `security_group_id` - The ID of the security group to authorize
|
||||||
|
* `security_group_owner_id` - The owner Id of the security group provided
|
||||||
|
by `security_group_name`.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The db security group ID.
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
---
|
||||||
|
layout: "digitalocean"
|
||||||
|
page_title: "Provider: DigitalOcean"
|
||||||
|
sidebar_current: "docs-do-index"
|
||||||
|
---
|
||||||
|
|
||||||
|
# DigitalOcean Provider
|
||||||
|
|
||||||
|
The DigitalOcean (DO) provider is used to interact with the
|
||||||
|
resources supported by DigitalOcean. The provider needs to be configured
|
||||||
|
with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the DigitalOcean Provider
|
||||||
|
provider "digitalocean" {
|
||||||
|
token = "${var.do_token}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a web server
|
||||||
|
resource "digitalocean_droplet" "web" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `token` - (Required) This is the DO API token.
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
layout: "digitalocean"
|
||||||
|
page_title: "DigitalOcean: digitalocean_droplet"
|
||||||
|
sidebar_current: "docs-do-resource-droplet"
|
||||||
|
---
|
||||||
|
|
||||||
|
# digitalocean\_droplet
|
||||||
|
|
||||||
|
Provides a DigitalOcean droplet resource. This can be used to create,
|
||||||
|
modify, and delete droplets. Droplets also support
|
||||||
|
[provisioning](/docs/provisioners/index.html).
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new Web droplet in the nyc2 region
|
||||||
|
resource "digitalocean_droplet" "web" {
|
||||||
|
image = "ubuntu1404"
|
||||||
|
name = "web-1"
|
||||||
|
region = "nyc2"
|
||||||
|
size = "512mb"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `image` - (Required) The droplet image ID or slug.
|
||||||
|
* `name` - (Required) The droplet name
|
||||||
|
* `region` - (Required) The region to start in
|
||||||
|
* `size` - (Required) The instance size to start
|
||||||
|
* `backups` - (Optional) Boolean controling if backups are made.
|
||||||
|
* `ipv6` - (Optional) Boolean controling if IPv6 is enabled.
|
||||||
|
* `private_networking` - (Optional) Boolean controling if private networks are enabled.
|
||||||
|
* `ssh_keys` - (Optional) A list of SSH IDs or fingerprints to enable.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the droplet
|
||||||
|
* `name`- The name of the droplet
|
||||||
|
* `region` - The region of the droplet
|
||||||
|
* `image` - The image of the droplet
|
||||||
|
* `ipv6` - Is IPv6 enabled
|
||||||
|
* `ipv6_address` - The IPv6 address
|
||||||
|
* `ipv4_address` - The IPv4 address
|
||||||
|
* `locked` - Is the Droplet locked
|
||||||
|
* `private_networking` - Is private networking enabled
|
||||||
|
* `size` - The instance size
|
||||||
|
* `status` - The status of the droplet
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
---
|
||||||
|
layout: "heroku"
|
||||||
|
page_title: "Provider: Heroku"
|
||||||
|
sidebar_current: "docs-heroku-index"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Heroku Provider
|
||||||
|
|
||||||
|
The Heroku provider is used to interact with the
|
||||||
|
resources supported by Heroku. The provider needs to be configured
|
||||||
|
with the proper credentials before it can be used.
|
||||||
|
|
||||||
|
Use the navigation to the left to read about the available resources.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Configure the Heroku provider
|
||||||
|
provider "heroku" {
|
||||||
|
email = "ops@company.com"
|
||||||
|
api_key = "${var.heroku_api_key}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a new applicaiton
|
||||||
|
resource "heroku_app" "default" {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `api_key` - (Required) Heroku API token
|
||||||
|
* `email` - (Required) Email to be notified by Heroku
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
layout: "heroku"
|
||||||
|
page_title: "Heroku: heroku_addon"
|
||||||
|
sidebar_current: "docs-heroku-resource-addon"
|
||||||
|
---
|
||||||
|
|
||||||
|
# heroku\_addon
|
||||||
|
|
||||||
|
Provides a Heroku Add-On resource. These can be attach
|
||||||
|
services to a Heroku app.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new heroku app
|
||||||
|
resource "heroku_app" "default" {
|
||||||
|
name = "test-app"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add a web-hook addon for the app
|
||||||
|
resource "heroku_addon" "webhook" {
|
||||||
|
app = "${heroku_app.default.name}"
|
||||||
|
plan = "deployhooks:http"
|
||||||
|
config {
|
||||||
|
url = "http://google.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `app` - (Required) The Heroku app to add to.
|
||||||
|
* `plan` - (Required) The addon to add.
|
||||||
|
* `config` - (Optional) Optional plan configuration.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the add-on
|
||||||
|
* `name` - The add-on name
|
||||||
|
* `plan` - The plan name
|
||||||
|
* `provider_id` - The ID of the plan provider
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "heroku"
|
||||||
|
page_title: "Heroku: heroku_app"
|
||||||
|
sidebar_current: "docs-heroku-resource-app"
|
||||||
|
---
|
||||||
|
|
||||||
|
# heroku\_app
|
||||||
|
|
||||||
|
Provides a Heroku App resource. This can be used to
|
||||||
|
create and manage applications on Heroku.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new heroku app
|
||||||
|
resource "heroku_app" "default" {
|
||||||
|
name = "my-cool-app"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `name` - (Optional) The name of the Heroku app
|
||||||
|
* `region` - (Optional) The region of the Heroku app
|
||||||
|
* `stack` - (Optional) The stack for the Heroku app
|
||||||
|
* `config_vars` - (Optional) Configuration variables for the app
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the app
|
||||||
|
* `name` - The name of the app
|
||||||
|
* `stack` - The stack of the app
|
||||||
|
* `region` - The region of the app
|
||||||
|
* `git_url` - The Git URL for the app
|
||||||
|
* `web_url` - The Web URL for the app
|
||||||
|
* `heroku_hostname` - The Heroku URL for the app
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
---
|
||||||
|
layout: "heroku"
|
||||||
|
page_title: "Heroku: heroku_domain"
|
||||||
|
sidebar_current: "docs-heroku-resource-domain"
|
||||||
|
---
|
||||||
|
|
||||||
|
# heroku\_domain
|
||||||
|
|
||||||
|
Provides a Heroku App resource. This can be used to
|
||||||
|
create and manage applications on Heroku.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
# Create a new heroku app
|
||||||
|
resource "heroku_app" "default" {
|
||||||
|
name = "test-app"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Associate a custom domain
|
||||||
|
resource "heroku_domain" "default" {
|
||||||
|
app = "${heroku_app.default.name}"
|
||||||
|
hostname = "terraform.example.com"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
The following arguments are supported:
|
||||||
|
|
||||||
|
* `hostname` - (Required) The hostname to serve requests from.
|
||||||
|
* `app` - (Required) The Heroku app to link to.
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported:
|
||||||
|
|
||||||
|
* `id` - The ID of the of the domain record
|
||||||
|
* `hostname` - The hostname traffic will be served as
|
||||||
|
* `cname` - The cname traffic should route to.
|
||||||
|
|
|
@ -22,7 +22,7 @@ resource "aws_instance" "web" {
|
||||||
provisioner "remote-exec" {
|
provisioner "remote-exec" {
|
||||||
inline = [
|
inline = [
|
||||||
"puppet apply",
|
"puppet apply",
|
||||||
"consul join ${aws_instance.web.private_ip",
|
"consul join ${aws_instance.web.private_ip}",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -194,6 +194,19 @@ a lot more metadata about it. This metadata can actually be referenced
|
||||||
for other resources or outputs, which will be covered later in
|
for other resources or outputs, which will be covered later in
|
||||||
the getting started guide.
|
the getting started guide.
|
||||||
|
|
||||||
|
## Provisioning
|
||||||
|
|
||||||
|
The EC2 instance we launched at this point is based on the AMI
|
||||||
|
given, but has no additional software installed. If you're running
|
||||||
|
an image-based infrastructure (perhaps creating images with
|
||||||
|
[Packer](http://www.packer.io)), then this is all you need.
|
||||||
|
|
||||||
|
However, many infrastructures still require some sort of initialization
|
||||||
|
or software provisioning step. Terraform supports
|
||||||
|
provisioners,
|
||||||
|
which we'll cover a little bit later in the getting started guide,
|
||||||
|
in order to do this.
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
Congratulations! You've built your first infrastructure with Terraform.
|
Congratulations! You've built your first infrastructure with Terraform.
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
---
|
||||||
|
layout: "intro"
|
||||||
|
page_title: "Resource Dependencies"
|
||||||
|
sidebar_current: "gettingstarted-deps"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Resource Dependencies
|
||||||
|
|
||||||
|
In this page, we're going to introduce resource dependencies,
|
||||||
|
where we'll not only see a configuration with multiple resources
|
||||||
|
for the first time, but also scenarios where resource parameters
|
||||||
|
use information from other resources.
|
||||||
|
|
||||||
|
Up to this point, our example has only contained a single resource.
|
||||||
|
Real infrastructure has a diverse set of resources and resource
|
||||||
|
types. Terraform configurations can contain multiple resources,
|
||||||
|
multiple resource types, and these types can even span multiple
|
||||||
|
providers.
|
||||||
|
|
||||||
|
On this page, we'll show a basic example of multiple resources
|
||||||
|
and how to reference the attributes of other resources to configure
|
||||||
|
subsequent resources.
|
||||||
|
|
||||||
|
## Assigning an Elastic IP
|
||||||
|
|
||||||
|
We'll improve our configuration by assigning an elastic IP to
|
||||||
|
the EC2 instance we're managing. Modify your `example.tf` and
|
||||||
|
add the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_eip" "ip" {
|
||||||
|
instance = "${aws_instance.example.id}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This should look familiar from the earlier example of adding
|
||||||
|
an EC2 instance resource, except this time we're building
|
||||||
|
an "aws\_eip" resource type. This resource type allocates
|
||||||
|
and associates an
|
||||||
|
[elastic IP](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html)
|
||||||
|
to an EC2 instance.
|
||||||
|
|
||||||
|
The only parameter for
|
||||||
|
[aws\_eip](/docs/providers/aws/r/eip.html) is "instance" which
|
||||||
|
is the EC2 instance to assign the IP to. For this value, we
|
||||||
|
use an interpolation to use an attribute from the EC2 instance
|
||||||
|
we managed earlier.
|
||||||
|
|
||||||
|
The syntax for this interpolation should be straightforward:
|
||||||
|
it requests the "id" attribute from the "aws\_instance.example"
|
||||||
|
resource.
|
||||||
|
|
||||||
|
## Plan and Execute
|
||||||
|
|
||||||
|
Run `terraform plan` to view the execution plan. The output
|
||||||
|
will look something like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform plan
|
||||||
|
...
|
||||||
|
|
||||||
|
+ aws_eip.ip
|
||||||
|
instance: "" => "${aws_instance.example.id}"
|
||||||
|
private_ip: "" => "<computed>"
|
||||||
|
public_ip: "" => "<computed>"
|
||||||
|
|
||||||
|
+ aws_instance.example
|
||||||
|
ami: "" => "ami-aa7ab6c2"
|
||||||
|
availability_zone: "" => "<computed>"
|
||||||
|
instance_type: "" => "t1.micro"
|
||||||
|
key_name: "" => "<computed>"
|
||||||
|
private_dns: "" => "<computed>"
|
||||||
|
private_ip: "" => "<computed>"
|
||||||
|
public_dns: "" => "<computed>"
|
||||||
|
public_ip: "" => "<computed>"
|
||||||
|
security_groups: "" => "<computed>"
|
||||||
|
subnet_id: "" => "<computed>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Terraform will create two resources: the instance and the elastic
|
||||||
|
IP. In the "instance" value for the "aws\_eip", you can see the
|
||||||
|
raw interpolation is still present. This is because this variable
|
||||||
|
won't be known until the "aws\_instance" is created. It will be
|
||||||
|
replaced at apply-time.
|
||||||
|
|
||||||
|
Next, run `terraform apply`. The output will look similar to the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```
|
||||||
|
aws_instance.example: Creating...
|
||||||
|
ami: "" => "ami-aa7ab6c2"
|
||||||
|
instance_type: "" => "t1.micro"
|
||||||
|
aws_eip.ip: Creating...
|
||||||
|
instance: "" => "i-0e737b25"
|
||||||
|
|
||||||
|
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||||||
|
```
|
||||||
|
|
||||||
|
It is clearer to see from actually running Terraform, but
|
||||||
|
Terraform creates the EC2 instance before the elastic IP
|
||||||
|
address. Due to the interpolation earlier where the elastic
|
||||||
|
IP requires the ID of the EC2 instance, Terraform is able
|
||||||
|
to infer a dependency, and knows to create the instance
|
||||||
|
first.
|
||||||
|
|
||||||
|
## Implicit and Explicit Dependencies
|
||||||
|
|
||||||
|
Most dependencies in Terraform are implicit: Terraform is able
|
||||||
|
to infer dependencies based on usage of attributes of other
|
||||||
|
resources.
|
||||||
|
|
||||||
|
Using this information, Terraform builds a graph of resources.
|
||||||
|
This tells Terraform not only what order to create resources,
|
||||||
|
but also what resources can be created in parallel. In our example,
|
||||||
|
since the IP address depended on the EC2 instance, they could
|
||||||
|
not be created in parallel.
|
||||||
|
|
||||||
|
Implicit dependencies work well and are usually all you ever need.
|
||||||
|
However, you can also specify explicit dependencies with the
|
||||||
|
`depends_on` parameter which is available on any resource. For example,
|
||||||
|
we could modify the "aws\_eip" resource to the following, which
|
||||||
|
effectively does the same thing and is redundant:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_eip" "ip" {
|
||||||
|
instance = "${aws_instance.example.id}"
|
||||||
|
depends_on = ["aws_instance.example"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you're ever unsure about the dependency chain that Terraform
|
||||||
|
is creating, you can use the `terraform graph` command to view
|
||||||
|
the graph. This command outputs a dot-formatted graph which can be
|
||||||
|
viewed with
|
||||||
|
[Graphviz](http://www.graphviz.org/).
|
||||||
|
|
||||||
|
## Non-Dependent Resources
|
||||||
|
|
||||||
|
We can now augment the configuration with another EC2 instance.
|
||||||
|
Because this doesn't rely on any other resource, it can be
|
||||||
|
created in parallel to everything else.
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_instance" "another" {
|
||||||
|
ami = "ami-aa7ab6c2"
|
||||||
|
instance_type = "t1.micro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can view the graph with `terraform graph` to see that
|
||||||
|
nothing depends on this and that it will likely be created
|
||||||
|
in parallel.
|
||||||
|
|
||||||
|
Before moving on, remove this resource from your configuration
|
||||||
|
and `terraform apply` again to destroy it. We won't use the
|
||||||
|
second instance anymore in the getting started guide.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
In this page you were introduced to both multiple resources
|
||||||
|
as well as basic resource dependencies and resource attribute
|
||||||
|
interpolation.
|
||||||
|
|
||||||
|
Moving on, we'll use provisioners to do some basic bootstrapping
|
||||||
|
of our launched instance.
|
|
@ -18,35 +18,37 @@ destroying is a useful action.
|
||||||
|
|
||||||
## Plan
|
## Plan
|
||||||
|
|
||||||
While our infrastructure is simple, viewing the execution plan
|
For Terraform to destroy our infrastructure, we need to ask
|
||||||
of a destroy can be useful to make sure that it is destroying
|
Terraform to generate a destroy execution plan. This is a special
|
||||||
only the resources you expect.
|
kind of execution plan that only destroys all Terraform-managed
|
||||||
|
infrastructure, and doesn't create or update any components.
|
||||||
To ask Terraform to create an execution plan to destroy all
|
|
||||||
infrastructure, run the plan command with the `-destroy` flag.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
$ terraform plan -destroy
|
$ terraform plan -destroy -out=terraform.tfplan
|
||||||
...
|
...
|
||||||
|
|
||||||
- aws_instance.example
|
- aws_instance.example
|
||||||
```
|
```
|
||||||
|
|
||||||
The output says that "aws\_instance.example" will be deleted.
|
The plan command is given two new flags.
|
||||||
|
|
||||||
The `-destroy` flag lets you destroy infrastructure without
|
The first flag, `-destroy` tells Terraform to create an execution
|
||||||
modifying the configuration. You can also destroy infrastructure
|
plan to destroy the infrastructure. You can see in the output that
|
||||||
by simply commenting out or deleting the contents of your
|
our one EC2 instance will be destroyed.
|
||||||
configuration, but usually you just want to destroy an instance
|
|
||||||
of your infrastructure rather than permanently deleting your
|
The second flag, `-out` tells Terraform to save the execution plan
|
||||||
configuration as well. The `-destroy` flag is for this case.
|
to a file. We haven't seen this before, but it isn't limited to
|
||||||
|
only destroys. Any plan can be saved to a file. Terraform can then
|
||||||
|
apply a plan, ensuring that only exactly the plan you saw is executed.
|
||||||
|
For destroys, you must save into a plan, since there is no way to
|
||||||
|
tell `apply` to destroy otherwise.
|
||||||
|
|
||||||
## Apply
|
## Apply
|
||||||
|
|
||||||
Let's apply the destroy:
|
Let's apply the destroy:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ terraform apply -destroy
|
$ terraform apply terraform.tfplan
|
||||||
aws_instance.example: Destroying...
|
aws_instance.example: Destroying...
|
||||||
|
|
||||||
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
|
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
|
||||||
|
@ -57,6 +59,9 @@ Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
|
||||||
Done. Terraform destroyed our one instance, and if you run a
|
Done. Terraform destroyed our one instance, and if you run a
|
||||||
`terraform show`, you'll see that the state file is now empty.
|
`terraform show`, you'll see that the state file is now empty.
|
||||||
|
|
||||||
|
For this command, we gave an argument to `apply` for the first
|
||||||
|
time. You can give apply a specific plan to execute.
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
You now know how to create, modify, and destroy infrastructure.
|
You now know how to create, modify, and destroy infrastructure.
|
||||||
|
|
|
@ -6,24 +6,16 @@ sidebar_current: "gettingstarted-nextsteps"
|
||||||
|
|
||||||
# Next Steps
|
# Next Steps
|
||||||
|
|
||||||
That concludes the getting started guide for Terraform. Hopefully you're able to
|
That concludes the getting started guide for Terraform. Hopefully
|
||||||
see that while Terraform is simple to use, it has a powerful set of features.
|
you're now able to not only see what Terraform is useful for, but
|
||||||
We've covered the basics for all of these features in this guide.
|
you're also able to put this knowledge to use to improve building
|
||||||
|
your own infrastructure.
|
||||||
|
|
||||||
Terraform is designed to be friendly to both the DevOps community and
|
We've covered the basics for all of these features in this guide.
|
||||||
application developers, making it perfect for modern, elastic infrastructures.
|
|
||||||
|
|
||||||
As a next step, the following resources are available:
|
As a next step, the following resources are available:
|
||||||
|
|
||||||
* [Documentation](/docs/index.html) - The documentation is an in-depth reference
|
* [Documentation](/docs/index.html) - The documentation is an in-depth
|
||||||
guide to all the features of Terraform, including technical details about the
|
reference guide to all the features of Terraform, including
|
||||||
internals of how Terraform operates.
|
technical details about the internals of how Terraform operates.
|
||||||
|
|
||||||
* [Guides](/docs/guides/index.html) - This section provides various getting
|
|
||||||
started guides with Terraform, including how to bootstrap a new datacenter.
|
|
||||||
|
|
||||||
* [Examples](https://github.com/hashicorp/terraform/tree/master/demo) -
|
|
||||||
The work-in-progress examples folder within the GitHub
|
|
||||||
repository for Terraform contains functional examples of various use cases
|
|
||||||
of Terraform to help you get started with exactly what you need.
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
---
|
||||||
|
layout: "intro"
|
||||||
|
page_title: "Output Variables"
|
||||||
|
sidebar_current: "gettingstarted-outputs"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Output Variables
|
||||||
|
|
||||||
|
In the previous section, we introduced input variables as a way
|
||||||
|
to parameterize Terraform configurations. In this page, we
|
||||||
|
introduce output variables as a way to organize data to be
|
||||||
|
easily queried and shown back to the Terraform user.
|
||||||
|
|
||||||
|
When building potentially complex infrastructure, Terraform
|
||||||
|
stores hundreds or thousands of attribute values for all your
|
||||||
|
resources. But as a user of Terraform, you may only be interested
|
||||||
|
in a few values of importance, such as a load balancer IP,
|
||||||
|
VPN address, etc.
|
||||||
|
|
||||||
|
Outputs are a way to tell Terraform what data is important.
|
||||||
|
This data is outputted when `apply` is called, and can be
|
||||||
|
queried using the `terraform output` command.
|
||||||
|
|
||||||
|
## Defining Outputs
|
||||||
|
|
||||||
|
Let's define an output to show us the public IP address of the
|
||||||
|
elastic IP address that we create. Add this to any of your
|
||||||
|
`*.tf` files:
|
||||||
|
|
||||||
|
```
|
||||||
|
output "ip" {
|
||||||
|
value = "${aws_eip.ip.public_ip}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This defines an output variables named "ip". The `value` field
|
||||||
|
specifies what the value will be, and almost always contains
|
||||||
|
one or more interpolations, since the output data is typically
|
||||||
|
dynamic in some form. In this case, we're outputting the
|
||||||
|
`public_ip` attribute of the elastic IP address.
|
||||||
|
|
||||||
|
Multiple `output` blocks can be defined to specify multiple
|
||||||
|
output variables.
|
||||||
|
|
||||||
|
## Viewing Outputs
|
||||||
|
|
||||||
|
Run `terraform apply` to populate the output. This only needs
|
||||||
|
to be done once after the output is defined. The apply output
|
||||||
|
should change slightly. At the end you should see this:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform apply
|
||||||
|
...
|
||||||
|
|
||||||
|
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
ip = 50.17.232.209
|
||||||
|
```
|
||||||
|
|
||||||
|
`apply` highlights the outputs. You can also query the outputs
|
||||||
|
after apply-time using `terraform output`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform output ip
|
||||||
|
50.17.232.209
|
||||||
|
```
|
||||||
|
|
||||||
|
This command is useful for scripts to extract outputs.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
You now know how to parameterize configurations with input
|
||||||
|
variables, and extract important data using output variables.
|
||||||
|
|
||||||
|
Next, we're going to use provisioners to install some software
|
||||||
|
on the instances created on top of the base AMI used.
|
|
@ -0,0 +1,110 @@
|
||||||
|
---
|
||||||
|
layout: "intro"
|
||||||
|
page_title: "Provision"
|
||||||
|
sidebar_current: "gettingstarted-provision"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Provision
|
||||||
|
|
||||||
|
You're now able to create and modify infrastructure. This page
|
||||||
|
introduces how to use provisioners to run basic shell scripts on
|
||||||
|
instances when they're created.
|
||||||
|
|
||||||
|
If you're using an image-based infrastructure (perhaps with images
|
||||||
|
created with [Packer](http://www.packer.io)), then what you've
|
||||||
|
learned so far is good enough. But if you need to do some initial
|
||||||
|
setup on your instances, provisioners let you upload files,
|
||||||
|
run shell scripts, etc.
|
||||||
|
|
||||||
|
## Defining a Provisioner
|
||||||
|
|
||||||
|
To define a provisioner, modify the resource block defining the
|
||||||
|
"example" EC2 instance to look like the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_instance" "example" {
|
||||||
|
ami = "ami-aa7ab6c2"
|
||||||
|
instance_type = "t1.micro"
|
||||||
|
|
||||||
|
provisioner "local-exec" {
|
||||||
|
command = "echo ${aws_instance.example.public_ip} > file.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This adds a `provision` block within the `resource` block. Multiple
|
||||||
|
`provision` blocks can be added to define multiple provisoining steps.
|
||||||
|
Terraform supports
|
||||||
|
[multiple provisioners](/docs/provisioners/index.html),
|
||||||
|
but for this example we use the "local-exec" provisioner.
|
||||||
|
|
||||||
|
The "local-exec" provisioner executes a command locally on the machine
|
||||||
|
running Terraform. We're using this provisioner versus the others so
|
||||||
|
we don't have to worry about specifying any
|
||||||
|
[connection info](/docs/provisioners/connection.html) right now.
|
||||||
|
|
||||||
|
## Running Provisioners
|
||||||
|
|
||||||
|
Provisioners are run only when a resource is _created_. They
|
||||||
|
are not a replacement for configuration management and changing
|
||||||
|
the software of an already-running server, and are instead just
|
||||||
|
meant as a way to bootstrap a server. For configuration management,
|
||||||
|
you should use Terraform provisioning to bootstrap a real configuration
|
||||||
|
management solution.
|
||||||
|
|
||||||
|
Make sure that your infrastructure is
|
||||||
|
[destroyed](/intro/getting-started/destroy.html) if it isn't already,
|
||||||
|
then run `apply`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform apply
|
||||||
|
aws_instance.example: Creating...
|
||||||
|
ami: "" => "ami-aa7ab6c2"
|
||||||
|
instance_type: "" => "t1.micro"
|
||||||
|
aws_eip.ip: Creating...
|
||||||
|
instance: "" => "i-213f350a"
|
||||||
|
|
||||||
|
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
|
||||||
|
```
|
||||||
|
|
||||||
|
Terraform currently doesn't output anything to indicate the provisioners
|
||||||
|
have run. This is going to be fixed soon. However, we can verify
|
||||||
|
everything worked by looking at the "file.txt" file:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cat file.txt
|
||||||
|
54.192.26.128
|
||||||
|
```
|
||||||
|
|
||||||
|
It contains the IP, just ask we asked!
|
||||||
|
|
||||||
|
## Failed Provisioners and Tainted Resources
|
||||||
|
|
||||||
|
If a resource successfully creates but fails during provision,
|
||||||
|
Terraform will error and mark the resource as "tainted." A
|
||||||
|
resource that is tainted has been physically created, but can't
|
||||||
|
be considered safe to use since provisioning failed.
|
||||||
|
|
||||||
|
When you generate your next execution plan, Terraform will remove
|
||||||
|
any tainted resources and create new resources, attempting to
|
||||||
|
provision again. It does not attempt to restart provisioning on the
|
||||||
|
same resource because it isn't guaranteed to be safe.
|
||||||
|
|
||||||
|
Terraform does not automatically roll back and destroy the resource
|
||||||
|
during the apply when the failure happens, because that would go
|
||||||
|
against the execution plan: the execution plan would've said a
|
||||||
|
resource will be created, but does not say it will ever be deleted.
|
||||||
|
But if you create an execution plan with a tainted resource, the
|
||||||
|
plan will clearly state that the resource will be destroyed because
|
||||||
|
it is tainted.
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
Provisioning is important for being able to bootstrap instances.
|
||||||
|
As another reminder, it is not a replacement for configuration
|
||||||
|
management. It is meant to simply bootstrap machines. If you use
|
||||||
|
configuration management, you should use the provisioning as a way
|
||||||
|
to bootstrap the configuration management utility.
|
||||||
|
|
||||||
|
In the next section, we start looking at variables as a way to
|
||||||
|
better parameterize our configurations.
|
|
@ -0,0 +1,141 @@
|
||||||
|
---
|
||||||
|
layout: "intro"
|
||||||
|
page_title: "Input Variables"
|
||||||
|
sidebar_current: "gettingstarted-variables"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Input Variables
|
||||||
|
|
||||||
|
You now have enough Terraform knowledge to create useful
|
||||||
|
configurations, but we're still hardcoding access keys,
|
||||||
|
AMIs, etc. To become truly shareable and commitable to version
|
||||||
|
control, we need to parameterize the configurations. This page
|
||||||
|
introduces input variables as a way to do this.
|
||||||
|
|
||||||
|
## Defining Variables
|
||||||
|
|
||||||
|
Let's first extract our access key, secret key, and region
|
||||||
|
into a few variables. Create another file `variables.tf` with
|
||||||
|
the following contents. Note that the file can be named anything,
|
||||||
|
since Terraform loads all files ending in `.tf` in a directory.
|
||||||
|
|
||||||
|
```
|
||||||
|
variable "access_key" {}
|
||||||
|
variable "secret_key" {}
|
||||||
|
variable "region" {
|
||||||
|
default = "us-east-1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This defines three variables within your Terraform configuration.
|
||||||
|
The first two have empty blocks `{}`. The third sets a default. If
|
||||||
|
a default value is set, the variable is optional. Otherwise, the
|
||||||
|
variable is required. If you run `terraform plan` now, Terraform will
|
||||||
|
error since the required variables are not set.
|
||||||
|
|
||||||
|
## Using Variables in Configuration
|
||||||
|
|
||||||
|
Next, replace the AWS provider configuration with the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
provider "aws" {
|
||||||
|
access_key = "${var.access_key}"
|
||||||
|
secret_key = "${var.secret_key}"
|
||||||
|
region = "${var.region}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses more interpolations, this time prefixed with `var.`. This
|
||||||
|
tells Terraform that you're accessing variables. This configures
|
||||||
|
the AWS provider with the given variables.
|
||||||
|
|
||||||
|
## Assigning Variables
|
||||||
|
|
||||||
|
There are two ways to assign variables.
|
||||||
|
|
||||||
|
First, you can set it directly on the command-line with the
|
||||||
|
`-var` flag. Any command in Terraform that inspects the configuration
|
||||||
|
accepts this flag, such as `apply`, `plan`, and `refresh`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform plan \
|
||||||
|
-var 'access_key=foo' \
|
||||||
|
-var 'secret_key=bar'
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Second, you can create a file and assign variables directly. Create
|
||||||
|
a file named "terraform.tfvars" with the following contents:
|
||||||
|
|
||||||
|
```
|
||||||
|
access_key = "foo"
|
||||||
|
secret_key = "bar"
|
||||||
|
```
|
||||||
|
|
||||||
|
If a "terraform.tfvars" file is present, Terraform automatically loads
|
||||||
|
it to populate variables. If the file is named something else, you can
|
||||||
|
use the `-var-file` flag directly to specify a file.
|
||||||
|
|
||||||
|
We recommend using the "terraform.tfvars" file, and ignoring it from
|
||||||
|
version control.
|
||||||
|
|
||||||
|
## Mappings
|
||||||
|
|
||||||
|
We've replaced our sensitive strings with variables, but we still
|
||||||
|
are hardcoding AMIs. Unfortunately, AMIs are specific to the region
|
||||||
|
that is in use. One option is to just ask the user to input the proper
|
||||||
|
AMI for the region, but Terraform can do better than that with
|
||||||
|
_mappings_.
|
||||||
|
|
||||||
|
Mappings are a way to create variables that are lookup tables. An example
|
||||||
|
will show this best. Let's extract our AMIs into a mapping and add
|
||||||
|
support for the "us-west-2" region as well:
|
||||||
|
|
||||||
|
```
|
||||||
|
variable "amis" {
|
||||||
|
default = {
|
||||||
|
"us-east-1": "ami-aa7ab6c2",
|
||||||
|
"us-west-2": "ami-23f78e13",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A variable becomes a mapping when it has a default value that is a
|
||||||
|
map like above. There is no way to create a required map.
|
||||||
|
|
||||||
|
Then, replace the "aws\_instance" with the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "aws_instance" "example" {
|
||||||
|
ami = "${lookup(var.amis, var.region)}"
|
||||||
|
instance_type = "t1.micro"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This introduces a new type of interpolation: a function call. The
|
||||||
|
`lookup` function does a dynamic lookup in a map for a key. The
|
||||||
|
key is `var.region`, which specifies that the value of the region
|
||||||
|
variables is the key.
|
||||||
|
|
||||||
|
While we don't use it in our example, it is worth nothing that you
|
||||||
|
can also do a static lookup of a mapping directly with
|
||||||
|
`${var.amis.us-east-1}`.
|
||||||
|
|
||||||
|
We set defaults, but mappings can also be overridden using the
|
||||||
|
`-var` and `-var-file` values. For example, if the user wanted to
|
||||||
|
specify an alternate AMI for us-east-1:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform plan -var 'amis.us-east-1=foo'
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next
|
||||||
|
|
||||||
|
Terraform provides variables for parameterizing your configurations.
|
||||||
|
Mappings let you build lookup tables in cases where that make sense.
|
||||||
|
Setting and using variables is uniform throughout your configurations.
|
||||||
|
|
||||||
|
In the next section, we'll take a look at output variables as a
|
||||||
|
mechanism to expose certain values more prominently to the Terraform
|
||||||
|
operator.
|
|
@ -60,7 +60,6 @@
|
||||||
<li class="first li-under"><a href="/intro/index.html">Intro</a></li>
|
<li class="first li-under"><a href="/intro/index.html">Intro</a></li>
|
||||||
<li class="li-under"><a href="/docs/index.html">Docs</a></li>
|
<li class="li-under"><a href="/docs/index.html">Docs</a></li>
|
||||||
<li class="li-under"><a href="/community.html">Community</a></li>
|
<li class="li-under"><a href="/community.html">Community</a></li>
|
||||||
<li class="li-under"><a href="http://demo.terraform.io/">Demo</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,6 +13,18 @@
|
||||||
<li<%= sidebar_current("docs-aws-resource") %>>
|
<li<%= sidebar_current("docs-aws-resource") %>>
|
||||||
<a href="#">Resources</a>
|
<a href="#">Resources</a>
|
||||||
<ul class="nav nav-visible">
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-aws-resource-autoscale") %>>
|
||||||
|
<a href="/docs/providers/aws/r/autoscale.html">aws_autoscaling_group</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-aws-resource-db-instance") %>>
|
||||||
|
<a href="/docs/providers/aws/r/db_instance.html">aws_db_instance</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-aws-resource-db-security-group") %>>
|
||||||
|
<a href="/docs/providers/aws/r/db_security_group.html">aws_db_security_group</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-aws-resource-eip") %>>
|
<li<%= sidebar_current("docs-aws-resource-eip") %>>
|
||||||
<a href="/docs/providers/aws/r/eip.html">aws_eip</a>
|
<a href="/docs/providers/aws/r/eip.html">aws_eip</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-do-index") %>>
|
||||||
|
<a href="/docs/providers/do/index.html">DigitalOcean Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-do-resource") %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-do-resource-droplet") %>>
|
||||||
|
<a href="/docs/providers/do/r/droplet.html">digitalocean_droplet</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
|
@ -15,37 +15,29 @@
|
||||||
<li<%= sidebar_current("docs-commands") %>>
|
<li<%= sidebar_current("docs-commands") %>>
|
||||||
<a href="/docs/commands/index.html">Commands (CLI)</a>
|
<a href="/docs/commands/index.html">Commands (CLI)</a>
|
||||||
<ul class="nav">
|
<ul class="nav">
|
||||||
<li<%= sidebar_current("docs-commands-agent") %>>
|
<li<%= sidebar_current("docs-commands-apply") %>>
|
||||||
<a href="/docs/commands/agent.html">agent</a>
|
<a href="/docs/commands/apply.html">apply</a>
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-forceleave") %>>
|
|
||||||
<a href="/docs/commands/force-leave.html">force-leave</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-join") %>>
|
|
||||||
<a href="/docs/commands/join.html">join</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-keygen") %>>
|
|
||||||
<a href="/docs/commands/keygen.html">keygen</a>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-leave") %>>
|
<li<%= sidebar_current("docs-commands-graph") %>>
|
||||||
<a href="/docs/commands/leave.html">leave</a>
|
<a href="/docs/commands/graph.html">graph</a>
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-members") %>>
|
|
||||||
<a href="/docs/commands/members.html">members</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-monitor") %>>
|
|
||||||
<a href="/docs/commands/monitor.html">monitor</a>
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-commands-info") %>>
|
<li<%= sidebar_current("docs-commands-output") %>>
|
||||||
<a href="/docs/commands/info.html">info</a>
|
<a href="/docs/commands/output.html">output</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-commands-plan") %>>
|
||||||
|
<a href="/docs/commands/plan.html">plan</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-commands-refresh") %>>
|
||||||
|
<a href="/docs/commands/refresh.html">refresh</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-commands-show") %>>
|
||||||
|
<a href="/docs/commands/show.html">show</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<% wrap_layout :inner do %>
|
||||||
|
<% content_for :sidebar do %>
|
||||||
|
<div class="docs-sidebar hidden-print affix-top" role="complementary">
|
||||||
|
<ul class="nav docs-sidenav">
|
||||||
|
<li<%= sidebar_current("docs-home") %>>
|
||||||
|
<a href="/docs/index.html">« Documentation Home</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-heroku-index") %>>
|
||||||
|
<a href="/docs/providers/heroku/index.html">Heroku Provider</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-heroku-resource") %>>
|
||||||
|
<a href="#">Resources</a>
|
||||||
|
<ul class="nav nav-visible">
|
||||||
|
<li<%= sidebar_current("docs-heroku-resource-addon") %>>
|
||||||
|
<a href="/docs/providers/heroku/r/addon.html">heroku_addon</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-heroku-resource-app") %>>
|
||||||
|
<a href="/docs/providers/heroku/r/app.html">heroku_app</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-heroku-resource-domain") %>>
|
||||||
|
<a href="/docs/providers/heroku/r/domain.html">heroku_domain</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= yield %>
|
||||||
|
<% end %>
|
|
@ -42,20 +42,20 @@
|
||||||
<a href="/intro/getting-started/destroy.html">Destroy Infrastructure</a>
|
<a href="/intro/getting-started/destroy.html">Destroy Infrastructure</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("gettingstarted-outputs") %>>
|
|
||||||
<a href="/intro/getting-started/outputs.html">Output Variables</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li<%= sidebar_current("gettingstarted-deps") %>>
|
<li<%= sidebar_current("gettingstarted-deps") %>>
|
||||||
<a href="/intro/getting-started/dependencies.html">Resource Dependencies</a>
|
<a href="/intro/getting-started/dependencies.html">Resource Dependencies</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("gettingstarted-provision") %>>
|
||||||
|
<a href="/intro/getting-started/provision.html">Provision</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
<li<%= sidebar_current("gettingstarted-variables") %>>
|
||||||
<a href="/intro/getting-started/variables.html">Input Variables</a>
|
<a href="/intro/getting-started/variables.html">Input Variables</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
<li<%= sidebar_current("gettingstarted-outputs") %>>
|
||||||
<a href="/intro/getting-started/provisioners.html">Provision</a>
|
<a href="/intro/getting-started/outputs.html">Output Variables</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("gettingstarted-nextsteps") %>>
|
<li<%= sidebar_current("gettingstarted-nextsteps") %>>
|
||||||
|
|
|
@ -6,6 +6,8 @@ body.page-sub{
|
||||||
background-color: @light-black;
|
background-color: @light-black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.layout-heroku,
|
||||||
|
body.layout-digitalocean,
|
||||||
body.layout-aws,
|
body.layout-aws,
|
||||||
body.layout-docs,
|
body.layout-docs,
|
||||||
body.layout-inner,
|
body.layout-inner,
|
||||||
|
@ -252,7 +254,7 @@ body.layout-intro{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bs-docs-section{
|
.bs-docs-section{
|
||||||
h1{
|
h1{
|
||||||
|
|
|
@ -1141,12 +1141,16 @@ body.page-home #footer {
|
||||||
body.page-sub {
|
body.page-sub {
|
||||||
background-color: #242424;
|
background-color: #242424;
|
||||||
}
|
}
|
||||||
|
body.layout-heroku,
|
||||||
|
body.layout-digitalocean,
|
||||||
body.layout-aws,
|
body.layout-aws,
|
||||||
body.layout-docs,
|
body.layout-docs,
|
||||||
body.layout-inner,
|
body.layout-inner,
|
||||||
body.layout-intro {
|
body.layout-intro {
|
||||||
background: #242424 url('../images/sidebar-wire.png') left 62px no-repeat;
|
background: #242424 url('../images/sidebar-wire.png') left 62px no-repeat;
|
||||||
}
|
}
|
||||||
|
body.layout-heroku > .container .col-md-8[role=main],
|
||||||
|
body.layout-digitalocean > .container .col-md-8[role=main],
|
||||||
body.layout-aws > .container .col-md-8[role=main],
|
body.layout-aws > .container .col-md-8[role=main],
|
||||||
body.layout-docs > .container .col-md-8[role=main],
|
body.layout-docs > .container .col-md-8[role=main],
|
||||||
body.layout-inner > .container .col-md-8[role=main],
|
body.layout-inner > .container .col-md-8[role=main],
|
||||||
|
@ -1154,6 +1158,8 @@ body.layout-intro > .container .col-md-8[role=main] {
|
||||||
min-height: 800px;
|
min-height: 800px;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
body.layout-heroku > .container .col-md-8[role=main] > div,
|
||||||
|
body.layout-digitalocean > .container .col-md-8[role=main] > div,
|
||||||
body.layout-aws > .container .col-md-8[role=main] > div,
|
body.layout-aws > .container .col-md-8[role=main] > div,
|
||||||
body.layout-docs > .container .col-md-8[role=main] > div,
|
body.layout-docs > .container .col-md-8[role=main] > div,
|
||||||
body.layout-inner > .container .col-md-8[role=main] > div,
|
body.layout-inner > .container .col-md-8[role=main] > div,
|
||||||
|
|
Loading…
Reference in New Issue