Merge branch 'master' of github.com:hashicorp/terraform
This commit is contained in:
commit
f39c1265cb
|
@ -7,3 +7,4 @@ bin/
|
|||
config/y.go
|
||||
config/y.output
|
||||
vendor/
|
||||
website/.vagrant
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/builtin/providers/dnsimple"
|
||||
"github.com/hashicorp/terraform/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(new(dnsimple.ResourceProvider))
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
package main
|
|
@ -47,7 +47,6 @@ func resource_aws_db_security_group_create(
|
|||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
log.Printf("%#v", rs.Attributes)
|
||||
|
||||
if _, ok := rs.Attributes["ingress.#"]; ok {
|
||||
ingresses := flatmap.Expand(
|
||||
|
|
|
@ -2,6 +2,7 @@ package aws
|
|||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
|
@ -26,14 +27,30 @@ type ResourceProvider struct {
|
|||
}
|
||||
|
||||
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
v := &config.Validator{
|
||||
Optional: []string{
|
||||
"access_key",
|
||||
"secret_key",
|
||||
"region",
|
||||
},
|
||||
type param struct {
|
||||
env string
|
||||
key string
|
||||
}
|
||||
params := []param{
|
||||
{"AWS_REGION", "region"},
|
||||
{"AWS_ACCESS_KEY", "access_key"},
|
||||
{"AWS_SECRET_KEY", "secret_key"},
|
||||
}
|
||||
|
||||
var optional []string
|
||||
var required []string
|
||||
for _, p := range params {
|
||||
if v := os.Getenv(p.env); v != "" {
|
||||
optional = append(optional, p.key)
|
||||
} else {
|
||||
required = append(required, p.key)
|
||||
}
|
||||
}
|
||||
|
||||
v := &config.Validator{
|
||||
Required: required,
|
||||
Optional: optional,
|
||||
}
|
||||
return v.Validate(c)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/rubyist/go-dnsimple"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Token string `mapstructure:"token"`
|
||||
Email string `mapstructure:"email"`
|
||||
}
|
||||
|
||||
// Client() returns a new client for accessing heroku.
|
||||
//
|
||||
func (c *Config) Client() (*dnsimple.DNSimpleClient, error) {
|
||||
|
||||
// If we have env vars set (like in the acc) tests,
|
||||
// we need to override the values passed in here.
|
||||
if v := os.Getenv("DNSIMPLE_EMAIL"); v != "" {
|
||||
c.Email = v
|
||||
}
|
||||
if v := os.Getenv("DNSIMPLE_TOKEN"); v != "" {
|
||||
c.Token = v
|
||||
}
|
||||
|
||||
client := dnsimple.NewClient(c.Token, c.Email)
|
||||
|
||||
log.Printf("[INFO] DNSimple Client configured for user: %s", client.Email)
|
||||
|
||||
return client, nil
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/rubyist/go-dnsimple"
|
||||
)
|
||||
|
||||
func resource_dnsimple_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 := dnsimple.Record{
|
||||
Name: rs.Attributes["name"],
|
||||
Content: rs.Attributes["value"],
|
||||
RecordType: rs.Attributes["type"],
|
||||
}
|
||||
|
||||
if attr, ok := rs.Attributes["ttl"]; ok {
|
||||
newRecord.TTL, err = strconv.Atoi(attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] record create configuration: %#v", newRecord)
|
||||
|
||||
rec, err := client.CreateRecord(rs.Attributes["domain"], newRecord)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create record: %s", err)
|
||||
}
|
||||
|
||||
rs.ID = strconv.Itoa(rec.Id)
|
||||
|
||||
log.Printf("[INFO] record ID: %s", rs.ID)
|
||||
|
||||
return resource_dnsimple_record_update_state(rs, &rec)
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_update(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
|
||||
panic("Cannot update record")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_destroy(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) error {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
log.Printf("[INFO] Deleting record: %s", s.ID)
|
||||
|
||||
rec, err := resource_dnsimple_record_retrieve(s.Attributes["domain"], s.ID, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rec.Delete(client)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting record: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
rec, err := resource_dnsimple_record_retrieve(s.Attributes["app"], s.ID, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource_dnsimple_record_update_state(s, rec)
|
||||
}
|
||||
|
||||
func resource_dnsimple_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.AttrTypeCreate,
|
||||
"value": diff.AttrTypeUpdate,
|
||||
"ttl": diff.AttrTypeCreate,
|
||||
"type": diff.AttrTypeUpdate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"priority",
|
||||
"domain_id",
|
||||
},
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_update_state(
|
||||
s *terraform.ResourceState,
|
||||
rec *dnsimple.Record) (*terraform.ResourceState, error) {
|
||||
|
||||
s.Attributes["name"] = rec.Name
|
||||
s.Attributes["value"] = rec.Content
|
||||
s.Attributes["type"] = rec.RecordType
|
||||
s.Attributes["ttl"] = strconv.Itoa(rec.TTL)
|
||||
s.Attributes["priority"] = strconv.Itoa(rec.Priority)
|
||||
s.Attributes["domain_id"] = strconv.Itoa(rec.DomainId)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_retrieve(domain string, id string, client *dnsimple.DNSimpleClient) (*dnsimple.Record, error) {
|
||||
intId, err := strconv.Atoi(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record, err := client.RetrieveRecord(domain, intId)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving record: %s", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
func resource_dnsimple_record_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{
|
||||
"domain",
|
||||
"name",
|
||||
"value",
|
||||
"type",
|
||||
},
|
||||
Optional: []string{
|
||||
"ttl",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/rubyist/go-dnsimple"
|
||||
)
|
||||
|
||||
func TestAccDNSimpleRecord_Basic(t *testing.T) {
|
||||
var record dnsimple.Record
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckDNSimpleRecordDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckDNSimpleRecordConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckDNSimpleRecordExists("dnsimple_record.foobar", &record),
|
||||
testAccCheckDNSimpleRecordAttributes(&record),
|
||||
resource.TestCheckResourceAttr(
|
||||
"dnsimple_record.foobar", "name", "terraform"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"dnsimple_record.foobar", "domain", "jack.ly"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"dnsimple_record.foobar", "value", "192.168.0.10"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckDNSimpleRecordDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "dnsimple_record" {
|
||||
continue
|
||||
}
|
||||
|
||||
intId, err := strconv.Atoi(rs.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.RetrieveRecord(rs.Attributes["domain"], intId)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Record still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckDNSimpleRecordAttributes(record *dnsimple.Record) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if record.Content != "192.168.0.10" {
|
||||
return fmt.Errorf("Bad content: %s", record.Content)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckDNSimpleRecordExists(n string, record *dnsimple.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
|
||||
|
||||
intId, err := strconv.Atoi(rs.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
foundRecord, err := client.RetrieveRecord(rs.Attributes["domain"], intId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if strconv.Itoa(foundRecord.Id) != rs.ID {
|
||||
return fmt.Errorf("Record not found")
|
||||
}
|
||||
|
||||
*record = foundRecord
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccCheckDNSimpleRecordConfig_basic = `
|
||||
resource "dnsimple_record" "foobar" {
|
||||
domain = "jack.ly"
|
||||
|
||||
name = "terraform"
|
||||
value = "192.168.0.10"
|
||||
type = "A"
|
||||
ttl = 3600
|
||||
}`
|
|
@ -0,0 +1,68 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/rubyist/go-dnsimple"
|
||||
)
|
||||
|
||||
type ResourceProvider struct {
|
||||
Config Config
|
||||
|
||||
client *dnsimple.DNSimpleClient
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
v := &config.Validator{
|
||||
Required: []string{
|
||||
"token",
|
||||
"email",
|
||||
},
|
||||
}
|
||||
|
||||
return v.Validate(c)
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) ValidateResource(
|
||||
t string, c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return resourceMap.Validate(t, c)
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Configure(c *terraform.ResourceConfig) error {
|
||||
if _, err := config.Decode(&p.Config, c.Config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Println("[INFO] Initializing DNSimple client")
|
||||
var err error
|
||||
p.client, err = p.Config.Client()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Apply(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff) (*terraform.ResourceState, error) {
|
||||
return resourceMap.Apply(s, d, p)
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
||||
return resourceMap.Diff(s, c, p)
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Refresh(
|
||||
s *terraform.ResourceState) (*terraform.ResourceState, error) {
|
||||
return resourceMap.Refresh(s, p)
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Resources() []terraform.ResourceType {
|
||||
return resourceMap.Resources()
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testAccProviders map[string]terraform.ResourceProvider
|
||||
var testAccProvider *ResourceProvider
|
||||
|
||||
func init() {
|
||||
testAccProvider = new(ResourceProvider)
|
||||
testAccProviders = map[string]terraform.ResourceProvider{
|
||||
"dnsimple": testAccProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = new(ResourceProvider)
|
||||
}
|
||||
|
||||
func TestResourceProvider_Configure(t *testing.T) {
|
||||
rp := new(ResourceProvider)
|
||||
var expectedToken string
|
||||
var expectedEmail string
|
||||
|
||||
if v := os.Getenv("DNSIMPLE_EMAIL"); v != "" {
|
||||
expectedEmail = v
|
||||
} else {
|
||||
expectedEmail = "foo"
|
||||
}
|
||||
|
||||
if v := os.Getenv("DNSIMPLE_TOKEN"); v != "" {
|
||||
expectedToken = v
|
||||
} else {
|
||||
expectedToken = "foo"
|
||||
}
|
||||
|
||||
raw := map[string]interface{}{
|
||||
"token": expectedToken,
|
||||
"email": expectedEmail,
|
||||
}
|
||||
|
||||
rawConfig, err := config.NewRawConfig(raw)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := Config{
|
||||
Token: expectedToken,
|
||||
Email: expectedEmail,
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rp.Config, expected) {
|
||||
t.Fatalf("bad: %#v", rp.Config)
|
||||
}
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("DNSIMPLE_EMAIL"); v == "" {
|
||||
t.Fatal("DNSIMPLE_EMAIL must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("DNSIMPLE_TOKEN"); v == "" {
|
||||
t.Fatal("DNSIMPLE_TOKEN must be set for acceptance tests")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package dnsimple
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
)
|
||||
|
||||
// resourceMap is the mapping of resources we support to their basic
|
||||
// operations. This makes it easy to implement new resource types.
|
||||
var resourceMap *resource.Map
|
||||
|
||||
func init() {
|
||||
resourceMap = &resource.Map{
|
||||
Mapping: map[string]resource.Resource{
|
||||
"dnsimple_record": resource.Resource{
|
||||
ConfigValidator: resource_dnsimple_record_validation(),
|
||||
Create: resource_dnsimple_record_create,
|
||||
Destroy: resource_dnsimple_record_destroy,
|
||||
Diff: resource_dnsimple_record_diff,
|
||||
Update: resource_dnsimple_record_update,
|
||||
Refresh: resource_dnsimple_record_refresh,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/flatmap"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func resource_heroku_addon_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)
|
||||
|
||||
app := rs.Attributes["app"]
|
||||
plan := rs.Attributes["plan"]
|
||||
opts := heroku.AddonCreateOpts{}
|
||||
|
||||
if attr, ok := rs.Attributes["config.#"]; ok && attr == "1" {
|
||||
vs := flatmap.Expand(
|
||||
rs.Attributes, "config").([]interface{})
|
||||
|
||||
config := make(map[string]string)
|
||||
for k, v := range vs[0].(map[string]interface{}) {
|
||||
config[k] = v.(string)
|
||||
}
|
||||
|
||||
opts.Config = &config
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Addon create configuration: %#v, %#v, %#v", app, plan, opts)
|
||||
|
||||
a, err := client.AddonCreate(app, plan, &opts)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
rs.ID = a.Id
|
||||
log.Printf("[INFO] Addon ID: %s", rs.ID)
|
||||
|
||||
addon, err := resource_heroku_addon_retrieve(app, rs.ID, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
|
||||
return resource_heroku_addon_update_state(rs, addon)
|
||||
}
|
||||
|
||||
func resource_heroku_addon_update(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
rs := s.MergeDiff(d)
|
||||
|
||||
app := rs.Attributes["app"]
|
||||
|
||||
if attr, ok := d.Attributes["plan"]; ok {
|
||||
ad, err := client.AddonUpdate(
|
||||
app, rs.ID,
|
||||
attr.New)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Store the new ID
|
||||
rs.ID = ad.Id
|
||||
}
|
||||
|
||||
addon, err := resource_heroku_addon_retrieve(app, rs.ID, client)
|
||||
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
|
||||
return resource_heroku_addon_update_state(rs, addon)
|
||||
}
|
||||
|
||||
func resource_heroku_addon_destroy(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) error {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
log.Printf("[INFO] Deleting Addon: %s", s.ID)
|
||||
|
||||
// Destroy the app
|
||||
err := client.AddonDelete(s.Attributes["app"], s.ID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting addon: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
app, err := resource_heroku_addon_retrieve(s.Attributes["app"], s.ID, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resource_heroku_addon_update_state(s, app)
|
||||
}
|
||||
|
||||
func resource_heroku_addon_diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||
|
||||
b := &diff.ResourceBuilder{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"app": diff.AttrTypeCreate,
|
||||
"plan": diff.AttrTypeUpdate,
|
||||
"config": diff.AttrTypeCreate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"provider_id",
|
||||
"config_vars",
|
||||
},
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
}
|
||||
|
||||
func resource_heroku_addon_update_state(
|
||||
s *terraform.ResourceState,
|
||||
addon *heroku.Addon) (*terraform.ResourceState, error) {
|
||||
|
||||
s.Attributes["name"] = addon.Name
|
||||
s.Attributes["plan"] = addon.Plan.Name
|
||||
s.Attributes["provider_id"] = addon.ProviderId
|
||||
|
||||
toFlatten := make(map[string]interface{})
|
||||
|
||||
if len(addon.ConfigVars) > 0 {
|
||||
toFlatten["config_vars"] = addon.ConfigVars
|
||||
}
|
||||
|
||||
for k, v := range flatmap.Flatten(toFlatten) {
|
||||
s.Attributes[k] = v
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_retrieve(app string, id string, client *heroku.Client) (*heroku.Addon, error) {
|
||||
addon, err := client.AddonInfo(app, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving addon: %s", err)
|
||||
}
|
||||
|
||||
return addon, nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{
|
||||
"app",
|
||||
"plan",
|
||||
},
|
||||
Optional: []string{
|
||||
"config.*",
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuAddon_Basic(t *testing.T) {
|
||||
var addon heroku.Addon
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuAddonDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuAddonConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuAddonExists("heroku_addon.foobar", &addon),
|
||||
testAccCheckHerokuAddonAttributes(&addon),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_addon.foobar", "config.0.url", "http://google.com"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_addon.foobar", "app", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_addon.foobar", "plan", "deployhooks:http"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuAddonDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_addon" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.AddonInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Addon still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckHerokuAddonAttributes(addon *heroku.Addon) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if addon.Plan.Name != "deployhooks:http" {
|
||||
return fmt.Errorf("Bad plan: %s", addon.Plan)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuAddonExists(n string, addon *heroku.Addon) 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 Addon ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
|
||||
foundAddon, err := client.AddonInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundAddon.Id != rs.ID {
|
||||
return fmt.Errorf("Addon not found")
|
||||
}
|
||||
|
||||
*addon = *foundAddon
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccCheckHerokuAddonConfig_basic = `
|
||||
resource "heroku_app" "foobar" {
|
||||
name = "terraform-test-app"
|
||||
}
|
||||
|
||||
resource "heroku_addon" "foobar" {
|
||||
app = "${heroku_app.foobar.name}"
|
||||
plan = "deployhooks:http"
|
||||
config {
|
||||
url = "http://google.com"
|
||||
}
|
||||
}`
|
|
@ -4,12 +4,45 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/flatmap"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/helper/multierror"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/bgentry/heroku-go"
|
||||
)
|
||||
|
||||
// type application is used to store all the details of a heroku app
|
||||
type application struct {
|
||||
Id string // Id of the resource
|
||||
|
||||
App *heroku.App // The heroku application
|
||||
Client *heroku.Client // Client to interact with the heroku API
|
||||
Vars map[string]string // The vars on the application
|
||||
}
|
||||
|
||||
// Updates the application to have the latest from remote
|
||||
func (a *application) Update() error {
|
||||
var errs []error
|
||||
var err error
|
||||
|
||||
a.App, err = a.Client.AppInfo(a.Id)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
a.Vars, err = retrieve_config_vars(a.Id, a.Client)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return &multierror.Error{Errors: errs}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_create(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
|
@ -38,15 +71,29 @@ func resource_heroku_app_create(
|
|||
|
||||
log.Printf("[DEBUG] App create configuration: %#v", opts)
|
||||
|
||||
app, err := client.AppCreate(&opts)
|
||||
a, err := client.AppCreate(&opts)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
rs.ID = app.Name
|
||||
|
||||
rs.ID = a.Name
|
||||
log.Printf("[INFO] App ID: %s", rs.ID)
|
||||
|
||||
if attr, ok := rs.Attributes["config_vars.#"]; ok && attr == "1" {
|
||||
vs := flatmap.Expand(
|
||||
rs.Attributes, "config_vars").([]interface{})
|
||||
|
||||
err = update_config_vars(rs.ID, vs, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
}
|
||||
|
||||
app, err := resource_heroku_app_retrieve(rs.ID, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
|
||||
return resource_heroku_app_update_state(rs, app)
|
||||
}
|
||||
|
||||
|
@ -54,10 +101,52 @@ func resource_heroku_app_update(
|
|||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
rs := s.MergeDiff(d)
|
||||
|
||||
panic("does not update")
|
||||
if attr, ok := d.Attributes["name"]; ok {
|
||||
opts := heroku.AppUpdateOpts{
|
||||
Name: &attr.New,
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
renamedApp, err := client.AppUpdate(rs.ID, &opts)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// Store the new ID
|
||||
rs.ID = renamedApp.Name
|
||||
}
|
||||
|
||||
attr, ok := s.Attributes["config_vars.#"]
|
||||
|
||||
// If the config var block was removed, nuke all config vars
|
||||
if ok && attr == "1" {
|
||||
vs := flatmap.Expand(
|
||||
rs.Attributes, "config_vars").([]interface{})
|
||||
|
||||
err := update_config_vars(rs.ID, vs, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
} else if ok && attr == "0" {
|
||||
log.Println("[INFO] Config vars removed, removing all vars")
|
||||
|
||||
err := update_config_vars(rs.ID, make([]interface{}, 0), client)
|
||||
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
}
|
||||
|
||||
app, err := resource_heroku_app_retrieve(rs.ID, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
|
||||
return resource_heroku_app_update_state(rs, app)
|
||||
}
|
||||
|
||||
func resource_heroku_app_destroy(
|
||||
|
@ -99,9 +188,10 @@ func resource_heroku_app_diff(
|
|||
|
||||
b := &diff.ResourceBuilder{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"name": diff.AttrTypeCreate,
|
||||
"region": diff.AttrTypeUpdate,
|
||||
"stack": diff.AttrTypeCreate,
|
||||
"name": diff.AttrTypeUpdate,
|
||||
"region": diff.AttrTypeUpdate,
|
||||
"stack": diff.AttrTypeCreate,
|
||||
"config_vars": diff.AttrTypeUpdate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
|
@ -111,6 +201,7 @@ func resource_heroku_app_diff(
|
|||
"git_url",
|
||||
"web_url",
|
||||
"id",
|
||||
"config_vars",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -119,26 +210,41 @@ func resource_heroku_app_diff(
|
|||
|
||||
func resource_heroku_app_update_state(
|
||||
s *terraform.ResourceState,
|
||||
app *heroku.App) (*terraform.ResourceState, error) {
|
||||
app *application) (*terraform.ResourceState, error) {
|
||||
|
||||
s.Attributes["name"] = app.Name
|
||||
s.Attributes["stack"] = app.Stack.Name
|
||||
s.Attributes["region"] = app.Region.Name
|
||||
s.Attributes["git_url"] = app.GitURL
|
||||
s.Attributes["web_url"] = app.WebURL
|
||||
s.Attributes["id"] = app.Id
|
||||
s.Attributes["name"] = app.App.Name
|
||||
s.Attributes["stack"] = app.App.Stack.Name
|
||||
s.Attributes["region"] = app.App.Region.Name
|
||||
s.Attributes["git_url"] = app.App.GitURL
|
||||
s.Attributes["web_url"] = app.App.WebURL
|
||||
|
||||
// We know that the hostname on heroku will be the name+herokuapp.com
|
||||
// You need this to do things like create DNS CNAME records
|
||||
s.Attributes["heroku_hostname"] = fmt.Sprintf("%s.herokuapp.com", app.App.Name)
|
||||
|
||||
toFlatten := make(map[string]interface{})
|
||||
|
||||
if len(app.Vars) > 0 {
|
||||
toFlatten["config_vars"] = []map[string]string{app.Vars}
|
||||
}
|
||||
|
||||
for k, v := range flatmap.Flatten(toFlatten) {
|
||||
s.Attributes[k] = v
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_retrieve(id string, client *heroku.Client) (*heroku.App, error) {
|
||||
app, err := client.AppInfo(id)
|
||||
func resource_heroku_app_retrieve(id string, client *heroku.Client) (*application, error) {
|
||||
app := application{Id: id, Client: client}
|
||||
|
||||
err := app.Update()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving app: %s", err)
|
||||
}
|
||||
|
||||
return app, nil
|
||||
return &app, nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_validation() *config.Validator {
|
||||
|
@ -148,6 +254,38 @@ func resource_heroku_app_validation() *config.Validator {
|
|||
"name",
|
||||
"region",
|
||||
"stack",
|
||||
"config_vars.*",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func retrieve_config_vars(id string, client *heroku.Client) (map[string]string, error) {
|
||||
vars, err := client.ConfigVarInfo(id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
// Updates the config vars for from an expanded (prior to assertion)
|
||||
// []map[string]string config
|
||||
func update_config_vars(id string, vs []interface{}, client *heroku.Client) error {
|
||||
vars := make(map[string]*string)
|
||||
|
||||
for k, v := range vs[0].(map[string]interface{}) {
|
||||
val := v.(string)
|
||||
vars[k] = &val
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Updating config vars: *%#v", vars)
|
||||
|
||||
_, err := client.ConfigVarUpdate(id, vars)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating config vars: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -24,6 +24,78 @@ func TestAccHerokuApp_Basic(t *testing.T) {
|
|||
testAccCheckHerokuAppAttributes(&app),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "name", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.FOO", "bar"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccHerokuApp_NameChange(t *testing.T) {
|
||||
var app heroku.App
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuAppDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuAppConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
|
||||
testAccCheckHerokuAppAttributes(&app),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "name", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.FOO", "bar"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuAppConfig_updated,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
|
||||
testAccCheckHerokuAppAttributesUpdated(&app),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "name", "terraform-test-renamed"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.FOO", "bing"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.BAZ", "bar"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccHerokuApp_NukeVars(t *testing.T) {
|
||||
var app heroku.App
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuAppDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuAppConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
|
||||
testAccCheckHerokuAppAttributes(&app),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "name", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.FOO", "bar"),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuAppConfig_no_vars,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuAppExists("heroku_app.foobar", &app),
|
||||
testAccCheckHerokuAppAttributesNoVars(&app),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "name", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_app.foobar", "config_vars.0.FOO", ""),
|
||||
),
|
||||
},
|
||||
},
|
||||
|
@ -50,8 +122,76 @@ func testAccCheckHerokuAppDestroy(s *terraform.State) error {
|
|||
|
||||
func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
// check attrs
|
||||
if app.Region.Name != "us" {
|
||||
return fmt.Errorf("Bad region: %s", app.Region.Name)
|
||||
}
|
||||
|
||||
if app.Stack.Name != "cedar" {
|
||||
return fmt.Errorf("Bad stack: %s", app.Stack.Name)
|
||||
}
|
||||
|
||||
if app.Name != "terraform-test-app" {
|
||||
return fmt.Errorf("Bad name: %s", app.Name)
|
||||
}
|
||||
|
||||
vars, err := client.ConfigVarInfo(app.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if vars["FOO"] != "bar" {
|
||||
return fmt.Errorf("Bad config vars: %v", vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
if app.Name != "terraform-test-renamed" {
|
||||
return fmt.Errorf("Bad name: %s", app.Name)
|
||||
}
|
||||
|
||||
vars, err := client.ConfigVarInfo(app.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure we kept the old one
|
||||
if vars["FOO"] != "bing" {
|
||||
return fmt.Errorf("Bad config vars: %v", vars)
|
||||
}
|
||||
|
||||
if vars["BAZ"] != "bar" {
|
||||
return fmt.Errorf("Bad config vars: %v", vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuAppAttributesNoVars(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
if app.Name != "terraform-test-app" {
|
||||
return fmt.Errorf("Bad name: %s", app.Name)
|
||||
}
|
||||
|
||||
vars, err := client.ConfigVarInfo(app.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(vars) != 0 {
|
||||
return fmt.Errorf("vars exist: %v", vars)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -60,7 +200,7 @@ func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc {
|
|||
func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.Resources[n]
|
||||
fmt.Printf("resources %#v", s.Resources)
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("Not found: %s", n)
|
||||
}
|
||||
|
@ -81,7 +221,7 @@ func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFu
|
|||
return fmt.Errorf("App not found")
|
||||
}
|
||||
|
||||
app = foundApp
|
||||
*app = *foundApp
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -89,5 +229,24 @@ func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFu
|
|||
|
||||
const testAccCheckHerokuAppConfig_basic = `
|
||||
resource "heroku_app" "foobar" {
|
||||
name = "terraform-test-app"
|
||||
name = "terraform-test-app"
|
||||
|
||||
config_vars {
|
||||
FOO = "bar"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccCheckHerokuAppConfig_updated = `
|
||||
resource "heroku_app" "foobar" {
|
||||
name = "terraform-test-renamed"
|
||||
|
||||
config_vars {
|
||||
FOO = "bing"
|
||||
BAZ = "bar"
|
||||
}
|
||||
}`
|
||||
|
||||
const testAccCheckHerokuAppConfig_no_vars = `
|
||||
resource "heroku_app" "foobar" {
|
||||
name = "terraform-test-app"
|
||||
}`
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func resource_heroku_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)
|
||||
|
||||
app := rs.Attributes["app"]
|
||||
hostname := rs.Attributes["hostname"]
|
||||
|
||||
log.Printf("[DEBUG] Domain create configuration: %#v, %#v", app, hostname)
|
||||
|
||||
do, err := client.DomainCreate(app, hostname)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
rs.ID = do.Id
|
||||
rs.Attributes["hostname"] = do.Hostname
|
||||
rs.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", app)
|
||||
|
||||
log.Printf("[INFO] Domain ID: %s", rs.ID)
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_update(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
|
||||
panic("Cannot update domain")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_destroy(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) error {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
log.Printf("[INFO] Deleting Domain: %s", s.ID)
|
||||
|
||||
// Destroy the app
|
||||
err := client.DomainDelete(s.Attributes["app"], s.ID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting domain: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
domain, err := resource_heroku_domain_retrieve(s.Attributes["app"], s.ID, client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.Attributes["hostname"] = domain.Hostname
|
||||
s.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", s.Attributes["app"])
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||
|
||||
b := &diff.ResourceBuilder{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"hostname": diff.AttrTypeCreate,
|
||||
"app": diff.AttrTypeCreate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"cname",
|
||||
},
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
}
|
||||
|
||||
func resource_heroku_domain_retrieve(app string, id string, client *heroku.Client) (*heroku.Domain, error) {
|
||||
domain, err := client.DomainInfo(app, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving domain: %s", err)
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{
|
||||
"hostname",
|
||||
"app",
|
||||
},
|
||||
Optional: []string{},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccHerokuDomain_Basic(t *testing.T) {
|
||||
var domain heroku.Domain
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckHerokuDomainDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
resource.TestStep{
|
||||
Config: testAccCheckHerokuDomainConfig_basic,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckHerokuDomainExists("heroku_domain.foobar", &domain),
|
||||
testAccCheckHerokuDomainAttributes(&domain),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_domain.foobar", "hostname", "terraform.example.com"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_domain.foobar", "app", "terraform-test-app"),
|
||||
resource.TestCheckResourceAttr(
|
||||
"heroku_domain.foobar", "cname", "terraform-test-app.herokuapp.com"),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckHerokuDomainDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_domain" {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err := client.DomainInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
if err == nil {
|
||||
return fmt.Errorf("Domain still exists")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckHerokuDomainAttributes(Domain *heroku.Domain) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
|
||||
if Domain.Hostname != "terraform.example.com" {
|
||||
return fmt.Errorf("Bad hostname: %s", Domain.Hostname)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func testAccCheckHerokuDomainExists(n string, Domain *heroku.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 Domain ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
|
||||
foundDomain, err := client.DomainInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if foundDomain.Id != rs.ID {
|
||||
return fmt.Errorf("Domain not found")
|
||||
}
|
||||
|
||||
*Domain = *foundDomain
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
const testAccCheckHerokuDomainConfig_basic = `
|
||||
resource "heroku_app" "foobar" {
|
||||
name = "terraform-test-app"
|
||||
}
|
||||
|
||||
resource "heroku_domain" "foobar" {
|
||||
app = "${heroku_app.foobar.name}"
|
||||
hostname = "terraform.example.com"
|
||||
}`
|
|
@ -11,6 +11,15 @@ var resourceMap *resource.Map
|
|||
func init() {
|
||||
resourceMap = &resource.Map{
|
||||
Mapping: map[string]resource.Resource{
|
||||
"heroku_addon": resource.Resource{
|
||||
ConfigValidator: resource_heroku_addon_validation(),
|
||||
Create: resource_heroku_addon_create,
|
||||
Destroy: resource_heroku_addon_destroy,
|
||||
Diff: resource_heroku_addon_diff,
|
||||
Refresh: resource_heroku_addon_refresh,
|
||||
Update: resource_heroku_addon_update,
|
||||
},
|
||||
|
||||
"heroku_app": resource.Resource{
|
||||
ConfigValidator: resource_heroku_app_validation(),
|
||||
Create: resource_heroku_app_create,
|
||||
|
@ -19,6 +28,14 @@ func init() {
|
|||
Refresh: resource_heroku_app_refresh,
|
||||
Update: resource_heroku_app_update,
|
||||
},
|
||||
|
||||
"heroku_domain": resource.Resource{
|
||||
ConfigValidator: resource_heroku_domain_validation(),
|
||||
Create: resource_heroku_domain_create,
|
||||
Destroy: resource_heroku_domain_destroy,
|
||||
Diff: resource_heroku_domain_diff,
|
||||
Refresh: resource_heroku_domain_refresh,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ func (v *FlagVarFile) Set(raw string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const libuclParseFlags = libucl.ParserKeyLowercase
|
||||
const libuclParseFlags = libucl.ParserNoTime
|
||||
|
||||
func loadVarFile(path string) (map[string]string, error) {
|
||||
var obj *libucl.Object
|
||||
|
|
|
@ -29,13 +29,14 @@ var ContextOpts terraform.ContextOpts
|
|||
|
||||
// Put the parse flags we use for libucl in a constant so we can get
|
||||
// equally behaving parsing everywhere.
|
||||
const libuclParseFlags = libucl.ParserKeyLowercase
|
||||
const libuclParseFlags = libucl.ParserNoTime
|
||||
|
||||
func init() {
|
||||
BuiltinConfig.Providers = map[string]string{
|
||||
"aws": "terraform-provider-aws",
|
||||
"digitalocean": "terraform-provider-digitalocean",
|
||||
"heroku": "terraform-provider-heroku",
|
||||
"dnsimple": "terraform-provider-dnsimple",
|
||||
}
|
||||
BuiltinConfig.Provisioners = map[string]string{
|
||||
"local-exec": "terraform-provisioner-local-exec",
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
// Put the parse flags we use for libucl in a constant so we can get
|
||||
// equally behaving parsing everywhere.
|
||||
const libuclParseFlags = libucl.ParserKeyLowercase
|
||||
const libuclParseFlags = libucl.ParserNoTime
|
||||
|
||||
// libuclConfigurable is an implementation of configurable that knows
|
||||
// how to turn libucl configuration into a *Config object.
|
||||
|
|
|
@ -485,6 +485,7 @@ do
|
|||
|
||||
const basicResourcesStr = `
|
||||
aws_instance[db] (x1)
|
||||
VPC
|
||||
security_groups
|
||||
dependsOn
|
||||
aws_instance.web
|
||||
|
|
|
@ -31,6 +31,7 @@ resource aws_instance "web" {
|
|||
|
||||
resource "aws_instance" "db" {
|
||||
security_groups = "${aws_security_group.firewall.*.id}"
|
||||
VPC = "foo"
|
||||
|
||||
depends_on = ["aws_instance.web"]
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
"aws_instance": {
|
||||
"db": {
|
||||
"security_groups": ["${aws_security_group.firewall.*.id}"],
|
||||
"VPC": "foo",
|
||||
"depends_on": ["aws_instance.web"]
|
||||
},
|
||||
|
||||
|
|
|
@ -1,93 +1,80 @@
|
|||
---
|
||||
layout: "inner"
|
||||
page_title: "Community"
|
||||
---
|
||||
|
||||
<div class="container">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="bs-docs-section">
|
||||
<h1>Community</h1>
|
||||
<h1>Community</h1>
|
||||
|
||||
<p>
|
||||
Terraform is a new project with a growing community. Despite this,
|
||||
there are active, dedicated users willing to help you through various
|
||||
mediums.
|
||||
</p>
|
||||
<p>
|
||||
<strong>IRC:</strong> <code>#terraform</code> on Freenode
|
||||
</p>
|
||||
<p>
|
||||
<strong>Mailing list:</strong>
|
||||
<a href="https://groups.google.com/group/terraform-tool">Terraform Google Group</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Bug Tracker:</strong>
|
||||
<a href="https://github.com/hashicorp/terraform/issues">Issue tracker
|
||||
on GitHub</a>. Please only use this for reporting bugs. Do not ask
|
||||
for general help here. Use IRC or the mailing list for that.
|
||||
|
||||
<h1>People</h1>
|
||||
<p>
|
||||
The following people are some of the faces behind Terraform. They each
|
||||
contribute to Terraform in some core way. Over time, faces may appear and
|
||||
disappear from this list as contributors come and go.
|
||||
</p>
|
||||
<div class="people">
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/54079122b67de9677c1f93933ce8b63a.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Mitchell Hashimoto (<a href="https://github.com/mitchellh">@mitchellh</a>)</h3>
|
||||
<p>
|
||||
Terraform is a new project with a growing community. Despite this,
|
||||
there are active, dedicated users willing to help you through various
|
||||
mediums.
|
||||
Mitchell Hashimoto is the creator of Terraform and works on all
|
||||
layers of Terraform from the core to providers. In addition to Terraform,
|
||||
Mitchell is the creator of
|
||||
<a href="http://www.vagrantup.com">Vagrant</a>,
|
||||
<a href="http://www.packer.io">Packer</a>, and
|
||||
<a href="http://www.consul.io">Consul</a>.
|
||||
</p>
|
||||
<p>
|
||||
<strong>IRC:</strong> <code>#terraform</code> on Freenode
|
||||
</p>
|
||||
<p>
|
||||
<strong>Mailing list:</strong>
|
||||
<a href="https://groups.google.com/group/terraform-tool">Terraform Google Group</a>
|
||||
</p>
|
||||
<p>
|
||||
<strong>Bug Tracker:</strong>
|
||||
<a href="https://github.com/hashicorp/terraform/issues">Issue tracker
|
||||
on GitHub</a>. Please only use this for reporting bugs. Do not ask
|
||||
for general help here. Use IRC or the mailing list for that.
|
||||
|
||||
<h1>People</h1>
|
||||
<p>
|
||||
The following people are some of the faces behind Terraform. They each
|
||||
contribute to Terraform in some core way. Over time, faces may appear and
|
||||
disappear from this list as contributors come and go.
|
||||
</p>
|
||||
<div class="people">
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/11ba9630c9136eef9a70d26473d355d5.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Armon Dadgar (<a href="https://github.com/armon">@armon</a>)</h3>
|
||||
<p>
|
||||
Armon Dadgar is the creator of Terraform. He researched and developed
|
||||
most of the internals of how Terraform works, including the
|
||||
gossip layer, leader election, etc. Armon is also the creator of
|
||||
<a href="https://github.com/hashicorp/serf">Serf</a>,
|
||||
<a href="https://github.com/armon/statsite">Statsite</a>, and
|
||||
<a href="https://github.com/armon/bloomd">Bloomd</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/54079122b67de9677c1f93933ce8b63a.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Mitchell Hashimoto (<a href="https://github.com/mitchellh">@mitchellh</a>)</h3>
|
||||
<p>
|
||||
Mitchell Hashimoto is a co-creator of Terraform. He primarily took
|
||||
a management role in the creation of Terraform, guiding product
|
||||
and user experience decisions on top of Armon's technical decisions.
|
||||
Mitchell Hashimoto is also the creator of
|
||||
<a href="http://www.vagrantup.com">Vagrant</a>,
|
||||
<a href="http://www.packer.io">Packer</a>, and
|
||||
<a href="http://www.serfdom.io">Serf</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/2acc31dd6370a54b18f6755cd0710ce6.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Jack Pearkes (<a href="https://github.com/pearkes">@pearkes</a>)</h3>
|
||||
<p>
|
||||
Jack Pearkes created and maintains the Terraform web UI.
|
||||
He is also a core committer to
|
||||
<a href="http://www.packer.io">Packer</a> and maintains
|
||||
many successful
|
||||
<a href="https://github.com/pearkes">open source projects</a>
|
||||
while also being an employee of
|
||||
<a href="http://www.hashicorp.com">HashiCorp</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person">
|
||||
<img class="pull-left" src="//www.gravatar.com/avatar/1e87e6016a7c4f4ecbd2517d84058467.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>William Tisäter (<a href="https://github.com/tiwilliam">@tiwilliam</a>)</h3>
|
||||
<p>William Tisäter is a Terraform core committer. He is also maintainer of <a href="https://pypi.python.org/pypi/pygeoip">pygeoip</a> and build things daily at <a href="https://tictail.com">Tictail</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/11ba9630c9136eef9a70d26473d355d5.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Armon Dadgar (<a href="https://github.com/armon">@armon</a>)</h3>
|
||||
<p>
|
||||
Armon Dadgar is a creator of Terraform. He created valuable sections
|
||||
of the core and helps maintain providers as well. Armon is also the
|
||||
creator of
|
||||
<a href="http://www.consul.io">Consul</a>,
|
||||
<a href="http://www.serfdom.io">Serf</a>,
|
||||
<a href="https://github.com/armon/statsite">Statsite</a>, and
|
||||
<a href="https://github.com/armon/bloomd">Bloomd</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="person">
|
||||
<img class="pull-left" src="http://www.gravatar.com/avatar/2acc31dd6370a54b18f6755cd0710ce6.png?s=125">
|
||||
<div class="bio">
|
||||
<h3>Jack Pearkes (<a href="https://github.com/pearkes">@pearkes</a>)</h3>
|
||||
<p>
|
||||
Jack Pearkes is a creator of Terraform. He created and maintains
|
||||
most of the providers and documentation.
|
||||
He is also a core committer to
|
||||
<a href="http://www.packer.io">Packer</a> and
|
||||
<a href="http://www.consul.io">Consul</a>
|
||||
while also being an employee of
|
||||
<a href="http://www.hashicorp.com">HashiCorp</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "Provider: AWS"
|
||||
sidebar_current: "docs-aws-index"
|
||||
---
|
||||
|
||||
# AWS Provider
|
||||
|
||||
The Amazon Web Services (AWS) provider is used to interact with the
|
||||
many resources supported by AWS. 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 AWS Provider
|
||||
provider "aws" {
|
||||
access_key = "${var.aws_access_key}"
|
||||
secret_key = "${var.aws_secret_key}"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
# Create a web server
|
||||
resource "aws_instance" "web" {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `access_key` - (Required) This is the AWS access key. It must be provided, but
|
||||
it can also be sourced from the `AWS_ACCESS_KEY` environment variable.
|
||||
|
||||
* `secret_key` - (Required) This is the AWS secret key. It must be provided, but
|
||||
it can also be sourced from the `AWS_SECRET_KEY` environment variable.
|
||||
|
||||
* `region` - (Required) This is the AWS region. It must be provided, but
|
||||
it can also be sourced from the `AWS_REGION` environment variables.
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_eip"
|
||||
sidebar_current: "docs-aws-resource-eip"
|
||||
---
|
||||
|
||||
# aws\_eip
|
||||
|
||||
Provides an Elastic IP resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_eip" "lb" {
|
||||
instance = "${aws_instance.web.instance_id}"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `vpc` - (Optional) VPC ID
|
||||
* `instance` - (Optional) EC2 instance ID.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `private_ip` - Contrains the private IP address (if in VPC).
|
||||
* `public_ip` - Contains the public IP address.
|
||||
* `instance` - Contains the ID of the instance attached ot.
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_instance"
|
||||
sidebar_current: "docs-aws-resource-instance"
|
||||
---
|
||||
|
||||
# aws\_instance
|
||||
|
||||
Provides an EC2 instance resource. This allows instances to be created, updated,
|
||||
and deleted. Instances also support [provisioning](/docs/provisioners/index.html).
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
# Create a new instance of the ami-1234 on an m1.small node
|
||||
resource "aws_instance" "web" {
|
||||
ami = "ami-1234"
|
||||
instance_type = "m1.small"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `ami` - (Required) The AMI to use for the instance.
|
||||
* `availability_zone` - (Optional) The AZ to start the instance in.
|
||||
* `instance_type` - (Required) The type of instance to start
|
||||
* `key_name` - (Optional) The key name to use for the instance.
|
||||
* `security_groups` - (Optional) A list of security group IDs to associate with.
|
||||
* `subnet_id` - (Optional) The VPC Subnet ID to launch in.
|
||||
* `source_dest_check` - (Optional) Controls if traffic is routed to the instance when
|
||||
the destination address does not match the instance. Used for NAT or VPNs. Defaults false.
|
||||
* `user_data` - (Optional) The user data to provide when launching the instance.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The instance ID.
|
||||
* `availability_zone` - The availability zone of the instance.
|
||||
* `key_name` - The key name of the instance
|
||||
* `private_dns` - The Private DNS name of the instance
|
||||
* `private_ip` - The private IP address.
|
||||
* `public_dns` - The public DNS name of the instance
|
||||
* `public_ip` - The public IP address.
|
||||
* `security_groups` - The associated security groups.
|
||||
* `subnet_id` - The VPC subnet ID.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_internet_gateway"
|
||||
sidebar_current: "docs-aws-resource-internet-gateway"
|
||||
---
|
||||
|
||||
# aws\_internet\_gateway
|
||||
|
||||
Provides a resource to create a VPC Internet Gateway.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_internet_gateway" "gw" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `vpc_id` - (Required) The VPC ID to create in.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the Internet Gateway.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_launch_configuration"
|
||||
sidebar_current: "docs-aws-resource-launch-config"
|
||||
---
|
||||
|
||||
# aws\_launch\_configuration
|
||||
|
||||
Provides a resource to create a new launch configuration, used for autoscaling groups.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_launch_configuration" "as_conf" {
|
||||
name = "web_config"
|
||||
image_id = "ami-1234"
|
||||
instance_type = "m1.small"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The name of the launch configuration.
|
||||
* `image_id` - (Required) The EC2 image ID to launch.
|
||||
* `instance_type` - (Required) The size of instance to launch.
|
||||
* `key_name` - (Optional) The key name that should be used for the instance.
|
||||
* `security_groups` - (Optional) A list of associated security group IDS.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the launch configuration.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_route53_record"
|
||||
sidebar_current: "docs-aws-resource-route53-record"
|
||||
---
|
||||
|
||||
# aws\_route53\_record
|
||||
|
||||
Provides a Route53 record resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_route53_record" "www" {
|
||||
zone_id = "${aws_route53_zone.primary.zone_id}"
|
||||
name = "www.example.com"
|
||||
type = "A"
|
||||
ttl = "300"
|
||||
records = ["${aws_eip.lb.public_ip}"]
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `zone_id` - (Required) The ID of the hosted zone to contain this record.
|
||||
* `name` - (Required) The name of the record.
|
||||
* `type` - (Required) The record type.
|
||||
* `ttl` - (Required) The TTL of the record.
|
||||
* `records` - (Required) A string list of records.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
No attributes are exported.
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_route53_zone"
|
||||
sidebar_current: "docs-aws-resource-route53-zone"
|
||||
---
|
||||
|
||||
# aws\_route53\_zone
|
||||
|
||||
Provides a Route53 Hosted Zone resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_route53_zone" "primary" {
|
||||
name = "example.com"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) This is the name of the hosted zone.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `zone_id` - The Hosted Zone ID. This can be referenced by zone records.
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_route_table"
|
||||
sidebar_current: "docs-aws-resource-route-table|"
|
||||
---
|
||||
|
||||
# aws\_route\_table
|
||||
|
||||
Provides a resource to create a VPC routing table.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_route_table" "r" {
|
||||
vpc_id = "${aws_vpc.default.id}"
|
||||
route {
|
||||
cidr_block = "10.0.1.0/24"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `vpc_id` - (Required) The ID of the routing table.
|
||||
* `route` - (Optional) A list of route objects. Their keys are documented below.
|
||||
|
||||
Each route supports the following:
|
||||
|
||||
* `cidr_block` - (Required) The CIDR block of the route.
|
||||
* `gateway_id` - (Optional) The Internet Gateway ID.
|
||||
* `instance_id` - (Optional) The EC2 instance ID.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the routing table
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_route_table_association"
|
||||
sidebar_current: "docs-aws-resource-route-table-assoc"
|
||||
---
|
||||
|
||||
# aws\_route\_table\_association
|
||||
|
||||
Provides a resource to create an association between a subnet and routing table.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_route_table_association" "a" {
|
||||
subnet_id = "${aws_subnet.foo.id}"
|
||||
route_table_id = "${aws_route_table.bar.id}"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `subnet_id` - (Required) The subnet ID to create an association.
|
||||
* `route_table_id` - (Required) The ID of the routing table to associate with.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the association
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_s3_bucket"
|
||||
sidebar_current: "docs-aws-resource-s3-bucket"
|
||||
---
|
||||
|
||||
# aws\_s3\_bucket
|
||||
|
||||
Provides a S3 bucket resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_s3_bucket" "b" {
|
||||
bucket = "my_tf_test_bucket"
|
||||
acl = "private"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `bucket` - (Required) The name of the bucket.
|
||||
* `acl` - (Optional) The canned ACL to apply. Defaults to "private".
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The name of the bucket
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_security_group"
|
||||
sidebar_current: "docs-aws-resource-security-group"
|
||||
---
|
||||
|
||||
# aws\_security\_group
|
||||
|
||||
Provides an security group resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_security_group" "allow_all" {
|
||||
name = "allow_all"
|
||||
ingress {
|
||||
from_port = 0
|
||||
to_port = 65535
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `name` - (Required) The name of the security group
|
||||
* `ingress` - (Required) Can be specified multiple times for each
|
||||
ingress rule. Each ingress block supports fields documented below.
|
||||
* `description` - (Optional) The security group description.
|
||||
* `vpc_id` - (Optional) The VPC ID.
|
||||
* `owner_id` - (Optional) The AWS Owner ID.
|
||||
|
||||
The `ingress` block supports:
|
||||
|
||||
* `cidr_blocks` - (Optional) List of CIDR blocks. Cannot be used with `security_groups`.
|
||||
* `from_port` - (Required) The start port.
|
||||
* `protocol` - (Required) The protocol.
|
||||
* `security_groups` - (Optional) List of security group IDs. Cannot be used with `cidr_blocks`.
|
||||
* `to_port` - (Required) The end range port.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the security group
|
||||
* `vpc_id` - The VPC ID.
|
||||
* `owner_id` - The owner ID.
|
||||
* `name` - The name of the security group
|
||||
* `description` - The description of the security group
|
||||
* `ingress` - The ingress rules. See above for more.
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_subnet"
|
||||
sidebar_current: "docs-aws-resource-subnet"
|
||||
---
|
||||
|
||||
# aws\_subnet
|
||||
|
||||
Provides an VPC subnet resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_subnet" "main" {
|
||||
vpc_id = "${aws_vpc.main.id}"
|
||||
cidr_block = "10.0.1.0/16"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `availability_zone`- (Optional) The AZ for the subnet.
|
||||
* `cidr_block` - (Required) The CIDR block for the subnet.
|
||||
* `vpc_id` - (Required) The VPC ID.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the subnet
|
||||
* `availability_zone`- The AZ for the subnet.
|
||||
* `cidr_block` - The CIDR block for the subnet.
|
||||
* `vpc_id` - The VPC ID.
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
layout: "aws"
|
||||
page_title: "AWS: aws_vpc"
|
||||
sidebar_current: "docs-aws-resource-vpc"
|
||||
---
|
||||
|
||||
# aws\_vpc
|
||||
|
||||
Provides an VPC resource.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
resource "aws_vpc" "main" {
|
||||
cidr_block = "10.0.0.0/16"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `cidr_block` - (Required) The CIDR block for the VPC.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
The following attributes are exported:
|
||||
|
||||
* `id` - The ID of the VPC
|
||||
* `cidr_block` - The CIDR block of the VPC
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Providers"
|
||||
sidebar_current: "docs-providers"
|
||||
---
|
||||
|
||||
# Providers
|
||||
|
||||
Terraform is used to create, manage, and manipulate infrastructure resources.
|
||||
Examples of resources include physical machines, VMs, network switches, containers,
|
||||
etc. Almost any infrastructure noun can be represented as a resource in Terraform.
|
||||
|
||||
Terraform is agnostic to the underlying platforms by supporting providers. A provider
|
||||
is responsible for understanding API interactions and exposing resources. Providers
|
||||
generally are an IaaS (e.g. AWS, DigitalOcean, GCE), PaaS (e.g. Heroku, CloudFoundry),
|
||||
or SaaS services (e.g. DNSimple, CloudFlare).
|
||||
|
||||
Use the navigation to the left to read about the available providers.
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Provisioner Connections"
|
||||
sidebar_current: "docs-provisioners-connection"
|
||||
---
|
||||
|
||||
# Provisioner Connections
|
||||
|
||||
Many provisioners require access to the remote resource. For example,
|
||||
a provisioner may need to use ssh to connect to the resource.
|
||||
|
||||
Terraform uses a number of defaults when connecting to a resource, but these
|
||||
can be overriden using `connection` block in either a `resource` or `provisioner`.
|
||||
Any `connection` information provided in a `resource` will apply to all the
|
||||
provisioners, but it can be scoped to a single provisioner as well. One use case
|
||||
is to have an initial provisioner connect as root to setup user accounts, and have
|
||||
subsequent provisioners connect as a user with more limited permissions.
|
||||
|
||||
## Example usage
|
||||
|
||||
```
|
||||
# Copies the file as the root user using a password
|
||||
provisioner "file" {
|
||||
source = "conf/myapp.conf"
|
||||
destination = "/etc/myapp.conf"
|
||||
connection {
|
||||
user = "root"
|
||||
password = "${var.root_password}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arugments are supported:
|
||||
|
||||
* `type` - The connection type that should be used. This defaults to "ssh". The type
|
||||
of connection supported depends on the provisioner.
|
||||
|
||||
* `user` - The user that we should use for the connection. This defaults to "root".
|
||||
|
||||
* `password` - The password we should use for the connection.
|
||||
|
||||
* `key_file` - The SSH key to use for the connection. This takes preference over the
|
||||
password if provided.
|
||||
|
||||
* `host` - The address of the resource to connect to. This is provided by the provider.
|
||||
|
||||
* `port` - The port to connect to. This defaults to 22.
|
||||
|
||||
* `timeout` - The timeout to wait for the conneciton to become available. This defaults
|
||||
to 5 minutes. Should be provided as a string like "30s" or "5m".
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Provisioner: file"
|
||||
sidebar_current: "docs-provisioners-file"
|
||||
---
|
||||
|
||||
# File Provisioner
|
||||
|
||||
The `file` provisioner is used to copy files or directories from the machine
|
||||
executing Terraform to the newly created resource. The `file` provisioner only
|
||||
supports `ssh` type [connections](/docs/provisioners/connection.html).
|
||||
|
||||
## Example usage
|
||||
|
||||
```
|
||||
resource "aws_instance" "web" {
|
||||
...
|
||||
|
||||
# Copies the myapp.conf file to /etc/myapp.conf
|
||||
provisioner "file" {
|
||||
source = "conf/myapp.conf"
|
||||
destination = "/etc/myapp.conf"
|
||||
}
|
||||
|
||||
# Copies the configs.d folder to /etc/configs.d
|
||||
provisioner "file" {
|
||||
source = "conf/configs.d"
|
||||
destination = "/etc"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arugments are supported:
|
||||
|
||||
* `source` - (Required) This is the source file or folder. It can be specified as relative
|
||||
to the current working directory or as an absolute path.
|
||||
|
||||
* `destination` - (Required) This is the destination path. It must be specified as an
|
||||
absolute path.
|
||||
|
||||
## Directory Uploads
|
||||
|
||||
The file provisioner is also able to upload a complete directory to the remote machine.
|
||||
When uploading a directory, there are a few important things you should know.
|
||||
|
||||
First, the destination directory must already exist. If you need to create it,
|
||||
use a remote-exec provisioner just prior to the file provisioner in order to create the directory.
|
||||
|
||||
Next, the existence of a trailing slash on the source path will determine whether the
|
||||
directory name will be embedded within the destination, or whether the destination will
|
||||
be created. An example explains this best:
|
||||
|
||||
If the source is `/foo` (no trailing slash), and the destination is `/tmp`, then the contents
|
||||
of `/foo` on the local machine will be uploaded to `/tmp/foo` on the remote machine. The
|
||||
`foo` directory on the remote machine will be created by Terraform.
|
||||
|
||||
If the source, however, is `/foo/` (a trailing slash is present), and the destination is
|
||||
`/tmp`, then the contents of `/foo` will be uploaded directly into `/tmp` directly.
|
||||
|
||||
This behavior was adopted from the standard behavior of rsync. Note that under the covers,
|
||||
rsync may or may not be used.
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Provisioners"
|
||||
sidebar_current: "docs-provisioners"
|
||||
---
|
||||
|
||||
# Provisioners
|
||||
|
||||
When a resource is initially created, provisioners can be executed to
|
||||
initialize that resource. This can be used to add resources to an inventory
|
||||
management system, run a configuration management tool, bootstrap the
|
||||
resource into a cluster, etc.
|
||||
|
||||
Use the navigation to the left to read about the available provisioners.
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Provisioner: local-exec"
|
||||
sidebar_current: "docs-provisioners-local"
|
||||
---
|
||||
|
||||
# local-exec Provisioner
|
||||
|
||||
The `local-exec` provisioner invokes a local executable after a resource
|
||||
is created. This invokes a process on the machine running Terraform, not on
|
||||
the resource. See the `remote-exec` [provisioner](/docs/provisioners/remote-exec.html)
|
||||
to run commands on the resource.
|
||||
|
||||
## Example usage
|
||||
|
||||
```
|
||||
# Join the newly created machine to our Consul cluster
|
||||
resource "aws_instance" "web" {
|
||||
...
|
||||
provisioner "local-exec" {
|
||||
command = "consul join ${aws_instance.web.private_ip}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arugments are supported:
|
||||
|
||||
* `command` - (Required) This is the command to execute. It can be provided
|
||||
as a relative path to the current working directory or as an absolute path.
|
||||
It is evaluated in a shell, and can use environment variables or Terraform
|
||||
variables.
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
layout: "docs"
|
||||
page_title: "Provisioner: remote-exec"
|
||||
sidebar_current: "docs-provisioners-remote"
|
||||
---
|
||||
|
||||
# remote-exec Provisioner
|
||||
|
||||
The `remote-exec` provisioner invokes a script on a remote resource after it
|
||||
is created. This can be used to run a configuration management tool, bootstrap
|
||||
into a cluster, etc. To invoke a local process, see the `local-exec`
|
||||
[provisioner](/docs/provisioners/local-exec.html) instead. The `remote-exec`
|
||||
provisioner only supports `ssh` type [connections](/docs/provisioners/connection.html).
|
||||
|
||||
|
||||
## Example usage
|
||||
|
||||
```
|
||||
# Run puppet and join our Consul cluster
|
||||
resource "aws_instance" "web" {
|
||||
...
|
||||
provisioner "remote-exec" {
|
||||
inline = [
|
||||
"puppet apply",
|
||||
"consul join ${aws_instance.web.private_ip",
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
|
||||
* `inline` - This is a list of command strings. They are executed in the order
|
||||
they are provided. This cannot be provided with `script` or `scripts`.
|
||||
|
||||
* `script` - This is a path (relative or absolute) to a local script that will
|
||||
be copied to the remote resource and then executed. This cannot be provided
|
||||
with `inline` or `scripts`.
|
||||
|
||||
* `scripts` - This is a list of paths (relative or absolute) to local scripts
|
||||
that will be copied to the remote resource and then executed. They are executed
|
||||
in the order they are provided. This cannot be provided with `inline` or `script`.
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Run the Agent"
|
||||
sidebar_current: "gettingstarted-agent"
|
||||
---
|
||||
|
||||
# Run the Terraform Agent
|
||||
|
||||
After Terraform is installed, the agent must be run. The agent can either run
|
||||
in a server or client mode. Each datacenter must have at least one server,
|
||||
although 3 or 5 is recommended. A single server deployment is _**highly**_ discouraged
|
||||
as data loss is inevitable in a failure scenario. [This guide](/docs/guides/bootstrapping.html)
|
||||
covers bootstrapping a new datacenter. All other agents run in client mode, which
|
||||
is a very lightweight process that registers services, runs health checks,
|
||||
and forwards queries to servers. The agent must be run for every node that
|
||||
will be part of the cluster.
|
||||
|
||||
## Starting the Agent
|
||||
|
||||
For simplicity, we'll run a single Terraform agent in server mode right now:
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul
|
||||
==> WARNING: Bootstrap mode enabled! Do not enable unless necessary
|
||||
==> WARNING: It is highly recommended to set GOMAXPROCS higher than 1
|
||||
==> Starting Terraform agent...
|
||||
==> Starting Terraform agent RPC...
|
||||
==> Terraform agent running!
|
||||
Node name: 'Armons-MacBook-Air'
|
||||
Datacenter: 'dc1'
|
||||
Server: true (bootstrap: true)
|
||||
Client Addr: 127.0.0.1 (HTTP: 8500, DNS: 8600, RPC: 8400)
|
||||
Cluster Addr: 10.1.10.38 (LAN: 8301, WAN: 8302)
|
||||
|
||||
==> Log data will now stream in as it occurs:
|
||||
|
||||
[INFO] serf: EventMemberJoin: Armons-MacBook-Air.local 10.1.10.38
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Follower] entering Follower state
|
||||
[INFO] terraform: adding server for datacenter: dc1, addr: 10.1.10.38:8300
|
||||
[ERR] agent: failed to sync remote state: rpc error: No cluster leader
|
||||
[WARN] raft: Heartbeat timeout reached, starting election
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Candidate] entering Candidate state
|
||||
[INFO] raft: Election won. Tally: 1
|
||||
[INFO] raft: Node at 10.1.10.38:8300 [Leader] entering Leader state
|
||||
[INFO] terraform: cluster leadership acquired
|
||||
[INFO] terraform: New leader elected: Armons-MacBook-Air
|
||||
[INFO] terraform: member 'Armons-MacBook-Air' joined, marking health alive
|
||||
```
|
||||
|
||||
As you can see, the Terraform agent has started and has output some log
|
||||
data. From the log data, you can see that our agent is running in server mode,
|
||||
and has claimed leadership of the cluster. Additionally, the local member has
|
||||
been marked as a healthy member of the cluster.
|
||||
|
||||
<div class="alert alert-block alert-warning">
|
||||
<strong>Note for OS X Users:</strong> Terraform uses your hostname as the
|
||||
default node name. If your hostname contains periods, DNS queries to
|
||||
that node will not work with Terraform. To avoid this, explicitly set
|
||||
the name of your node with the <code>-node</code> flag.
|
||||
</div>
|
||||
|
||||
## Cluster Members
|
||||
|
||||
If you run `terraform members` in another terminal, you can see the members of
|
||||
the Terraform cluster. You should only see one member (yourself). We'll cover
|
||||
joining clusters in the next section.
|
||||
|
||||
```
|
||||
$ terraform members
|
||||
Armons-MacBook-Air 10.1.10.38:8301 alive role=terraform,dc=dc1,vsn=1,vsn_min=1,vsn_max=1,port=8300,bootstrap=1
|
||||
```
|
||||
|
||||
The output shows our own node, the address it is running on, its
|
||||
health state, and some metadata associated with the node. Some important
|
||||
metadata keys to recognize are the `role` and `dc` keys. These tell you
|
||||
the service name and the datacenter that member is within. These can be
|
||||
used to lookup nodes and services using the DNS interface, which is covered
|
||||
shortly.
|
||||
|
||||
The output from the `members` command is generated based on the
|
||||
[gossip protocol](/docs/internals/gossip.html) and is eventually consistent.
|
||||
For a strongly consistent view of the world, use the
|
||||
[HTTP API](/docs/agent/http.html), which forwards the request to the
|
||||
Terraform servers:
|
||||
|
||||
```
|
||||
$ curl localhost:8500/v1/catalog/nodes
|
||||
[{"Node":"Armons-MacBook-Air","Address":"10.1.10.38"}]
|
||||
```
|
||||
|
||||
In addition to the HTTP API, the
|
||||
[DNS interface](/docs/agent/dns.html) can be used to query the node. Note
|
||||
that you have to make sure to point your DNS lookups to the Terraform agent's
|
||||
DNS server, which runs on port 8600 by default. The format of the DNS
|
||||
entries (such as "Armons-MacBook-Air.node.terraform") will be covered later.
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 Armons-MacBook-Air.node.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;Armons-MacBook-Air.node.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
Armons-MacBook-Air.node.terraform. 0 IN A 10.1.10.38
|
||||
```
|
||||
|
||||
## Stopping the Agent
|
||||
|
||||
You can use `Ctrl-C` (the interrupt signal) to gracefully halt the agent.
|
||||
After interrupting the agent, you should see it leave the cluster gracefully
|
||||
and shut down.
|
||||
|
||||
By gracefully leaving, Terraform notifies other cluster members that the
|
||||
node _left_. If you had forcibly killed the agent process, other members
|
||||
of the cluster would have detected that the node _failed_. When a member leaves,
|
||||
its services and checks are removed from the catalog. When a member fails,
|
||||
its health is simply marked as critical, but is not removed from the catalog.
|
||||
Terraform will automatically try to reconnect to _failed_ nodes, which allows it
|
||||
to recover from certain network conditions, while _left_ nodes are no longer contacted.
|
||||
|
||||
Additionally, if an agent is operating as a server, a graceful leave is important
|
||||
to avoid causing a potential availability outage affecting the [consensus protocol](/docs/internals/consensus.html).
|
||||
See the [guides section](/docs/guides/index.html) to safely add and remove servers.
|
||||
|
|
@ -0,0 +1,203 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Build Infrastructure"
|
||||
sidebar_current: "gettingstarted-build"
|
||||
---
|
||||
|
||||
# Build Infrastructure
|
||||
|
||||
With Terraform installed, let's dive right into it and start creating
|
||||
some infrastructure.
|
||||
|
||||
We'll build infrastructure on
|
||||
[AWS](http://aws.amazon.com) for the getting started guide
|
||||
since it is popular and generally understood, but Terraform
|
||||
can [manage many providers](#),
|
||||
including multiple providers in a single configuration.
|
||||
Some examples of this are in the
|
||||
[use cases section](/intro/use-cases.html).
|
||||
|
||||
If you don't have an AWS account,
|
||||
[create one now](http://aws.amazon.com/free/).
|
||||
For the getting started guide, we'll only be using resources
|
||||
which qualify under the AWS
|
||||
[free-tier](http://aws.amazon.com/free/),
|
||||
meaning it will be free.
|
||||
If you already have an AWS account, you may be charged some
|
||||
amount of money, but it shouldn't be more than a few dollars
|
||||
at most.
|
||||
|
||||
<div class="alert alert-block alert-warning">
|
||||
<p>
|
||||
<strong>Note:</strong> If you're not using an account that qualifies
|
||||
under the AWS
|
||||
<a href="http://aws.amazon.com/free/">free-tier</a>,
|
||||
you may be charged to run these examples. The most you should
|
||||
be charged should only be a few dollars, but we're not responsible
|
||||
for any charges that may incur.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
## Configuration
|
||||
|
||||
The set of files used to describe infrastructure in Terraform is simply
|
||||
known as a Terraform _configuration_. We're going to write our first
|
||||
configuration now to launch a single AWS EC2 instance.
|
||||
|
||||
The format of the configuration files is
|
||||
[documented here](#).
|
||||
Configuration files can
|
||||
[also be JSON](#), but we recommend only using JSON when the
|
||||
configuration is generated by a machine.
|
||||
|
||||
The entire configuration is shown below. We'll go over each part
|
||||
after. Save the contents to a file named `example.tf`. Verify that
|
||||
there are no other `*.tf` files in your directory, since Terraform
|
||||
loads all of them.
|
||||
|
||||
```
|
||||
provider "aws" {
|
||||
access_key = "ACCESS_KEY_HERE"
|
||||
secret_key = "SECRET_KEY_HERE"
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
resource "aws_instance" "example" {
|
||||
ami = "ami-408c7f28"
|
||||
instance_type = "t1.micro"
|
||||
}
|
||||
```
|
||||
|
||||
Replace the `ACCESS_KEY_HERE` and `SECRET_KEY_HERE` with your
|
||||
AWS access key and secret key, available from
|
||||
[this page](https://console.aws.amazon.com/iam/home?#security_credential).
|
||||
We're hardcoding them for now, but will extract these into
|
||||
variables later in the getting started guide.
|
||||
|
||||
This is a complete configuration that Terraform is ready to apply.
|
||||
The general structure should be intuitive and straightforward.
|
||||
|
||||
The `provider` block is used to configure the named provider, in
|
||||
our case "aws." A provider is responsible for creating and
|
||||
managing resources. Multiple provider blocks can exist if a
|
||||
Terraform configuration is comprised of multiple providers,
|
||||
which is a common situation.
|
||||
|
||||
The `resource` block defines a resource that exists within
|
||||
the infrastructure. A resource might be a physical component such
|
||||
as an EC2 instance, or it can be a logical resource such as
|
||||
a Heroku applcation.
|
||||
|
||||
The resource block has two strings before opening the block:
|
||||
the resource type and the resource name. In our example, the
|
||||
resource type is "aws\_instance" and the name is "example."
|
||||
The prefix of the type maps to the provider. In our case
|
||||
"aws\_instance" automatically tells Terraform that it is
|
||||
managed by the "aws" provider.
|
||||
|
||||
Within the resource block itself is configuration for that
|
||||
resource. This is dependent on each resource provider and
|
||||
is fully documented within our
|
||||
[providers reference](#). For our EC2 instance, we specify
|
||||
an AMI for Ubuntu, and request a "t1.micro" instance so we
|
||||
qualify under the free tier.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
Next, let's see what Terraform would do if we asked it to
|
||||
apply this configuration. In the same directory as the
|
||||
`example.tf` file you created, run `terraform plan`. You
|
||||
should see output similar to what is copied below. We've
|
||||
truncated some of the output to save space.
|
||||
|
||||
```
|
||||
$ terraform plan
|
||||
...
|
||||
|
||||
+ aws_instance.example
|
||||
ami: "" => "ami-408c7f28"
|
||||
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 plan` shows what changes Terraform will apply to
|
||||
your infrastructure given the current state of your infrastructure
|
||||
as well as the current contents of your configuration.
|
||||
|
||||
If `terraform plan` failed with an error, read the error message
|
||||
and fix the error that occurred. At this stage, it is probably a
|
||||
syntax error in the configuration.
|
||||
|
||||
The output format is similar to the diff format generated by tools
|
||||
such as Git. The output has a "+" next to "aws\_instance.example",
|
||||
meaning that Terraform will create this resource. Beneath that,
|
||||
it shows the attributes that will be set. When the value it is
|
||||
going to is `<computed>`, it means that the value won't be known
|
||||
until the resource is created.
|
||||
|
||||
## Apply
|
||||
|
||||
The plan looks good, our configuration appears valid, so its time to
|
||||
create real resources. Run `terraform apply` in the same directory
|
||||
as your `example.tf`, and watch it go! It will take a few minutes
|
||||
since Terraform waits for the EC2 instance to become available.
|
||||
|
||||
```
|
||||
$ terraform apply
|
||||
aws_instance.example: Creating...
|
||||
ami: "" => "ami-408c7f28"
|
||||
instance_type: "" => "t1.micro"
|
||||
|
||||
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Done! You can go to the AWS console to prove to yourself that the
|
||||
EC2 instance has been created.
|
||||
|
||||
Terraform also put some state into the `terraform.tfstate` file
|
||||
by default. This state file is extremely important; it maps various
|
||||
resource metadata to actual resource IDs so that Terraform knows
|
||||
what it is managing. This file must be saved and distributed
|
||||
to anyone who might run Terraform. We recommend simply putting it
|
||||
into version control, since it generally isn't too large.
|
||||
|
||||
You can inspect the state using `terraform show`:
|
||||
|
||||
```
|
||||
$ terraform show
|
||||
aws_instance.example:
|
||||
id = i-e60900cd
|
||||
ami = ami-408c7f28
|
||||
availability_zone = us-east-1c
|
||||
instance_type = t1.micro
|
||||
key_name =
|
||||
private_dns = domU-12-31-39-12-38-AB.compute-1.internal
|
||||
private_ip = 10.200.59.89
|
||||
public_dns = ec2-54-81-21-192.compute-1.amazonaws.com
|
||||
public_ip = 54.81.21.192
|
||||
security_groups.# = 1
|
||||
security_groups.0 = default
|
||||
subnet_id =
|
||||
```
|
||||
|
||||
You can see that by creating our resource, we've also gathered
|
||||
a lot more metadata about it. This metadata can actually be referenced
|
||||
for other resources or outputs, which will be covered later in
|
||||
the getting started guide.
|
||||
|
||||
## Next
|
||||
|
||||
Congratulations! You've built your first infrastructure with Terraform.
|
||||
You've seen the configuration syntax, an example of a basic execution
|
||||
plan, and understand the state file.
|
||||
|
||||
Next, we're going to move on to changing and destroying infrastructure.
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Change Infrastructure"
|
||||
sidebar_current: "gettingstarted-change"
|
||||
---
|
||||
|
||||
# Change Infrastructure
|
||||
|
||||
In the previous page, you created your first infrastructure with
|
||||
Terraform: a single EC2 instance. In this page, we're going to
|
||||
modify that resource, and see how Terraform handles change.
|
||||
|
||||
Infrastructure is continuously evolving, and Terraform was built
|
||||
to help manage and enact that change. As you change Terraform
|
||||
configurations, Terraform builds an execution plan that only
|
||||
modifies what is necessary to reach your desired state.
|
||||
|
||||
By using Terraform to change infrastructure, you can version
|
||||
control not only your configurations but also your state so you
|
||||
can see how the infrastructure evolved over time.
|
||||
|
||||
## Configuration
|
||||
|
||||
Let's modify the `ami` of our instance. Edit the "aws\_instance.web"
|
||||
resource in your configuration and change it to the following:
|
||||
|
||||
```
|
||||
resource "aws_instance" "example" {
|
||||
ami = "ami-aa7ab6c2"
|
||||
instance_type = "t1.micro"
|
||||
}
|
||||
```
|
||||
|
||||
We've changed the AMI from being an Ubuntu 14.04 AMI to being
|
||||
an Ubuntu 12.04 AMI. Terraform configurations are meant to be
|
||||
changed like this. You can also completely remove resources
|
||||
and Terraform will know to destroy the old one.
|
||||
|
||||
## Execution Plan
|
||||
|
||||
Let's see what Terraform will do with the change we made.
|
||||
|
||||
```
|
||||
$ terraform plan
|
||||
...
|
||||
|
||||
-/+ aws_instance.example
|
||||
ami: "ami-408c7f28" => "ami-aa7ab6c2" (forces new resource)
|
||||
availability_zone: "us-east-1c" => "<computed>"
|
||||
key_name: "" => "<computed>"
|
||||
private_dns: "domU-12-31-39-12-38-AB.compute-1.internal" => "<computed>"
|
||||
private_ip: "10.200.59.89" => "<computed>"
|
||||
public_dns: "ec2-54-81-21-192.compute-1.amazonaws.com" => "<computed>"
|
||||
public_ip: "54.81.21.192" => "<computed>"
|
||||
security_groups: "" => "<computed>"
|
||||
subnet_id: "" => "<computed>"
|
||||
```
|
||||
|
||||
The prefix "-/+" means that Terraform will destroy and recreate
|
||||
the resource, versus purely updating it in-place. While some attributes
|
||||
can do in-place updates (which are shown with a "~" prefix), AMI
|
||||
changing on EC2 instance requires a new resource. Terraform handles
|
||||
these details for you, and the execution plan makes it clear what
|
||||
Terraform will do.
|
||||
|
||||
Additionally, the plan output shows that the AMI change is what
|
||||
necessitated the creation of a new resource. Using this information,
|
||||
you can tweak your changes to possibly avoid destroy/create updates
|
||||
if you didn't want to do them at this time.
|
||||
|
||||
## Apply
|
||||
|
||||
From the plan, we know what will happen. Let's apply and enact
|
||||
the change.
|
||||
|
||||
```
|
||||
$ terraform apply
|
||||
aws_instance.example: Destroying...
|
||||
aws_instance.example: Modifying...
|
||||
ami: "ami-408c7f28" => "ami-aa7ab6c2"
|
||||
|
||||
Apply complete! Resources: 0 added, 1 changed, 1 destroyed.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
As the plan predicted, Terraform started by destroying our old
|
||||
instance, then creating the new one. You can use `terraform show`
|
||||
again to see the new properties associated with this instance.
|
||||
|
||||
## Next
|
||||
|
||||
You've now seen how easy it is to modify infrastructure with
|
||||
Terraform. Feel free to play around with this more before continuing.
|
||||
In the next section we're going to destroy our infrastructure.
|
|
@ -1,94 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Registering Health Checks"
|
||||
sidebar_current: "gettingstarted-checks"
|
||||
---
|
||||
|
||||
# Health Checks
|
||||
|
||||
We've now seen how simple it is to run Terraform, add nodes and services, and
|
||||
query those nodes and services. In this section we will continue by adding
|
||||
health checks to both nodes and services, a critical component of service
|
||||
discovery that prevents using services that are unhealthy.
|
||||
|
||||
This page will build upon the previous page and assumes you have a
|
||||
two node cluster running.
|
||||
|
||||
## Defining Checks
|
||||
|
||||
Similarly to a service, a check can be registered either by providing a
|
||||
[check definition](/docs/agent/checks.html)
|
||||
, or by making the appropriate calls to the
|
||||
[HTTP API](/docs/agent/http.html).
|
||||
|
||||
We will use the check definition, because just like services, definitions
|
||||
are the most common way to setup checks.
|
||||
|
||||
Create two definition files in the Terraform configuration directory of
|
||||
the second node.
|
||||
The first file will add a host-level check, and the second will modify the web
|
||||
service definition to add a service-level check.
|
||||
|
||||
```
|
||||
$ echo '{"check": {"name": "ping", "script": "ping -c1 google.com >/dev/null", "interval": "30s"}}' >/etc/terraform.d/ping.json
|
||||
|
||||
$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 80,
|
||||
"check": {"script": "curl localhost:80 >/dev/null 2>&1", "interval": "10s"}}}' >/etc/terraform.d/web.json
|
||||
```
|
||||
|
||||
The first definition adds a host-level check named "ping". This check runs
|
||||
on a 30 second interval, invoking `ping -c1 google.com`. If the command
|
||||
exits with a non-zero exit code, then the node will be flagged unhealthy.
|
||||
|
||||
The second command modifies the web service and adds a check that uses
|
||||
curl every 10 seconds to verify that the web server is running.
|
||||
|
||||
Restart the second agent, or send a `SIGHUP` to it. We should now see the
|
||||
following log lines:
|
||||
|
||||
```
|
||||
==> Starting Terraform agent...
|
||||
...
|
||||
[INFO] agent: Synced service 'web'
|
||||
[INFO] agent: Synced check 'service:web'
|
||||
[INFO] agent: Synced check 'ping'
|
||||
[WARN] Check 'service:web' is now critical
|
||||
```
|
||||
|
||||
The first few log lines indicate that the agent has synced the new
|
||||
definitions. The last line indicates that the check we added for
|
||||
the `web` service is critical. This is because we're not actually running
|
||||
a web server and the curl test is failing!
|
||||
|
||||
## Checking Health Status
|
||||
|
||||
Now that we've added some simple checks, we can use the HTTP API to check
|
||||
them. First, we can look for any failing checks. You can run this curl
|
||||
on either node:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/health/state/critical
|
||||
[{"Node":"agent-two","CheckID":"service:web","Name":"Service 'web' check","Status":"critical","Notes":"","ServiceID":"web","ServiceName":"web"}]
|
||||
```
|
||||
|
||||
We can see that there is only a single check in the `critical` state, which is
|
||||
our `web` service check.
|
||||
|
||||
Additionally, we can attempt to query the web service using DNS. Terraform
|
||||
will not return any results, since the service is unhealthy:
|
||||
|
||||
```
|
||||
dig @127.0.0.1 -p 8600 web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN A
|
||||
```
|
||||
|
||||
This section should have shown that checks can be easily added. Check definitions
|
||||
can be updated by changing configuration files and sending a `SIGHUP` to the agent.
|
||||
Alternatively the HTTP API can be used to add, remove and modify checks dynamically.
|
||||
The API allows for a "dead man's switch" or [TTL based check](/docs/agent/checks.html).
|
||||
TTL checks can be used to integrate an application more tightly with Terraform, enabling
|
||||
business logic to be evaluated as part of passing a check.
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Destroy Infrastructure"
|
||||
sidebar_current: "gettingstarted-destroy"
|
||||
---
|
||||
|
||||
# Destroy Infrastructure
|
||||
|
||||
We've now seen how to build and change infrastructure. Before we
|
||||
move on to creating multiple resources and showing resource
|
||||
dependencies, we're going to go over how to completely destroy
|
||||
the Terraform-managed infrastructure.
|
||||
|
||||
Destroying your infrastructure is a rare event in production
|
||||
environments. But if you're using Terraform to spin up multiple
|
||||
environments such as development, test, QA environments, then
|
||||
destroying is a useful action.
|
||||
|
||||
## Plan
|
||||
|
||||
While our infrastructure is simple, viewing the execution plan
|
||||
of a destroy can be useful to make sure that it is destroying
|
||||
only the resources you expect.
|
||||
|
||||
To ask Terraform to create an execution plan to destroy all
|
||||
infrastructure, run the plan command with the `-destroy` flag.
|
||||
|
||||
```
|
||||
$ terraform plan -destroy
|
||||
...
|
||||
|
||||
- aws_instance.example
|
||||
```
|
||||
|
||||
The output says that "aws\_instance.example" will be deleted.
|
||||
|
||||
The `-destroy` flag lets you destroy infrastructure without
|
||||
modifying the configuration. You can also destroy infrastructure
|
||||
by simply commenting out or deleting the contents of your
|
||||
configuration, but usually you just want to destroy an instance
|
||||
of your infrastructure rather than permanently deleting your
|
||||
configuration as well. The `-destroy` flag is for this case.
|
||||
|
||||
## Apply
|
||||
|
||||
Let's apply the destroy:
|
||||
|
||||
```
|
||||
$ terraform apply -destroy
|
||||
aws_instance.example: Destroying...
|
||||
|
||||
Apply complete! Resources: 0 added, 0 changed, 1 destroyed.
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Done. Terraform destroyed our one instance, and if you run a
|
||||
`terraform show`, you'll see that the state file is now empty.
|
||||
|
||||
## Next
|
||||
|
||||
You now know how to create, modify, and destroy infrastructure.
|
||||
With these building blocks, you can effectively experiment with
|
||||
any part of Terraform.
|
||||
|
||||
Next, we move on to features that make Terraform configurations
|
||||
slightly more useful: variables, resource dependencies, provisioning,
|
||||
and more.
|
|
@ -6,36 +6,25 @@ sidebar_current: "gettingstarted-install"
|
|||
|
||||
# Install Terraform
|
||||
|
||||
Terraform must first be installed on every node that will be a member of a
|
||||
Terraform cluster. To make installation easy, Terraform is distributed as a
|
||||
[binary package](/downloads.html) for all supported platforms and
|
||||
architectures. This page will not cover how to compile Terraform from
|
||||
Terraform must first be installed on your machine. Terraform is distributed
|
||||
as a [binary package](/downloads.html) for all supported platforms and
|
||||
architecture. This page will not cover how to compile Terraform from
|
||||
source.
|
||||
|
||||
## Installing Terraform
|
||||
|
||||
To install Terraform, find the [appropriate package](/downloads.html) for
|
||||
your system and download it. Terraform is packaged as a "zip" archive.
|
||||
your system and download it. Terraform is packaged as a zip archive.
|
||||
|
||||
After downloading Terraform, unzip the package. Copy the `terraform` binary to
|
||||
somewhere on the PATH so that it can be executed. On Unix systems,
|
||||
`~/bin` and `/usr/local/bin` are common installation directories,
|
||||
depending on if you want to restrict the install to a single user or
|
||||
expose it to the entire system. On Windows systems, you can put it wherever
|
||||
you would like.
|
||||
|
||||
### OS X
|
||||
|
||||
If you are using [homebrew](http://brew.sh/#install) as a package manager,
|
||||
than you can install terraform as simple as:
|
||||
```
|
||||
brew cask install terraform
|
||||
```
|
||||
|
||||
if you are missing the [cask plugin](http://caskroom.io/) you can install it with:
|
||||
```
|
||||
brew install caskroom/cask/brew-cask
|
||||
```
|
||||
After downloading Terraform, unzip the package into a directory where
|
||||
Terraform will be installed. The directory will contain a set of binary
|
||||
programs, such as `terraform`, `terraform-provider-aws`, etc. The final
|
||||
step is to make sure the directory you installed Terraform to is on the
|
||||
PATH. See
|
||||
[this page](http://stackoverflow.com/questions/14637979/how-to-permanently-set-path-on-linux)
|
||||
for instructions on setting the PATH on Linux and Mac.
|
||||
[This page](http://stackoverflow.com/questions/1618280/where-can-i-set-path-to-make-exe-on-windows)
|
||||
contains instructions for setting the PATH on Windows.
|
||||
|
||||
## Verifying the Installation
|
||||
|
||||
|
@ -48,15 +37,13 @@ $ terraform
|
|||
usage: terraform [--version] [--help] <command> [<args>]
|
||||
|
||||
Available commands are:
|
||||
agent Runs a Terraform agent
|
||||
force-leave Forces a member of the cluster to enter the "left" state
|
||||
info Provides debugging information for operators
|
||||
join Tell Terraform agent to join cluster
|
||||
keygen Generates a new encryption key
|
||||
leave Gracefully leaves the Terraform cluster and shuts down
|
||||
members Lists the members of a Terraform cluster
|
||||
monitor Stream logs from a Terraform agent
|
||||
version Prints the Terraform version
|
||||
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
|
||||
```
|
||||
|
||||
If you get an error that `terraform` could not be found, then your PATH
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Terraform Cluster"
|
||||
sidebar_current: "gettingstarted-join"
|
||||
---
|
||||
|
||||
# Terraform Cluster
|
||||
|
||||
By this point, we've started our first agent and registered and queried
|
||||
one or more services on that agent. This showed how easy it is to use
|
||||
Terraform, but didn't show how this could be extended to a scalable production
|
||||
service discovery infrastructure. On this page, we'll create our first
|
||||
real cluster with multiple members.
|
||||
|
||||
When starting a Terraform agent, it begins without knowledge of any other node, and is
|
||||
an isolated cluster of one. To learn about other cluster members, the agent must
|
||||
_join_ an existing cluster. To join an existing cluster, only needs to know
|
||||
about a _single_ existing member. After it joins, the agent will gossip with this
|
||||
member and quickly discover the other members in the cluster. A Terraform
|
||||
agent can join any other agent, it doesn't have to be an agent in server mode.
|
||||
|
||||
## Starting the Agents
|
||||
|
||||
To simulate a more realistic cluster, we are using a two node cluster in
|
||||
Vagrant. The Vagrantfile can be found in the demo section of the repo
|
||||
[here](https://github.com/hashicorp/terraform/tree/master/demo/vagrant-cluster).
|
||||
|
||||
We start the first agent on our first node and also specify a node name.
|
||||
The node name must be unique and is how a machine is uniquely identified.
|
||||
By default it is the hostname of the machine, but we'll manually override it.
|
||||
We are also providing a bind address. This is the address that Terraform listens on,
|
||||
and it *must* be accessible by all other nodes in the cluster. The first node
|
||||
will act as our server in this cluster. We're still not making a cluster
|
||||
of servers.
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul \
|
||||
-node=agent-one -bind=172.20.20.10
|
||||
...
|
||||
```
|
||||
|
||||
Then, in another terminal, start the second agent on the new node.
|
||||
This time, we set the bind address to match the IP of the second node
|
||||
as specified in the Vagrantfile. In production, you will generally want
|
||||
to provide a bind address or interface as well.
|
||||
|
||||
```
|
||||
$ terraform agent -data-dir /tmp/consul -node=agent-two -bind=172.20.20.11
|
||||
...
|
||||
```
|
||||
|
||||
At this point, you have two Terraform agents running, one server and one client.
|
||||
The two Terraform agents still don't know anything about each other, and are each part of their own
|
||||
clusters (of one member). You can verify this by running `terraform members`
|
||||
against each agent and noting that only one member is a part of each.
|
||||
|
||||
## Joining a Cluster
|
||||
|
||||
Now, let's tell the first agent to join the second agent by running
|
||||
the following command in a new terminal:
|
||||
|
||||
```
|
||||
$ terraform join 172.20.20.11
|
||||
Successfully joined cluster by contacting 1 nodes.
|
||||
```
|
||||
|
||||
You should see some log output in each of the agent logs. If you read
|
||||
carefully, you'll see that they received join information. If you
|
||||
run `terraform members` against each agent, you'll see that both agents now
|
||||
know about each other:
|
||||
|
||||
```
|
||||
$ terraform members
|
||||
agent-one 172.20.20.10:8301 alive role=terraform,dc=dc1,vsn=1,vsn_min=1,vsn_max=1,port=8300,bootstrap=1
|
||||
agent-two 172.20.20.11:8301 alive role=node,dc=dc1,vsn=1,vsn_min=1,vsn_max=1
|
||||
```
|
||||
|
||||
<div class="alert alert-block alert-info">
|
||||
<p><strong>Remember:</strong> To join a cluster, a Terraform agent needs to only
|
||||
learn about <em>one existing member</em>. After joining the cluster, the
|
||||
agents gossip with each other to propagate full membership information.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
In addition to using `terraform join` you can use the `-join` flag on
|
||||
`terraform agent` to join a cluster as part of starting up the agent.
|
||||
|
||||
## Querying Nodes
|
||||
|
||||
Just like querying services, Terraform has an API for querying the
|
||||
nodes themselves. You can do this via the DNS or HTTP API.
|
||||
|
||||
For the DNS API, the structure of the names is `NAME.node.terraform` or
|
||||
`NAME.DATACENTER.node.terraform`. If the datacenter is omitted, Terraform
|
||||
will only search the local datacenter.
|
||||
|
||||
From "agent-one", query "agent-two":
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 agent-two.node.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;agent-two.node.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
agent-two.node.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
The ability to look up nodes in addition to services is incredibly
|
||||
useful for system administration tasks. For example, knowing the address
|
||||
of the node to SSH into is as easy as making it part of the Terraform cluster
|
||||
and querying it.
|
||||
|
||||
## Leaving a Cluster
|
||||
|
||||
To leave the cluster, you can either gracefully quit an agent (using
|
||||
`Ctrl-C`) or force kill one of the agents. Gracefully leaving allows
|
||||
the node to transition into the _left_ state, otherwise other nodes
|
||||
will detect it as having _failed_. The difference is covered
|
||||
in more detail [here](/intro/getting-started/agent.html#toc_3).
|
|
@ -1,118 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Key/Value Data"
|
||||
sidebar_current: "gettingstarted-kv"
|
||||
---
|
||||
|
||||
# Key/Value Data
|
||||
|
||||
In addition to providing service discovery and integrated health checking,
|
||||
Terraform provides an easy to use Key/Value store. This can be used to hold
|
||||
dynamic configuration, assist in service coordination, build leader election,
|
||||
and anything else a developer can think to build. The
|
||||
[HTTP API](/docs/agent/http.html) fully documents the features of the K/V store.
|
||||
|
||||
This page assumes you have at least one Terraform agent already running.
|
||||
|
||||
## Simple Usage
|
||||
|
||||
To demonstrate how simple it is to get started, we will manipulate a few keys
|
||||
in the K/V store.
|
||||
|
||||
Querying the agent we started in a prior page, we can first verify that
|
||||
there are no existing keys in the k/v store:
|
||||
|
||||
```
|
||||
$ curl -v http://localhost:8500/v1/kv/?recurse
|
||||
* About to connect() to localhost port 8500 (#0)
|
||||
* Trying 127.0.0.1... connected
|
||||
> GET /v1/kv/?recurse HTTP/1.1
|
||||
> User-Agent: curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
|
||||
> Host: localhost:8500
|
||||
> Accept: */*
|
||||
>
|
||||
< HTTP/1.1 404 Not Found
|
||||
< X-Terraform-Index: 1
|
||||
< Date: Fri, 11 Apr 2014 02:10:28 GMT
|
||||
< Content-Length: 0
|
||||
< Content-Type: text/plain; charset=utf-8
|
||||
<
|
||||
* Connection #0 to host localhost left intact
|
||||
* Closing connection #0
|
||||
```
|
||||
|
||||
Since there are no keys, we get a 404 response back.
|
||||
Now, we can put a few example keys:
|
||||
|
||||
```
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key1
|
||||
true
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/key2?flags=42
|
||||
true
|
||||
$ curl -X PUT -d 'test' http://localhost:8500/v1/kv/web/sub/key3
|
||||
true
|
||||
$ curl http://localhost:8500/v1/kv/?recurse
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":98,"ModifyIndex":98,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":99,"ModifyIndex":99,"Key":"web/sub/key3","Flags":0,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
Here we have created 3 keys, each with the value of "test". Note that the
|
||||
`Value` field returned is base64 encoded to allow non-UTF8
|
||||
characters. For the "web/key2" key, we set a `flag` value of 42. All keys
|
||||
support setting a 64bit integer flag value. This is opaque to Terraform but can
|
||||
be used by clients for any purpose.
|
||||
|
||||
After setting the values, we then issued a GET request to retrieve multiple
|
||||
keys using the `?recurse` parameter.
|
||||
|
||||
You can also fetch a single key just as easily:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/kv/web/key1
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
Deleting keys is simple as well. We can delete a single key by specifying the
|
||||
full path, or we can recursively delete all keys under a root using "?recurse":
|
||||
|
||||
```
|
||||
$ curl -X DELETE http://localhost:8500/v1/kv/web/sub?recurse
|
||||
$ curl http://localhost:8500/v1/kv/web?recurse
|
||||
[{"CreateIndex":97,"ModifyIndex":97,"Key":"web/key1","Flags":0,"Value":"dGVzdA=="},
|
||||
{"CreateIndex":98,"ModifyIndex":98,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
A key can be updated by setting a new value by issuing the same PUT request.
|
||||
Additionally, Terraform provides a Check-And-Set operation, enabling atomic
|
||||
key updates. This is done by providing the `?cas=` paramter with the last
|
||||
`ModifyIndex` value from the GET request. For example, suppose we wanted
|
||||
to update "web/key1":
|
||||
|
||||
```
|
||||
$ curl -X PUT -d 'newval' http://localhost:8500/v1/kv/web/key1?cas=97
|
||||
true
|
||||
$ curl -X PUT -d 'newval' http://localhost:8500/v1/kv/web/key1?cas=97
|
||||
false
|
||||
```
|
||||
|
||||
In this case, the first CAS update succeeds because the last modify time is 97.
|
||||
However the second operation fails because the `ModifyIndex` is no longer 97.
|
||||
|
||||
We can also make use of the `ModifyIndex` to wait for a key's value to change.
|
||||
For example, suppose we wanted to wait for key2 to be modified:
|
||||
|
||||
```
|
||||
$ curl "http://localhost:8500/v1/kv/web/key2?index=101&wait=5s"
|
||||
[{"CreateIndex":98,"ModifyIndex":101,"Key":"web/key2","Flags":42,"Value":"dGVzdA=="}]
|
||||
```
|
||||
|
||||
By providing "?index=" we are asking to wait until the key has a `ModifyIndex` greater
|
||||
than 101. However the "?wait=5s" parameter restricts the query to at most 5 seconds,
|
||||
returning the current, unchanged value. This can be used to efficiently wait for
|
||||
key modifications. Additionally, this same technique can be used to wait for a list
|
||||
of keys, waiting only until any of the keys has a newer modification time.
|
||||
|
||||
This is only a few example of what the API supports. For full documentation, please
|
||||
reference the [HTTP API](/docs/agent/http.html).
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Registering Services"
|
||||
sidebar_current: "gettingstarted-services"
|
||||
---
|
||||
|
||||
# Registering Services
|
||||
|
||||
In the previous page, we ran our first agent, saw the cluster members, and
|
||||
queried that node. On this page, we'll register our first service and query
|
||||
that service. We're not yet running a cluster of Terraform agents.
|
||||
|
||||
## Defining a Service
|
||||
|
||||
A service can be registered either by providing a
|
||||
[service definition](/docs/agent/services.html),
|
||||
or by making the appropriate calls to the
|
||||
[HTTP API](/docs/agent/http.html).
|
||||
|
||||
We're going to start by registering a service using a service definition,
|
||||
since this is the most common way that services are registered. We'll be
|
||||
building on what we covered in the
|
||||
[previous page](/intro/getting-started/agent.html).
|
||||
|
||||
First, create a directory for Terraform configurations. A good directory
|
||||
is typically `/etc/terraform.d`. Terraform loads all configuration files in the
|
||||
configuration directory.
|
||||
|
||||
```
|
||||
$ sudo mkdir /etc/terraform.d
|
||||
```
|
||||
|
||||
Next, we'll write a service definition configuration file. We'll
|
||||
pretend we have a service named "web" running on port 80. Additionally,
|
||||
we'll give it some tags, which we can use as additional ways to query
|
||||
it later.
|
||||
|
||||
```
|
||||
$ echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' \
|
||||
>/etc/terraform.d/web.json
|
||||
```
|
||||
|
||||
Now, restart the agent we're running, providing the configuration directory:
|
||||
|
||||
```
|
||||
$ terraform agent -server -bootstrap -data-dir /tmp/consul -config-dir /etc/consul.d
|
||||
==> Starting Terraform agent...
|
||||
...
|
||||
[INFO] agent: Synced service 'web'
|
||||
...
|
||||
```
|
||||
|
||||
You'll notice in the output that it "synced" the web service. This means
|
||||
that it loaded the information from the configuration.
|
||||
|
||||
If you wanted to register multiple services, you create multiple service
|
||||
definition files in the Terraform configuration directory.
|
||||
|
||||
## Querying Services
|
||||
|
||||
Once the agent is started and the service is synced, we can query that
|
||||
service using either the DNS or HTTP API.
|
||||
|
||||
### DNS API
|
||||
|
||||
Let's first query it using the DNS API. For the DNS API, the DNS name
|
||||
for services is `NAME.service.terraform`. All DNS names are always in the
|
||||
`terraform` namespace. The `service` subdomain on that tells Terraform we're
|
||||
querying services, and the `NAME` is the name of the service. For the
|
||||
web service we registered, that would be `web.service.terraform`:
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
web.service.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
As you can see, an A record was returned with the IP address of the node that
|
||||
the service is available on. A records can only hold IP addresses. You can
|
||||
also use the DNS API to retrieve the entire address/port pair using SRV
|
||||
records:
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 web.service.terraform SRV
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;web.service.terraform. IN SRV
|
||||
|
||||
;; ANSWER SECTION:
|
||||
web.service.terraform. 0 IN SRV 1 1 80 agent-one.node.dc1.consul.
|
||||
|
||||
;; ADDITIONAL SECTION:
|
||||
agent-one.node.dc1.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
The SRV record returned says that the web service is running on port 80
|
||||
and exists on the node `agent-one.node.dc1.terraform.`. An additional section
|
||||
is returned by the DNS with the A record for that node.
|
||||
|
||||
Finally, we can also use the DNS API to filter services by tags. The
|
||||
format for tag-based service queries is `TAG.NAME.service.terraform`. In
|
||||
the example below, we ask Terraform for all web services with the "rails"
|
||||
tag. We get a response since we registered our service with that tag.
|
||||
|
||||
```
|
||||
$ dig @127.0.0.1 -p 8600 rails.web.service.terraform
|
||||
...
|
||||
|
||||
;; QUESTION SECTION:
|
||||
;rails.web.service.terraform. IN A
|
||||
|
||||
;; ANSWER SECTION:
|
||||
rails.web.service.terraform. 0 IN A 172.20.20.11
|
||||
```
|
||||
|
||||
### HTTP API
|
||||
|
||||
In addition to the DNS API, the HTTP API can be used to query services:
|
||||
|
||||
```
|
||||
$ curl http://localhost:8500/v1/catalog/service/web
|
||||
[{"Node":"agent-one","Address":"172.20.20.11","ServiceID":"web","ServiceName":"web","ServiceTags":["rails"],"ServicePort":80}]
|
||||
```
|
||||
|
||||
## Updating Services
|
||||
|
||||
Service definitions can be updated by changing configuration files and
|
||||
sending a `SIGHUP` to the agent. This lets you update services without
|
||||
any downtime or unavailability to service queries.
|
||||
|
||||
Alternatively the HTTP API can be used to add, remove, and modify services
|
||||
dynamically.
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
---
|
||||
layout: "intro"
|
||||
page_title: "Web UI"
|
||||
sidebar_current: "gettingstarted-ui"
|
||||
---
|
||||
|
||||
# Terraform Web UI
|
||||
|
||||
Terraform comes with support for a
|
||||
[beautiful, functional web UI](http://demo.terraform.io) out of the box.
|
||||
This UI can be used for viewing all services and nodes, viewing all
|
||||
health checks and their current status, and for reading and setting
|
||||
key/value data. The UI automatically supports multi-datacenter.
|
||||
|
||||
For ease of deployment, the UI is
|
||||
[distributed](/downloads_web_ui.html)
|
||||
as static HTML and JavaScript.
|
||||
You don't need a separate web server to run the web UI. The Terraform
|
||||
agent itself can be configured to serve the UI.
|
||||
|
||||
## Screenshot and Demo
|
||||
|
||||
You can view a live demo of the Terraform Web UI
|
||||
[here](http://demo.terraform.io).
|
||||
|
||||
While the live demo is able to access data from all datacenters,
|
||||
we've also setup demo endpoints in the specific datacenters:
|
||||
[AMS2](http://ams2.demo.terraform.io) (Amsterdam),
|
||||
[SFO1](http://sfo1.demo.terraform.io) (San Francisco),
|
||||
and [NYC1](http://nyc1.demo.terraform.io) (New York).
|
||||
|
||||
A screenshot of one page of the demo is shown below so you can get an
|
||||
idea of what the web UI is like. Click the screenshot for the full size.
|
||||
|
||||
<div class="center">
|
||||
<a href="/images/terraform_web_ui.png">
|
||||
<img src="/images/terraform_web_ui.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
## Set Up
|
||||
|
||||
To set up the web UI,
|
||||
[download the web UI package](/downloads_web_ui.html)
|
||||
and unzip it to a directory somewhere on the server where the Terraform agent
|
||||
is also being run. Then, just append the `-ui-dir` to the `terraform agent`
|
||||
command pointing to the directory where you unzipped the UI (the
|
||||
directory with the `index.html` file):
|
||||
|
||||
```
|
||||
$ terraform agent -ui-dir /path/to/ui
|
||||
...
|
||||
```
|
||||
|
||||
The UI is available at the `/ui` path on the same port as the HTTP API.
|
||||
By default this is `http://localhost:8500/ui`.
|
|
@ -6,70 +6,66 @@ sidebar_current: "what"
|
|||
|
||||
# Introduction to Terraform
|
||||
|
||||
Welcome to the intro guide to Terraform! This guide is the best place to start
|
||||
with Terraform. We cover what Terraform is, what problems it can solve, how it compares
|
||||
to existing software, and a quick start for using Terraform. If you are already familiar
|
||||
with the basics of Terraform, the [documentation](/docs/index.html) provides more
|
||||
of a reference for all available features.
|
||||
Welcome to the intro guide to Terraform! This guide is the best
|
||||
place to start with Terraform. We cover what Terraform is, what
|
||||
problems it can solve, how it compares to existing software,
|
||||
and contains a quick start for using Terraform.
|
||||
|
||||
If you are already familiar with the basics of Terraform, the
|
||||
[documentation](/docs/index.html) provides a better reference
|
||||
guide for all available features as well as internals.
|
||||
|
||||
## What is Terraform?
|
||||
|
||||
Terraform has multiple components, but as a whole, it is a tool for discovering
|
||||
and configuring services in your infrastructure. It provides several
|
||||
key features:
|
||||
Terraform is a tool for building, changing, and versioning infrastructure
|
||||
safely and efficiently. Terraform can manage existing and popular service
|
||||
providers as well as custom in-house solutions.
|
||||
|
||||
* **Service Discovery**: Clients of Terraform can _provide_ a service, such as
|
||||
`api` or `mysql`, and other clients can use Terraform to _discover_ providers
|
||||
of a given service. Using either DNS or HTTP, applications can easily find
|
||||
the services they depend upon.
|
||||
Configuration files describe to Terraform the components needed to
|
||||
run a single application or your entire datacenter.
|
||||
Terraform generates an execution plan describing
|
||||
what it will do to reach the desired state, and then executes it to build the
|
||||
described infrastructure. As the configuration changes, Terraform is able
|
||||
to determine what changed and create incremental execution plans which
|
||||
can be applied.
|
||||
|
||||
* **Health Checking**: Terraform clients can provide any number of health checks,
|
||||
either associated with a given service ("is the webserver returning 200 OK"), or
|
||||
with the local node ("is memory utilization below 90%"). This information can be
|
||||
used by an operator to monitor cluster health, and it is used by the service
|
||||
discovery components to route traffic away from unhealthy hosts.
|
||||
The infrastructure Terraform can manage includes
|
||||
low-level components such as
|
||||
compute instances, storage, and networking, as well as high-level
|
||||
components such as DNS entries, SaaS features, etc.
|
||||
|
||||
* **Key/Value Store**: Applications can make use of Terraform's hierarchical key/value
|
||||
store for any number of purposes including: dynamic configuration, feature flagging,
|
||||
coordination, leader election, etc. The simple HTTP API makes it easy to use.
|
||||
Examples work best to showcase Terraform. Please see the
|
||||
[use cases](/intro/use-cases.html).
|
||||
|
||||
* **Multi Datacenter**: Terraform supports multiple datacenters out of the box. This
|
||||
means users of Terraform do not have to worry about building additional layers of
|
||||
abstraction to grow to multiple regions.
|
||||
The key features of Terraform are:
|
||||
|
||||
Terraform is designed to be friendly to both the DevOps community and
|
||||
application developers, making it perfect for modern, elastic infrastructures.
|
||||
* **Infrastructure as Code**: Infrastructure is described using a high-level
|
||||
configuration syntax. This allows a blueprint of your datacenter to be
|
||||
versioned and treated as you would any other code. Additionally,
|
||||
infrastructure can be shared and re-used.
|
||||
|
||||
## Basic Architecture of Terraform
|
||||
* **Execution Plans**: Terraform has a "planning" step where it generates
|
||||
an _execution plan_. The execution plan shows what Terraform will do when
|
||||
you call apply. This lets you avoid any surprises when Terraform
|
||||
manipulates infrastructure.
|
||||
|
||||
Terraform is a distributed, highly available system. There is an
|
||||
[in-depth architecture overview](/docs/internals/architecture.html) available,
|
||||
but this section will cover the basics so you can get an understanding
|
||||
of how Terraform works. This section will purposely omit details to quickly
|
||||
provide an overview of the architecture.
|
||||
* **Resource Graph**: Terraform builds a graph of all your resources,
|
||||
and parallelizes the creation and modification of any non-dependent
|
||||
resources. Because of this, Terraform builds infrastructure as efficiently
|
||||
as possible, and operators get insight into dependencies in their
|
||||
infrastructure.
|
||||
|
||||
Every node that provides services to Terraform runs a _Terraform agent_. Running
|
||||
an agent is not required for discovering other services or getting/setting
|
||||
key/value data. The agent is responsible for health checking the services
|
||||
on the node as well as the node itself.
|
||||
|
||||
The agents talk to one or more _Terraform servers_. The Terraform servers are
|
||||
where data is stored and replicated. The servers themselves elect a leader.
|
||||
While Terraform can function with one server, 3 to 5 is recommended to avoid
|
||||
data loss scenarios. A cluster of Terraform servers is recommended for each
|
||||
datacenter.
|
||||
|
||||
Components of your infrastructure that need to discover other services
|
||||
or nodes can query any of the Terraform servers _or_ any of the Terraform agents.
|
||||
The agents forward queries to the servers automatically.
|
||||
|
||||
Each datacenter runs a cluster of Terraform servers. When a cross-datacenter
|
||||
service discovery or configuration request is made, the local Terraform servers
|
||||
forward the request to the remote datacenter and return the result.
|
||||
* **Change Automation**: Potentially complex changesets are applied to
|
||||
your infrastructure with minimal human interaction.
|
||||
With the previously mentioned execution
|
||||
plan and resource graph, you know exactly what Terraform will change
|
||||
and in what order, avoiding many possible human errors.
|
||||
|
||||
## Next Steps
|
||||
|
||||
See the page on [how Terraform compares to other software](/intro/vs/index.html)
|
||||
to see how it fits into your existing infrastructure. Or continue onwards with
|
||||
See the page on [Terraform use cases](/intro/use-cases.html) to see the
|
||||
multiple ways Terraform can be used. Then see
|
||||
[how Terraform compares to other software](/intro/vs/index.html)
|
||||
to see how it fits into your existing infrastructure. Finally, continue onwards with
|
||||
the [getting started guide](/intro/getting-started/install.html) to get
|
||||
Terraform up and running and see how it works.
|
||||
Terraform managing some real infrastructure and to see how it works.
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<% 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-aws-index") %>>
|
||||
<a href="/docs/providers/aws/index.html">AWS Provider</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource") %>>
|
||||
<a href="#">Resources</a>
|
||||
<ul class="nav nav-visible">
|
||||
<li<%= sidebar_current("docs-aws-resource-eip") %>>
|
||||
<a href="/docs/providers/aws/r/eip.html">aws_eip</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-instance") %>>
|
||||
<a href="/docs/providers/aws/r/instance.html">aws_instance</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-internet-gateway") %>>
|
||||
<a href="/docs/providers/aws/r/internet_gateway.html">aws_internet_gateway</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-launch-config") %>>
|
||||
<a href="/docs/providers/aws/r/launch_config.html">aws_launch_configuration</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-route-table|") %>>
|
||||
<a href="/docs/providers/aws/r/route_table.html">aws_route_table</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-route-table-assoc") %>>
|
||||
<a href="/docs/providers/aws/r/route_table_assoc.html">aws_route_table_association</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-route53-record") %>>
|
||||
<a href="/docs/providers/aws/r/route53_record.html">aws_route53_record</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-route53-zone") %>>
|
||||
<a href="/docs/providers/aws/r/route53_zone.html">aws_route53_zone</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-s3-bucket") %>>
|
||||
<a href="/docs/providers/aws/r/s3_bucket.html">aws_s3_bucket</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-security-group") %>>
|
||||
<a href="/docs/providers/aws/r/security_group.html">aws_security_group</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-subnet") %>>
|
||||
<a href="/docs/providers/aws/r/subnet.html">aws_subnet</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-aws-resource-vpc") %>>
|
||||
<a href="/docs/providers/aws/r/vpc.html">aws_vpc</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= yield %>
|
||||
<% end %>
|
|
@ -4,52 +4,16 @@
|
|||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("docs-home") %>>
|
||||
<a href="/docs/index.html">Documentation Home</a>
|
||||
</li>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-upgrading") %>>
|
||||
<a href="/docs/upgrading.html">Upgrading and Compatibility</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-upgrading-upgrading") %>>
|
||||
<a href="/docs/upgrading.html">Upgrading Terraform</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-upgrading-compat") %>>
|
||||
<a href="/docs/compatibility.html">Compatibility Promise</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals") %>>
|
||||
<a href="/docs/internals/index.html">Terraform Internals</a>
|
||||
<li<%= sidebar_current("docs-config") %>>
|
||||
<a href="/docs/configuration/index.html">Configuration</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-internals-architecture") %>>
|
||||
<a href="/docs/internals/architecture.html">Architecture</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-consensus") %>>
|
||||
<a href="/docs/internals/consensus.html">Consensus Protocol</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-gossip") %>>
|
||||
<a href="/docs/internals/gossip.html">Gossip Protocol</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-sessions") %>>
|
||||
<a href="/docs/internals/sessions.html">Sessions</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-security") %>>
|
||||
<a href="/docs/internals/security.html">Security Model</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-jepsen") %>>
|
||||
<a href="/docs/internals/jepsen.html">Jepsen Testing</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-commands") %>>
|
||||
<a href="/docs/commands/index.html">Terraform Commands (CLI)</a>
|
||||
<a href="/docs/commands/index.html">Commands (CLI)</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-commands-agent") %>>
|
||||
<a href="/docs/commands/agent.html">agent</a>
|
||||
|
@ -85,45 +49,43 @@
|
|||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent") %>>
|
||||
<a href="/docs/agent/basics.html">Terraform Agent</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-agent-running") %>>
|
||||
<a href="/docs/agent/basics.html">Running and Stopping</a>
|
||||
<li<%= sidebar_current("docs-providers") %>>
|
||||
<a href="/docs/providers/index.html">Providers</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-providers-aws") %>>
|
||||
<a href="/docs/providers/aws/index.html">AWS</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-dns") %>>
|
||||
<a href="/docs/agent/dns.html">DNS Interface</a>
|
||||
<li<%= sidebar_current("docs-providers-do") %>>
|
||||
<a href="/docs/providers/do/index.html">DigitalOcean</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-http") %>>
|
||||
<a href="/docs/agent/http.html">HTTP API</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-config") %>>
|
||||
<a href="/docs/agent/options.html">Configuration</a>
|
||||
<li<%= sidebar_current("docs-providers-heroku") %>>
|
||||
<a href="/docs/providers/heroku/index.html">Heroku</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-services") %>>
|
||||
<a href="/docs/agent/services.html">Service Definitions</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-checks") %>>
|
||||
<a href="/docs/agent/checks.html">Check Definitions</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-encryption") %>>
|
||||
<a href="/docs/agent/encryption.html">Encryption</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-rpc") %>>
|
||||
<a href="/docs/agent/rpc.html">RPC Protocol</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-agent-telemetry") %>>
|
||||
<a href="/docs/agent/telemetry.html">Telemetry</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-provisioners") %>>
|
||||
<a href="/docs/provisioners/index.html">Provisioners</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-provisioners-connection") %>>
|
||||
<a href="/docs/provisioners/connection.html">connection</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-provisioners-file") %>>
|
||||
<a href="/docs/provisioners/file.html">file</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-provisioners-local") %>>
|
||||
<a href="/docs/provisioners/local-exec.html">local-exec</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-provisioners-remote") %>>
|
||||
<a href="/docs/provisioners/remote-exec.html">remote-exec</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-guides") %>>
|
||||
<a href="/docs/guides/index.html">Guides</a>
|
||||
|
@ -161,6 +123,34 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<li<%= sidebar_current("docs-internals") %>>
|
||||
<a href="/docs/internals/index.html">Internals</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("docs-internals-architecture") %>>
|
||||
<a href="/docs/internals/architecture.html">Architecture</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-consensus") %>>
|
||||
<a href="/docs/internals/consensus.html">Consensus Protocol</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-gossip") %>>
|
||||
<a href="/docs/internals/gossip.html">Gossip Protocol</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-sessions") %>>
|
||||
<a href="/docs/internals/sessions.html">Sessions</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-security") %>>
|
||||
<a href="/docs/internals/security.html">Security Model</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("docs-internals-jepsen") %>>
|
||||
<a href="/docs/internals/jepsen.html">Jepsen Testing</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -4,34 +4,18 @@
|
|||
<ul class="nav docs-sidenav">
|
||||
<li<%= sidebar_current("what") %>>
|
||||
<a href="/intro/index.html">What is Terraform?</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("use-cases") %>>
|
||||
<a href="/intro/index.html">Use Cases</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other") %>>
|
||||
<a href="/intro/vs/index.html">Terraform vs. Other Software</a>
|
||||
<ul class="nav">
|
||||
<li<%= sidebar_current("vs-other-zk") %>>
|
||||
<a href="/intro/vs/zookeeper.html">ZooKeeper, doozerd, etcd</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-chef") %>>
|
||||
<a href="/intro/vs/chef-puppet.html">Chef, Puppet, etc.</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-nagios-sensu") %>>
|
||||
<a href="/intro/vs/nagios-sensu.html">Nagios, Sensu</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-skydns") %>>
|
||||
<a href="/intro/vs/skydns.html">SkyDNS</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-smartstack") %>>
|
||||
<a href="/intro/vs/smartstack.html">SmartStack</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-serf") %>>
|
||||
<a href="/intro/vs/serf.html">Serf</a>
|
||||
</li>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("vs-other-custom") %>>
|
||||
<a href="/intro/vs/custom.html">Custom Solutions</a>
|
||||
|
@ -46,34 +30,37 @@
|
|||
<a href="/intro/getting-started/install.html">Install Terraform</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-agent") %>>
|
||||
<a href="/intro/getting-started/agent.html">Run the Agent</a>
|
||||
<li<%= sidebar_current("gettingstarted-build") %>>
|
||||
<a href="/intro/getting-started/build.html">Build Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-services") %>>
|
||||
<a href="/intro/getting-started/services.html">Services</a>
|
||||
<li<%= sidebar_current("gettingstarted-change") %>>
|
||||
<a href="/intro/getting-started/change.html">Change Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-join") %>>
|
||||
<a href="/intro/getting-started/join.html">Terraform Cluster</a>
|
||||
<li<%= sidebar_current("gettingstarted-destroy") %>>
|
||||
<a href="/intro/getting-started/destroy.html">Destroy Infrastructure</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-checks") %>>
|
||||
<a href="/intro/getting-started/checks.html">Health Checks</a>
|
||||
<li<%= sidebar_current("gettingstarted-outputs") %>>
|
||||
<a href="/intro/getting-started/outputs.html">Output Variables</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-kv") %>>
|
||||
<a href="/intro/getting-started/kv.html">Key/Value Data</a>
|
||||
<li<%= sidebar_current("gettingstarted-deps") %>>
|
||||
<a href="/intro/getting-started/dependencies.html">Resource Dependencies</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-ui") %>>
|
||||
<a href="/intro/getting-started/ui.html">Web UI</a>
|
||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
||||
<a href="/intro/getting-started/variables.html">Input Variables</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-variables") %>>
|
||||
<a href="/intro/getting-started/provisioners.html">Provision</a>
|
||||
</li>
|
||||
|
||||
<li<%= sidebar_current("gettingstarted-nextsteps") %>>
|
||||
<a href="/intro/getting-started/next-steps.html">Next Steps</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -6,7 +6,9 @@ body.page-sub{
|
|||
background-color: @light-black;
|
||||
}
|
||||
|
||||
body.layout-aws,
|
||||
body.layout-docs,
|
||||
body.layout-inner,
|
||||
body.layout-intro{
|
||||
background: @light-black url('../images/sidebar-wire.png') left 62px no-repeat;
|
||||
|
||||
|
@ -127,6 +129,10 @@ body.layout-intro{
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,6 +198,7 @@ body.layout-intro{
|
|||
|
||||
@media (max-width: 992px) {
|
||||
body.layout-docs,
|
||||
body.layout-inner,
|
||||
body.layout-intro{
|
||||
>.container{
|
||||
.col-md-8[role=main]{
|
||||
|
|
|
@ -993,16 +993,22 @@ body.page-home #footer {
|
|||
body.page-sub {
|
||||
background-color: #242424;
|
||||
}
|
||||
body.layout-aws,
|
||||
body.layout-docs,
|
||||
body.layout-inner,
|
||||
body.layout-intro {
|
||||
background: #242424 url('../images/sidebar-wire.png') left 62px no-repeat;
|
||||
}
|
||||
body.layout-aws > .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-intro > .container .col-md-8[role=main] {
|
||||
min-height: 800px;
|
||||
background-color: white;
|
||||
}
|
||||
body.layout-aws > .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-intro > .container .col-md-8[role=main] > div {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
@ -1096,6 +1102,9 @@ body.layout-intro > .container .col-md-8[role=main] > div {
|
|||
-webkit-font-smoothing: antialiased;
|
||||
padding: 6px 15px;
|
||||
}
|
||||
.docs-sidebar .docs-sidenav .nav-visible {
|
||||
display: block;
|
||||
}
|
||||
.bs-docs-section {
|
||||
padding-top: 10px;
|
||||
padding-left: 3%;
|
||||
|
@ -1151,10 +1160,12 @@ body.layout-intro > .container .col-md-8[role=main] > div {
|
|||
}
|
||||
@media (max-width: 992px) {
|
||||
body.layout-docs > .container .col-md-8[role=main],
|
||||
body.layout-inner > .container .col-md-8[role=main],
|
||||
body.layout-intro > .container .col-md-8[role=main] {
|
||||
min-height: 0;
|
||||
}
|
||||
body.layout-docs > .container .col-md-8[role=main]::before,
|
||||
body.layout-inner > .container .col-md-8[role=main]::before,
|
||||
body.layout-intro > .container .col-md-8[role=main]::before {
|
||||
border-left: 9999px solid white;
|
||||
}
|
||||
|
|
|
@ -26,4 +26,4 @@
|
|||
// Components w/ JavaScript
|
||||
/*@import "modals.less";*/
|
||||
|
||||
// 68
|
||||
// 69
|
||||
|
|
Loading…
Reference in New Issue