Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data-driven Terraform Configuration #6598

Merged
merged 22 commits into from May 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fc4fa10
config: "ResourceMode" concept for resources
apparentlymart May 1, 2016
8601400
config: Data source loading
apparentlymart Jan 17, 2016
718cdda
config: Parsing of data.TYPE.NAME.FIELD variables
apparentlymart May 1, 2016
0e0e3d7
core: New ResourceProvider methods for data resources
apparentlymart May 8, 2016
6a468dc
helper/schema: Resource can be writable or not
apparentlymart Apr 17, 2016
fb262d0
helper/schema: shim for making data sources act like resources
apparentlymart Apr 17, 2016
3eb4a89
provider/terraform: remote state resource becomes a data source
apparentlymart May 1, 2016
6cd22a4
helper/schema: emit warning when using data source resource shim
apparentlymart May 8, 2016
64f2651
website: Initial documentation about data sources
apparentlymart May 1, 2016
844e1ab
core: ResourceStateKey understands resource modes
apparentlymart May 2, 2016
c1315b3
core: EvalValidate calls appropriate validator for resource mode
apparentlymart May 2, 2016
1da560b
core: Separate resource lifecycle for data vs. managed resources
apparentlymart May 2, 2016
3605447
core: lifecycle for data resources
apparentlymart May 8, 2016
4d50f22
core: separate lifecycle for data resource "orphans"
apparentlymart May 8, 2016
afc7ec5
core: Destroy data resources with "terraform destroy"
apparentlymart May 8, 2016
61ab8bf
core: ResourceAddress supports data resources
apparentlymart May 8, 2016
60c24e3
command: Prevent data resources from being tainted
apparentlymart May 8, 2016
5d27a5b
command: Show id only when refreshing managed resources
apparentlymart May 8, 2016
bfee4b0
command: don't show old values for create diffs in plan
apparentlymart May 8, 2016
2ca10ad
command: Show data source reads differently in plans
apparentlymart May 8, 2016
f95dccf
provider/null: null_data_source data source
apparentlymart May 8, 2016
453fc50
core: Tolerate missing resource variables during input walk
apparentlymart May 14, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
54 changes: 54 additions & 0 deletions builtin/providers/null/data_source.go
@@ -0,0 +1,54 @@
package null

import (
"fmt"
"math/rand"
"time"

"github.com/hashicorp/terraform/helper/schema"
)

func init() {
rand.Seed(time.Now().Unix())
}

func dataSource() *schema.Resource {
return &schema.Resource{
Read: dataSourceRead,

Schema: map[string]*schema.Schema{
"inputs": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
},
"outputs": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
},
"random": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"has_computed_default": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
},
},
}
}

func dataSourceRead(d *schema.ResourceData, meta interface{}) error {

inputs := d.Get("inputs")
d.Set("outputs", inputs)

d.Set("random", fmt.Sprintf("%d", rand.Int()))
if d.Get("has_computed_default") == "" {
d.Set("has_computed_default", "default")
}

d.SetId("static")

return nil
}
4 changes: 4 additions & 0 deletions builtin/providers/null/provider.go
Expand Up @@ -13,5 +13,9 @@ func Provider() terraform.ResourceProvider {
ResourcesMap: map[string]*schema.Resource{
"null_resource": resource(),
},

DataSourcesMap: map[string]*schema.Resource{
"null_data_source": dataSource(),
},
}
}
Expand Up @@ -8,23 +8,19 @@ import (
"github.com/hashicorp/terraform/state/remote"
)

