diff --git a/.gitignore b/.gitignore
index e7669ef01..5c9083838 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ bin/
config/y.go
config/y.output
vendor/
+website/.vagrant
diff --git a/builtin/bins/provider-dnsimple/main.go b/builtin/bins/provider-dnsimple/main.go
new file mode 100644
index 000000000..44860d71b
--- /dev/null
+++ b/builtin/bins/provider-dnsimple/main.go
@@ -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))
+}
diff --git a/builtin/bins/provider-dnsimple/main_test.go b/builtin/bins/provider-dnsimple/main_test.go
new file mode 100644
index 000000000..06ab7d0f9
--- /dev/null
+++ b/builtin/bins/provider-dnsimple/main_test.go
@@ -0,0 +1 @@
+package main
diff --git a/builtin/providers/aws/resource_aws_db_security_group.go b/builtin/providers/aws/resource_aws_db_security_group.go
index 72be35b4c..a23f6a49a 100644
--- a/builtin/providers/aws/resource_aws_db_security_group.go
+++ b/builtin/providers/aws/resource_aws_db_security_group.go
@@ -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(
diff --git a/builtin/providers/aws/resource_provider.go b/builtin/providers/aws/resource_provider.go
index dda9cc4c0..92a7b544c 100644
--- a/builtin/providers/aws/resource_provider.go
+++ b/builtin/providers/aws/resource_provider.go
@@ -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)
}
diff --git a/builtin/providers/dnsimple/config.go b/builtin/providers/dnsimple/config.go
new file mode 100644
index 000000000..dfec0f2e1
--- /dev/null
+++ b/builtin/providers/dnsimple/config.go
@@ -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
+}
diff --git a/builtin/providers/dnsimple/resource_dnsimple_record.go b/builtin/providers/dnsimple/resource_dnsimple_record.go
new file mode 100644
index 000000000..881b28a71
--- /dev/null
+++ b/builtin/providers/dnsimple/resource_dnsimple_record.go
@@ -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",
+ },
+ }
+}
diff --git a/builtin/providers/dnsimple/resource_dnsimple_record_test.go b/builtin/providers/dnsimple/resource_dnsimple_record_test.go
new file mode 100644
index 000000000..eb91e7f6d
--- /dev/null
+++ b/builtin/providers/dnsimple/resource_dnsimple_record_test.go
@@ -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
+}`
diff --git a/builtin/providers/dnsimple/resource_provider.go b/builtin/providers/dnsimple/resource_provider.go
new file mode 100644
index 000000000..478acb470
--- /dev/null
+++ b/builtin/providers/dnsimple/resource_provider.go
@@ -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()
+}
diff --git a/builtin/providers/dnsimple/resource_provider_test.go b/builtin/providers/dnsimple/resource_provider_test.go
new file mode 100644
index 000000000..63dd0b067
--- /dev/null
+++ b/builtin/providers/dnsimple/resource_provider_test.go
@@ -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")
+ }
+}
diff --git a/builtin/providers/dnsimple/resources.go b/builtin/providers/dnsimple/resources.go
new file mode 100644
index 000000000..7cbd5db91
--- /dev/null
+++ b/builtin/providers/dnsimple/resources.go
@@ -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,
+ },
+ },
+ }
+}
diff --git a/builtin/providers/heroku/resource_heroku_addon.go b/builtin/providers/heroku/resource_heroku_addon.go
new file mode 100644
index 000000000..0081b8477
--- /dev/null
+++ b/builtin/providers/heroku/resource_heroku_addon.go
@@ -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.*",
+ },
+ }
+}
diff --git a/builtin/providers/heroku/resource_heroku_addon_test.go b/builtin/providers/heroku/resource_heroku_addon_test.go
new file mode 100644
index 000000000..1c099b683
--- /dev/null
+++ b/builtin/providers/heroku/resource_heroku_addon_test.go
@@ -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"
+ }
+}`
diff --git a/builtin/providers/heroku/resource_heroku_app.go b/builtin/providers/heroku/resource_heroku_app.go
index c1f6b728c..5c88cded8 100644
--- a/builtin/providers/heroku/resource_heroku_app.go
+++ b/builtin/providers/heroku/resource_heroku_app.go
@@ -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
+}
diff --git a/builtin/providers/heroku/resource_heroku_app_test.go b/builtin/providers/heroku/resource_heroku_app_test.go
index f6f196ce7..ff7c6f110 100644
--- a/builtin/providers/heroku/resource_heroku_app_test.go
+++ b/builtin/providers/heroku/resource_heroku_app_test.go
@@ -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"
}`
diff --git a/builtin/providers/heroku/resource_heroku_domain.go b/builtin/providers/heroku/resource_heroku_domain.go
new file mode 100644
index 000000000..6f00fdf55
--- /dev/null
+++ b/builtin/providers/heroku/resource_heroku_domain.go
@@ -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{},
+ }
+}
diff --git a/builtin/providers/heroku/resource_heroku_domain_test.go b/builtin/providers/heroku/resource_heroku_domain_test.go
new file mode 100644
index 000000000..315881690
--- /dev/null
+++ b/builtin/providers/heroku/resource_heroku_domain_test.go
@@ -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"
+}`
diff --git a/builtin/providers/heroku/resources.go b/builtin/providers/heroku/resources.go
index 00140d3f7..a208714c6 100644
--- a/builtin/providers/heroku/resources.go
+++ b/builtin/providers/heroku/resources.go
@@ -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,
+ },
},
}
}
diff --git a/command/flag_var.go b/command/flag_var.go
index 41450366f..54bd783a4 100644
--- a/command/flag_var.go
+++ b/command/flag_var.go
@@ -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
diff --git a/config.go b/config.go
index 049bd38de..29a30e66c 100644
--- a/config.go
+++ b/config.go
@@ -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",
diff --git a/config/loader_libucl.go b/config/loader_libucl.go
index 2599a6d16..2471a9b77 100644
--- a/config/loader_libucl.go
+++ b/config/loader_libucl.go
@@ -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.
diff --git a/config/loader_test.go b/config/loader_test.go
index 168f4e931..c154075e6 100644
--- a/config/loader_test.go
+++ b/config/loader_test.go
@@ -485,6 +485,7 @@ do
const basicResourcesStr = `
aws_instance[db] (x1)
+ VPC
security_groups
dependsOn
aws_instance.web
diff --git a/config/test-fixtures/basic.tf b/config/test-fixtures/basic.tf
index a8938c018..fe3267c8a 100644
--- a/config/test-fixtures/basic.tf
+++ b/config/test-fixtures/basic.tf
@@ -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"]
}
diff --git a/config/test-fixtures/basic.tf.json b/config/test-fixtures/basic.tf.json
index 7c43b5b30..6c6ff00bd 100644
--- a/config/test-fixtures/basic.tf.json
+++ b/config/test-fixtures/basic.tf.json
@@ -21,6 +21,7 @@
"aws_instance": {
"db": {
"security_groups": ["${aws_security_group.firewall.*.id}"],
+ "VPC": "foo",
"depends_on": ["aws_instance.web"]
},
diff --git a/website/source/community.html.erb b/website/source/community.html.erb
index ce58ebf27..43775e978 100644
--- a/website/source/community.html.erb
+++ b/website/source/community.html.erb
@@ -1,93 +1,80 @@
---
+layout: "inner"
page_title: "Community"
---
-
-
-
-
Community
+
Community
+
+Terraform is a new project with a growing community. Despite this,
+there are active, dedicated users willing to help you through various
+mediums.
+
+
+IRC: #terraform
on Freenode
+
+
+Mailing list:
+Terraform Google Group
+
+
+Bug Tracker:
+Issue tracker
+ on GitHub. Please only use this for reporting bugs. Do not ask
+for general help here. Use IRC or the mailing list for that.
+
+
People
+
+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.
+
+
+
+
+
+
Mitchell Hashimoto (@mitchellh)
- 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
+ Vagrant,
+ Packer, and
+ Consul.
-
- IRC: #terraform
on Freenode
-
-
- Mailing list:
- Terraform Google Group
-
-
- Bug Tracker:
- Issue tracker
- on GitHub. Please only use this for reporting bugs. Do not ask
- for general help here. Use IRC or the mailing list for that.
-
-
People
-
- 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.
-
-
-
-
-
-
Armon Dadgar (@armon)
-
- 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
- Serf,
- Statsite, and
- Bloomd.
-
-
-
-
-
-
-
Mitchell Hashimoto (@mitchellh)
-
- 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
- Vagrant,
- Packer, and
- Serf.
-
-
-
-
-
-
-
-
-
- Jack Pearkes created and maintains the Terraform web UI.
- He is also a core committer to
- Packer and maintains
- many successful
- open source projects
- while also being an employee of
- HashiCorp.
-
-
-
-
-
-
-
-
-
William Tisäter is a Terraform core committer. He is also maintainer of pygeoip and build things daily at Tictail.
-
-
-
-
-
+
+
+
+
+
Armon Dadgar (@armon)
+
+ 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
+ Consul,
+ Serf,
+ Statsite, and
+ Bloomd.
+
+
+
+
+
+
+
+
+ Jack Pearkes is a creator of Terraform. He created and maintains
+ most of the providers and documentation.
+ He is also a core committer to
+ Packer and
+ Consul
+ while also being an employee of
+ HashiCorp.
+
+
+
+
+
diff --git a/website/source/docs/providers/aws/index.html.markdown b/website/source/docs/providers/aws/index.html.markdown
new file mode 100644
index 000000000..8105d8b83
--- /dev/null
+++ b/website/source/docs/providers/aws/index.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/eip.html.markdown b/website/source/docs/providers/aws/r/eip.html.markdown
new file mode 100644
index 000000000..c70f7b813
--- /dev/null
+++ b/website/source/docs/providers/aws/r/eip.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/instance.html.markdown b/website/source/docs/providers/aws/r/instance.html.markdown
new file mode 100644
index 000000000..e1356ef20
--- /dev/null
+++ b/website/source/docs/providers/aws/r/instance.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/internet_gateway.html.markdown b/website/source/docs/providers/aws/r/internet_gateway.html.markdown
new file mode 100644
index 000000000..e83ec6871
--- /dev/null
+++ b/website/source/docs/providers/aws/r/internet_gateway.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/launch_config.html.markdown b/website/source/docs/providers/aws/r/launch_config.html.markdown
new file mode 100644
index 000000000..4c697d9fa
--- /dev/null
+++ b/website/source/docs/providers/aws/r/launch_config.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/route53_record.html.markdown b/website/source/docs/providers/aws/r/route53_record.html.markdown
new file mode 100644
index 000000000..4a519cf82
--- /dev/null
+++ b/website/source/docs/providers/aws/r/route53_record.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/route53_zone.html.markdown b/website/source/docs/providers/aws/r/route53_zone.html.markdown
new file mode 100644
index 000000000..6be339aaf
--- /dev/null
+++ b/website/source/docs/providers/aws/r/route53_zone.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/route_table.html.markdown b/website/source/docs/providers/aws/r/route_table.html.markdown
new file mode 100644
index 000000000..fdde1d252
--- /dev/null
+++ b/website/source/docs/providers/aws/r/route_table.html.markdown
@@ -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
+
diff --git a/website/source/docs/providers/aws/r/route_table_assoc.html.markdown b/website/source/docs/providers/aws/r/route_table_assoc.html.markdown
new file mode 100644
index 000000000..2c924b7d2
--- /dev/null
+++ b/website/source/docs/providers/aws/r/route_table_assoc.html.markdown
@@ -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
+
diff --git a/website/source/docs/providers/aws/r/s3_bucket.html.markdown b/website/source/docs/providers/aws/r/s3_bucket.html.markdown
new file mode 100644
index 000000000..dcce52738
--- /dev/null
+++ b/website/source/docs/providers/aws/r/s3_bucket.html.markdown
@@ -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
+
diff --git a/website/source/docs/providers/aws/r/security_group.html.markdown b/website/source/docs/providers/aws/r/security_group.html.markdown
new file mode 100644
index 000000000..ea226af4c
--- /dev/null
+++ b/website/source/docs/providers/aws/r/security_group.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/subnet.html.markdown b/website/source/docs/providers/aws/r/subnet.html.markdown
new file mode 100644
index 000000000..358ed720b
--- /dev/null
+++ b/website/source/docs/providers/aws/r/subnet.html.markdown
@@ -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.
+
diff --git a/website/source/docs/providers/aws/r/vpc.html.markdown b/website/source/docs/providers/aws/r/vpc.html.markdown
new file mode 100644
index 000000000..9f16b82d8
--- /dev/null
+++ b/website/source/docs/providers/aws/r/vpc.html.markdown
@@ -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
+
diff --git a/website/source/docs/providers/index.html.markdown b/website/source/docs/providers/index.html.markdown
new file mode 100644
index 000000000..fb739259d
--- /dev/null
+++ b/website/source/docs/providers/index.html.markdown
@@ -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.
diff --git a/website/source/docs/provisioners/connection.html.markdown b/website/source/docs/provisioners/connection.html.markdown
new file mode 100644
index 000000000..4206ca990
--- /dev/null
+++ b/website/source/docs/provisioners/connection.html.markdown
@@ -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".
+
diff --git a/website/source/docs/provisioners/file.html.markdown b/website/source/docs/provisioners/file.html.markdown
new file mode 100644
index 000000000..d3f8457da
--- /dev/null
+++ b/website/source/docs/provisioners/file.html.markdown
@@ -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.
+
diff --git a/website/source/docs/provisioners/index.html.markdown b/website/source/docs/provisioners/index.html.markdown
new file mode 100644
index 000000000..8fc766a3c
--- /dev/null
+++ b/website/source/docs/provisioners/index.html.markdown
@@ -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.
+
diff --git a/website/source/docs/provisioners/local-exec.html.markdown b/website/source/docs/provisioners/local-exec.html.markdown
new file mode 100644
index 000000000..5ac15c498
--- /dev/null
+++ b/website/source/docs/provisioners/local-exec.html.markdown
@@ -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.
+
diff --git a/website/source/docs/provisioners/remote-exec.html.markdown b/website/source/docs/provisioners/remote-exec.html.markdown
new file mode 100644
index 000000000..0b6123115
--- /dev/null
+++ b/website/source/docs/provisioners/remote-exec.html.markdown
@@ -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`.
+
diff --git a/website/source/intro/getting-started/agent.html.markdown b/website/source/intro/getting-started/agent.html.markdown
deleted file mode 100644
index 2400432c8..000000000
--- a/website/source/intro/getting-started/agent.html.markdown
+++ /dev/null
@@ -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.
-
-
-Note for OS X Users: 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 -node
flag.
-
-
-## 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.
-
diff --git a/website/source/intro/getting-started/build.html.md b/website/source/intro/getting-started/build.html.md
new file mode 100644
index 000000000..e0a19274b
--- /dev/null
+++ b/website/source/intro/getting-started/build.html.md
@@ -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.
+
+
+
+Note: If you're not using an account that qualifies
+under the AWS
+free-tier,
+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.
+
+
+
+## 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: "" => "
"
+ instance_type: "" => "t1.micro"
+ key_name: "" => ""
+ private_dns: "" => ""
+ private_ip: "" => ""
+ public_dns: "" => ""
+ public_ip: "" => ""
+ security_groups: "" => ""
+ subnet_id: "" => ""
+```
+
+`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 ``, 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.
diff --git a/website/source/intro/getting-started/change.html.md b/website/source/intro/getting-started/change.html.md
new file mode 100644
index 000000000..d9a4243ea
--- /dev/null
+++ b/website/source/intro/getting-started/change.html.md
@@ -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" => ""
+ 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: "" => ""
+ subnet_id: "" => ""
+```
+
+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.
diff --git a/website/source/intro/getting-started/checks.html.markdown b/website/source/intro/getting-started/checks.html.markdown
deleted file mode 100644
index 81b2fddd0..000000000
--- a/website/source/intro/getting-started/checks.html.markdown
+++ /dev/null
@@ -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.
-
diff --git a/website/source/intro/getting-started/destroy.html.md b/website/source/intro/getting-started/destroy.html.md
new file mode 100644
index 000000000..1f4ff9a70
--- /dev/null
+++ b/website/source/intro/getting-started/destroy.html.md
@@ -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.
diff --git a/website/source/intro/getting-started/install.html.markdown b/website/source/intro/getting-started/install.html.markdown
index cfd271daf..e5ff20512 100644
--- a/website/source/intro/getting-started/install.html.markdown
+++ b/website/source/intro/getting-started/install.html.markdown
@@ -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] []
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
diff --git a/website/source/intro/getting-started/join.html.markdown b/website/source/intro/getting-started/join.html.markdown
deleted file mode 100644
index fc710f689..000000000
--- a/website/source/intro/getting-started/join.html.markdown
+++ /dev/null
@@ -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
-```
-
-
-
Remember: To join a cluster, a Terraform agent needs to only
-learn about one existing member. After joining the cluster, the
-agents gossip with each other to propagate full membership information.
-
-
-
-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).
diff --git a/website/source/intro/getting-started/kv.html.markdown b/website/source/intro/getting-started/kv.html.markdown
deleted file mode 100644
index dd424e737..000000000
--- a/website/source/intro/getting-started/kv.html.markdown
+++ /dev/null
@@ -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).
-
diff --git a/website/source/intro/getting-started/services.html.markdown b/website/source/intro/getting-started/services.html.markdown
deleted file mode 100644
index dfd070863..000000000
--- a/website/source/intro/getting-started/services.html.markdown
+++ /dev/null
@@ -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.
-
diff --git a/website/source/intro/getting-started/ui.html.markdown b/website/source/intro/getting-started/ui.html.markdown
deleted file mode 100644
index ab782fca4..000000000
--- a/website/source/intro/getting-started/ui.html.markdown
+++ /dev/null
@@ -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.
-
-
-
-## 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`.
diff --git a/website/source/intro/index.html.markdown b/website/source/intro/index.html.markdown
index bc47ea037..847ab875d 100644
--- a/website/source/intro/index.html.markdown
+++ b/website/source/intro/index.html.markdown
@@ -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.
diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb
new file mode 100644
index 000000000..eb25b366f
--- /dev/null
+++ b/website/source/layouts/aws.erb
@@ -0,0 +1,70 @@
+<% wrap_layout :inner do %>
+ <% content_for :sidebar do %>
+
+ <% end %>
+
+ <%= yield %>
+ <% end %>
diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb
index c51102838..b0a276c2f 100644
--- a/website/source/layouts/docs.erb
+++ b/website/source/layouts/docs.erb
@@ -4,52 +4,16 @@
+
>
- Terraform Commands (CLI)
+ Commands (CLI)
- >
agent
@@ -85,45 +49,43 @@
- >
- Terraform Agent
-
+ >
+ Internals
+
+
<% end %>
diff --git a/website/source/layouts/intro.erb b/website/source/layouts/intro.erb
index 41c536ffd..664b0e01b 100644
--- a/website/source/layouts/intro.erb
+++ b/website/source/layouts/intro.erb
@@ -4,34 +4,18 @@
diff --git a/website/source/stylesheets/_docs.less b/website/source/stylesheets/_docs.less
index e06e6e8ab..ba689fbdb 100755
--- a/website/source/stylesheets/_docs.less
+++ b/website/source/stylesheets/_docs.less
@@ -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]{
diff --git a/website/source/stylesheets/main.css b/website/source/stylesheets/main.css
index dd5cc39ea..e54cd4388 100644
--- a/website/source/stylesheets/main.css
+++ b/website/source/stylesheets/main.css
@@ -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;
}
diff --git a/website/source/stylesheets/main.less b/website/source/stylesheets/main.less
index f542f49af..98e699d7a 100755
--- a/website/source/stylesheets/main.less
+++ b/website/source/stylesheets/main.less
@@ -26,4 +26,4 @@
// Components w/ JavaScript
/*@import "modals.less";*/
-// 68
+// 69