func resourceRemoteState() *schema.Resource {
func dataSourceRemoteState() *schema.Resource {
return &schema.Resource{
Create: resourceRemoteStateCreate,
Read: resourceRemoteStateRead,
Delete: resourceRemoteStateDelete,
Read: dataSourceRemoteStateRead,

Schema: map[string]*schema.Schema{
"backend": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"config": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
},

"output": &schema.Schema{
Expand All @@ -35,11 +31,7 @@ func resourceRemoteState() *schema.Resource {
}
}

func resourceRemoteStateCreate(d *schema.ResourceData, meta interface{}) error {
return resourceRemoteStateRead(d, meta)
}

func resourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
func dataSourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
backend := d.Get("backend").(string)
config := make(map[string]string)
for k, v := range d.Get("config").(map[string]interface{}) {
Expand Down Expand Up @@ -69,8 +61,3 @@ func resourceRemoteStateRead(d *schema.ResourceData, meta interface{}) error {
d.Set("output", outputs)
return nil
}

func resourceRemoteStateDelete(d *schema.ResourceData, meta interface{}) error {
d.SetId("")
return nil
}
8 changes: 7 additions & 1 deletion builtin/providers/terraform/provider.go
Expand Up @@ -9,7 +9,13 @@ import (
func Provider() terraform.ResourceProvider {
return &schema.Provider{
ResourcesMap: map[string]*schema.Resource{
"terraform_remote_state": resourceRemoteState(),
"terraform_remote_state": schema.DataSourceResourceShim(
"terraform_remote_state",
dataSourceRemoteState(),
),
},
DataSourcesMap: map[string]*schema.Resource{
"terraform_remote_state": dataSourceRemoteState(),
},
}
}
36 changes: 29 additions & 7 deletions command/format_plan.go
Expand Up @@ -87,13 +87,26 @@ func formatPlanModuleExpand(
// resource header.
color := "yellow"
symbol := "~"
oldValues := true
switch rdiff.ChangeType() {
case terraform.DiffDestroyCreate:
color = "green"
symbol = "-/+"
case terraform.DiffCreate:
color = "green"
symbol = "+"
oldValues = false

// If we're "creating" a data resource then we'll present it
// to the user as a "read" operation, so it's clear that this
// operation won't change anything outside of the Terraform state.
// Unfortunately by the time we get here we only have the name
// to work with, so we need to cheat and exploit knowledge of the
// naming scheme for data resources.
if strings.HasPrefix(name, "data.") {
symbol = "<="
color = "cyan"
}
case terraform.DiffDestroy:
color = "red"
symbol = "-"
Expand Down Expand Up @@ -134,13 +147,22 @@ func formatPlanModuleExpand(
newResource = opts.Color.Color(" [red](forces new resource)")
}

buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
attrDiff.Old,
v,
newResource))
if oldValues {
buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
attrDiff.Old,
v,
newResource))
} else {
buf.WriteString(fmt.Sprintf(
" %s:%s %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
v,
newResource))
}
}

// Write the reset color so we don't overload the user's terminal
Expand Down
12 changes: 10 additions & 2 deletions command/hook_ui.go
Expand Up @@ -245,9 +245,17 @@ func (h *UiHook) PreRefresh(
h.once.Do(h.init)

id := n.HumanId()

var stateIdSuffix string
// Data resources refresh before they have ids, whereas managed
// resources are only refreshed when they have ids.
if s.ID != "" {
stateIdSuffix = fmt.Sprintf(" (ID: %s)", s.ID)
}

h.ui.Output(h.Colorize.Color(fmt.Sprintf(
"[reset][bold]%s: Refreshing state... (ID: %s)",
id, s.ID)))
"[reset][bold]%s: Refreshing state...%s",
id, stateIdSuffix)))
return terraform.HookActionContinue, nil
}

Expand Down
4 changes: 2 additions & 2 deletions command/plan.go
Expand Up @@ -219,7 +219,7 @@ The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
Expand All @@ -230,7 +230,7 @@ The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.
will be destroyed. Cyan entries are data sources to be read.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
Expand Down
13 changes: 13 additions & 0 deletions command/taint.go
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform/terraform"
)

// TaintCommand is a cli.Command implementation that manually taints
Expand Down Expand Up @@ -43,6 +45,17 @@ func (c *TaintCommand) Run(args []string) int {
module = "root." + module
}

rsk, err := terraform.ParseResourceStateKey(name)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err))
return 1
}

if !rsk.Mode.Taintable() {
c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name))
return 1
}

// Get the state that we'll be modifying
state, err := c.State()
if err != nil {
Expand Down
32 changes: 27 additions & 5 deletions config/config.go
Expand Up @@ -67,9 +67,11 @@ type ProviderConfig struct {
}

// A resource represents a single Terraform resource in the configuration.
// A Terraform resource is something that represents some component that
// can be created and managed, and has some properties associated with it.
// A Terraform resource is something that supports some or all of the
// usual "create, read, update, delete" operations, depending on
// the given Mode.
type Resource struct {
Mode ResourceMode // which operations the resource supports
Name string
Type string
RawCount *RawConfig
Expand All @@ -85,6 +87,7 @@ type Resource struct {
// interpolation.
func (r *Resource) Copy() *Resource {
n := &Resource{
Mode: r.Mode,
Name: r.Name,
Type: r.Type,
RawCount: r.RawCount.Copy(),
Expand Down Expand Up @@ -210,7 +213,14 @@ func (r *Resource) Count() (int, error) {

// A unique identifier for this resource.
func (r *Resource) Id() string {
return fmt.Sprintf("%s.%s", r.Type, r.Name)
switch r.Mode {
case ManagedResourceMode:
return fmt.Sprintf("%s.%s", r.Type, r.Name)
case DataResourceMode:
return fmt.Sprintf("data.%s.%s", r.Type, r.Name)
default:
panic(fmt.Errorf("unknown resource mode %s", r.Mode))
}
}

// Validate does some basic semantic checking of the configuration.
Expand Down Expand Up @@ -558,7 +568,7 @@ func (c *Config) Validate() error {
continue
}

id := fmt.Sprintf("%s.%s", rv.Type, rv.Name)
id := rv.ResourceId()
if _, ok := resources[id]; !ok {
errs = append(errs, fmt.Errorf(
"%s: unknown resource '%s' referenced in variable %s",
Expand Down Expand Up @@ -804,13 +814,14 @@ func (c *ProviderConfig) mergerMerge(m merger) merger {
}

func (r *Resource) mergerName() string {
return fmt.Sprintf("%s.%s", r.Type, r.Name)
return r.Id()
}

func (r *Resource) mergerMerge(m merger) merger {
r2 := m.(*Resource)

result := *r
result.Mode = r2.Mode
result.Name = r2.Name
result.Type = r2.Type
result.RawConfig = result.RawConfig.merge(r2.RawConfig)
Expand Down Expand Up @@ -927,3 +938,14 @@ func (v *Variable) inferTypeFromDefault() VariableType {

return VariableTypeUnknown
}

func (m ResourceMode) Taintable() bool {
switch m {
case ManagedResourceMode:
return true
case DataResourceMode:
return false
default:
panic(fmt.Errorf("unsupported ResourceMode value %s", m))
}
}
7 changes: 3 additions & 4 deletions config/config_string.go
Expand Up @@ -178,7 +178,7 @@ func resourcesStr(rs []*Resource) string {
ks := make([]string, 0, len(rs))
mapping := make(map[string]int)
for i, r := range rs {
k := fmt.Sprintf("%s[%s]", r.Type, r.Name)
k := r.Id()
ks = append(ks, k)
mapping[k] = i
}
Expand All @@ -190,9 +190,8 @@ func resourcesStr(rs []*Resource) string {
for _, i := range order {
r := rs[i]
result += fmt.Sprintf(
"%s[%s] (x%s)\n",
r.Type,
r.Name,
"%s (x%s)\n",
r.Id(),
r.RawCount.Value())

ks := make([]string, 0, len(r.RawConfig.Raw))
Expand Down