Merge pull request #201 from hashicorp/f-resource
High-Level Framework for Writing Providers and Resources
This commit is contained in:
commit
839f9d84c8
|
@ -6,5 +6,5 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(new(heroku.ResourceProvider))
|
||||
plugin.Serve(new(heroku.Provider()))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// Provider returns a terraform.ResourceProvider.
|
||||
func Provider() *schema.Provider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"email": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
|
||||
"api_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"heroku_app": resourceHerokuApp(),
|
||||
"heroku_addon": resourceHerokuAddon(),
|
||||
"heroku_domain": resourceHerokuDomain(),
|
||||
"heroku_drain": resourceHerokuDrain(),
|
||||
},
|
||||
|
||||
ConfigureFunc: providerConfigure,
|
||||
}
|
||||
}
|
||||
|
||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
var config Config
|
||||
configRaw := d.Get("").(map[string]interface{})
|
||||
if err := mapstructure.Decode(configRaw, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Println("[INFO] Initializing Heroku client")
|
||||
return config.Client()
|
||||
}
|
|
@ -2,29 +2,35 @@ package heroku
|
|||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
var testAccProviders map[string]terraform.ResourceProvider
|
||||
var testAccProvider *ResourceProvider
|
||||
var testAccProvider *schema.Provider
|
||||
|
||||
func init() {
|
||||
testAccProvider = new(ResourceProvider)
|
||||
testAccProvider = Provider()
|
||||
testAccProviders = map[string]terraform.ResourceProvider{
|
||||
"heroku": testAccProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = new(ResourceProvider)
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_Configure(t *testing.T) {
|
||||
rp := new(ResourceProvider)
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = Provider()
|
||||
}
|
||||
|
||||
func TestProviderConfigure(t *testing.T) {
|
||||
var expectedKey string
|
||||
var expectedEmail string
|
||||
|
||||
|
@ -50,18 +56,18 @@ func TestResourceProvider_Configure(t *testing.T) {
|
|||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
rp := Provider()
|
||||
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := Config{
|
||||
APIKey: expectedKey,
|
||||
Email: expectedEmail,
|
||||
config := rp.Meta().(*heroku.Client)
|
||||
if config.Username != expectedEmail {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rp.Config, expected) {
|
||||
t.Fatalf("bad: %#v", rp.Config)
|
||||
if config.Password != expectedKey {
|
||||
t.Fatalf("bad: %#v", config)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"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/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -17,163 +15,133 @@ import (
|
|||
// multiple addons simultaneously.
|
||||
var addonLock sync.Mutex
|
||||
|
||||
func resource_heroku_addon_create(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
func resourceHerokuAddon() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuAddonCreate,
|
||||
Read: resourceHerokuAddonRead,
|
||||
Update: resourceHerokuAddonUpdate,
|
||||
Delete: resourceHerokuAddonDelete,
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"app": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"plan": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
|
||||
"config": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
},
|
||||
},
|
||||
|
||||
"provider_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"config_vars": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{Type: schema.TypeMap},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuAddonCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
addonLock.Lock()
|
||||
defer addonLock.Unlock()
|
||||
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
client := meta.(*heroku.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"]
|
||||
app := d.Get("app").(string)
|
||||
plan := d.Get("plan").(string)
|
||||
opts := heroku.AddonCreateOpts{}
|
||||
|
||||
if attr, ok := rs.Attributes["config.#"]; ok && attr == "1" {
|
||||
vs := flatmap.Expand(
|
||||
rs.Attributes, "config").([]interface{})
|
||||
|
||||
if v := d.Get("config"); v != nil {
|
||||
config := make(map[string]string)
|
||||
for k, v := range vs[0].(map[string]interface{}) {
|
||||
config[k] = v.(string)
|
||||
for _, v := range v.([]interface{}) {
|
||||
for k, v := range v.(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
|
||||
return err
|
||||
}
|
||||
|
||||
rs.ID = a.Id
|
||||
log.Printf("[INFO] Addon ID: %s", rs.ID)
|
||||
d.SetId(a.Id)
|
||||
log.Printf("[INFO] Addon ID: %s", d.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)
|
||||
return resourceHerokuAddonRead(d, meta)
|
||||
}
|
||||
|
||||
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)
|
||||
func resourceHerokuAddonRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
addon, err := resource_heroku_addon_retrieve(
|
||||
d.Get("app").(string), d.Id(), client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
return 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)
|
||||
}
|
||||
d.Set("name", addon.Name)
|
||||
d.Set("plan", addon.Plan.Name)
|
||||
d.Set("provider_id", addon.ProviderId)
|
||||
d.Set("config_vars", []interface{}{addon.ConfigVars})
|
||||
d.SetDependencies([]terraform.ResourceDependency{
|
||||
terraform.ResourceDependency{ID: d.Get("app").(string)},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuAddonUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
app, err := resource_heroku_addon_retrieve(s.Attributes["app"], s.ID, client)
|
||||
app := d.Get("app").(string)
|
||||
|
||||
if d.HasChange("plan") {
|
||||
ad, err := client.AddonUpdate(
|
||||
app, d.Id(), d.Get("plan").(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the new ID
|
||||
d.SetId(ad.Id)
|
||||
}
|
||||
|
||||
return resourceHerokuAddonRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceHerokuAddonDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
log.Printf("[INFO] Deleting Addon: %s", d.Id())
|
||||
|
||||
// Destroy the app
|
||||
err := client.AddonDelete(d.Get("app").(string), d.Id())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("Error deleting addon: %s", 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
|
||||
}
|
||||
|
||||
s.Dependencies = []terraform.ResourceDependency{
|
||||
terraform.ResourceDependency{ID: s.Attributes["app"]},
|
||||
}
|
||||
|
||||
return s, nil
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_retrieve(app string, id string, client *heroku.Client) (*heroku.Addon, error) {
|
||||
|
@ -185,15 +153,3 @@ func resource_heroku_addon_retrieve(app string, id string, client *heroku.Client
|
|||
|
||||
return addon, nil
|
||||
}
|
||||
|
||||
func resource_heroku_addon_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{
|
||||
"app",
|
||||
"plan",
|
||||
},
|
||||
Optional: []string{
|
||||
"config.*",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestAccHerokuAddon_Basic(t *testing.T) {
|
|||
}
|
||||
|
||||
func testAccCheckHerokuAddonDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_addon" {
|
||||
|
@ -75,7 +75,7 @@ func testAccCheckHerokuAddonExists(n string, addon *heroku.Addon) resource.TestC
|
|||
return fmt.Errorf("No Addon ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
foundAddon, err := client.AddonInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
|
|
|
@ -5,11 +5,8 @@ import (
|
|||
"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/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
// type application is used to store all the details of a heroku app
|
||||
|
@ -43,200 +40,167 @@ func (a *application) Update() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_create(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuApp() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuAppCreate,
|
||||
Read: resourceHerokuAppRead,
|
||||
Update: resourceHerokuAppUpdate,
|
||||
Delete: resourceHerokuAppDelete,
|
||||
|
||||
// Merge the diff into the state so that we have all the attributes
|
||||
// properly.
|
||||
rs := s.MergeDiff(d)
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"region": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"stack": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"config_vars": &schema.Schema{
|
||||
Type: schema.TypeList,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Elem: &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
},
|
||||
},
|
||||
|
||||
"git_url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"web_url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
|
||||
"heroku_hostname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuAppCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
// Build up our creation options
|
||||
opts := heroku.AppCreateOpts{}
|
||||
|
||||
if attr := rs.Attributes["name"]; attr != "" {
|
||||
opts.Name = &attr
|
||||
if v := d.Get("name"); v != nil {
|
||||
vs := v.(string)
|
||||
log.Printf("[DEBUG] App name: %s", vs)
|
||||
opts.Name = &vs
|
||||
}
|
||||
if v := d.Get("region"); v != nil {
|
||||
vs := v.(string)
|
||||
log.Printf("[DEBUG] App region: %s", vs)
|
||||
opts.Region = &vs
|
||||
}
|
||||
if v := d.Get("stack"); v != nil {
|
||||
vs := v.(string)
|
||||
log.Printf("[DEBUG] App stack: %s", vs)
|
||||
opts.Stack = &vs
|
||||
}
|
||||
|
||||
if attr := rs.Attributes["region"]; attr != "" {
|
||||
opts.Region = &attr
|
||||
}
|
||||
|
||||
if attr := rs.Attributes["stack"]; attr != "" {
|
||||
opts.Stack = &attr
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] App create configuration: %#v", opts)
|
||||
|
||||
log.Printf("[DEBUG] Creating Heroku app...")
|
||||
a, err := client.AppCreate(&opts)
|
||||
if err != nil {
|
||||
return s, err
|
||||
return err
|
||||
}
|
||||
|
||||
rs.ID = a.Name
|
||||
log.Printf("[INFO] App ID: %s", rs.ID)
|
||||
d.SetId(a.Name)
|
||||
log.Printf("[INFO] App ID: %s", d.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 v := d.Get("config_vars"); v != nil {
|
||||
err = update_config_vars(d.Id(), v.([]interface{}), client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
app, err := resource_heroku_app_retrieve(rs.ID, client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
}
|
||||
|
||||
return resource_heroku_app_update_state(rs, app)
|
||||
return resourceHerokuAppRead(d, meta)
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if attr, ok := d.Attributes["name"]; ok {
|
||||
opts := heroku.AppUpdateOpts{
|
||||
Name: &attr.New,
|
||||
}
|
||||
|
||||
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)
|
||||
func resourceHerokuAppRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
app, err := resource_heroku_app_retrieve(d.Id(), client)
|
||||
if err != nil {
|
||||
return rs, err
|
||||
return err
|
||||
}
|
||||
|
||||
return resource_heroku_app_update_state(rs, app)
|
||||
}
|
||||
d.Set("name", app.App.Name)
|
||||
d.Set("stack", app.App.Stack.Name)
|
||||
d.Set("region", app.App.Region.Name)
|
||||
d.Set("git_url", app.App.GitURL)
|
||||
d.Set("web_url", app.App.WebURL)
|
||||
d.Set("config_vars", []map[string]string{app.Vars})
|
||||
|
||||
func resource_heroku_app_destroy(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) error {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
log.Printf("[INFO] Deleting App: %s", s.ID)
|
||||
|
||||
// Destroy the app
|
||||
err := client.AppDelete(s.ID)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting App: %s", err)
|
||||
}
|
||||
// We know that the hostname on heroku will be the name+herokuapp.com
|
||||
// You need this to do things like create DNS CNAME records
|
||||
d.Set("heroku_hostname", fmt.Sprintf("%s.herokuapp.com", app.App.Name))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuAppUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
app, err := resource_heroku_app_retrieve(s.ID, client)
|
||||
// If name changed, update it
|
||||
if d.HasChange("name") {
|
||||
v := d.Get("name").(string)
|
||||
opts := heroku.AppUpdateOpts{
|
||||
Name: &v,
|
||||
}
|
||||
|
||||
renamedApp, err := client.AppUpdate(d.Id(), &opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store the new ID
|
||||
d.SetId(renamedApp.Name)
|
||||
}
|
||||
|
||||
// Get the config vars. If we have none, then set it to the empty
|
||||
// list so that they're properly removed.
|
||||
v := d.Get("config_vars")
|
||||
if v == nil {
|
||||
v = []interface{}{}
|
||||
}
|
||||
|
||||
err := update_config_vars(d.Id(), v.([]interface{}), client)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
return resource_heroku_app_update_state(s, app)
|
||||
return resourceHerokuAppRead(d, meta)
|
||||
}
|
||||
|
||||
func resource_heroku_app_diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||
func resourceHerokuAppDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
b := &diff.ResourceBuilder{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"name": diff.AttrTypeUpdate,
|
||||
"region": diff.AttrTypeUpdate,
|
||||
"stack": diff.AttrTypeCreate,
|
||||
"config_vars": diff.AttrTypeUpdate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"name",
|
||||
"region",
|
||||
"stack",
|
||||
"git_url",
|
||||
"web_url",
|
||||
"id",
|
||||
"config_vars",
|
||||
},
|
||||
|
||||
ComputedAttrsUpdate: []string{
|
||||
"heroku_hostname",
|
||||
},
|
||||
log.Printf("[INFO] Deleting App: %s", d.Id())
|
||||
err := client.AppDelete(d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting App: %s", err)
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
}
|
||||
|
||||
func resource_heroku_app_update_state(
|
||||
s *terraform.ResourceState,
|
||||
app *application) (*terraform.ResourceState, error) {
|
||||
|
||||
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
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_retrieve(id string, client *heroku.Client) (*application, error) {
|
||||
|
@ -251,18 +215,6 @@ func resource_heroku_app_retrieve(id string, client *heroku.Client) (*applicatio
|
|||
return &app, nil
|
||||
}
|
||||
|
||||
func resource_heroku_app_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{},
|
||||
Optional: []string{
|
||||
"name",
|
||||
"region",
|
||||
"stack",
|
||||
"config_vars.*",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func retrieve_config_vars(id string, client *heroku.Client) (map[string]string, error) {
|
||||
vars, err := client.ConfigVarInfo(id)
|
||||
|
||||
|
@ -273,21 +225,19 @@ func retrieve_config_vars(id string, client *heroku.Client) (map[string]string,
|
|||
return vars, nil
|
||||
}
|
||||
|
||||
// Updates the config vars for from an expanded (prior to assertion)
|
||||
// []map[string]string config
|
||||
// Updates the config vars for from an expanded configuration.
|
||||
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
|
||||
for _, v := range vs {
|
||||
for k, v := range v.(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 {
|
||||
if _, err := client.ConfigVarUpdate(id, vars); err != nil {
|
||||
return fmt.Errorf("Error updating config vars: %s", err)
|
||||
}
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ func TestAccHerokuApp_NukeVars(t *testing.T) {
|
|||
}
|
||||
|
||||
func testAccCheckHerokuAppDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_app" {
|
||||
|
@ -122,7 +122,7 @@ func testAccCheckHerokuAppDestroy(s *terraform.State) error {
|
|||
|
||||
func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
if app.Region.Name != "us" {
|
||||
return fmt.Errorf("Bad region: %s", app.Region.Name)
|
||||
|
@ -151,7 +151,7 @@ func testAccCheckHerokuAppAttributes(app *heroku.App) resource.TestCheckFunc {
|
|||
|
||||
func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
if app.Name != "terraform-test-renamed" {
|
||||
return fmt.Errorf("Bad name: %s", app.Name)
|
||||
|
@ -178,7 +178,7 @@ func testAccCheckHerokuAppAttributesUpdated(app *heroku.App) resource.TestCheckF
|
|||
|
||||
func testAccCheckHerokuAppAttributesNoVars(app *heroku.App) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
if app.Name != "terraform-test-app" {
|
||||
return fmt.Errorf("Bad name: %s", app.Name)
|
||||
|
@ -209,7 +209,7 @@ func testAccCheckHerokuAppExists(n string, app *heroku.App) resource.TestCheckFu
|
|||
return fmt.Errorf("No App Name is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
foundApp, err := client.AppInfo(rs.ID)
|
||||
|
||||
|
|
|
@ -5,63 +5,64 @@ import (
|
|||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resource_heroku_domain_create(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuDomain() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuDomainCreate,
|
||||
Read: resourceHerokuDomainRead,
|
||||
Delete: resourceHerokuDomainDelete,
|
||||
|
||||
// Merge the diff into the state so that we have all the attributes
|
||||
// properly.
|
||||
rs := s.MergeDiff(d)
|
||||
Schema: map[string]*schema.Schema{
|
||||
"hostname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
app := rs.Attributes["app"]
|
||||
hostname := rs.Attributes["hostname"]
|
||||
"app": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"cname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuDomainCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
app := d.Get("app").(string)
|
||||
hostname := d.Get("hostname").(string)
|
||||
|
||||
log.Printf("[DEBUG] Domain create configuration: %#v, %#v", app, hostname)
|
||||
|
||||
do, err := client.DomainCreate(app, hostname)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
return err
|
||||
}
|
||||
|
||||
rs.ID = do.Id
|
||||
rs.Attributes["hostname"] = do.Hostname
|
||||
rs.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", app)
|
||||
d.SetId(do.Id)
|
||||
d.Set("hostname", do.Hostname)
|
||||
d.Set("cname", fmt.Sprintf("%s.herokuapp.com", app))
|
||||
|
||||
log.Printf("[INFO] Domain ID: %s", rs.ID)
|
||||
|
||||
return rs, nil
|
||||
log.Printf("[INFO] Domain ID: %s", d.Id())
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_update(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
func resourceHerokuDomainDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
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)
|
||||
log.Printf("[INFO] Deleting Domain: %s", d.Id())
|
||||
|
||||
// Destroy the domain
|
||||
err := client.DomainDelete(d.Get("app").(string), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting domain: %s", err)
|
||||
}
|
||||
|
@ -69,58 +70,17 @@ func resource_heroku_domain_destroy(
|
|||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_domain_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuDomainRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
domain, err := resource_heroku_domain_retrieve(s.Attributes["app"], s.ID, client)
|
||||
app := d.Get("app").(string)
|
||||
do, err := client.DomainInfo(app, d.Id())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("Error retrieving domain: %s", err)
|
||||
}
|
||||
|
||||
s.Attributes["hostname"] = domain.Hostname
|
||||
s.Attributes["cname"] = fmt.Sprintf("%s.herokuapp.com", s.Attributes["app"])
|
||||
d.Set("hostname", do.Hostname)
|
||||
d.Set("cname", fmt.Sprintf("%s.herokuapp.com", 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{},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ func TestAccHerokuDomain_Basic(t *testing.T) {
|
|||
}
|
||||
|
||||
func testAccCheckHerokuDomainDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_domain" {
|
||||
|
@ -75,7 +75,7 @@ func testAccCheckHerokuDomainExists(n string, Domain *heroku.Domain) resource.Te
|
|||
return fmt.Errorf("No Domain ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
foundDomain, err := client.DomainInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
|
|
|
@ -5,63 +5,64 @@ import (
|
|||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/helper/diff"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func resource_heroku_drain_create(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuDrain() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceHerokuDrainCreate,
|
||||
Read: resourceHerokuDrainRead,
|
||||
Delete: resourceHerokuDrainDelete,
|
||||
|
||||
// Merge the diff into the state so that we have all the attributes
|
||||
// properly.
|
||||
rs := s.MergeDiff(d)
|
||||
Schema: map[string]*schema.Schema{
|
||||
"url": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
app := rs.Attributes["app"]
|
||||
url := rs.Attributes["url"]
|
||||
"app": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"token": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceHerokuDrainCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
app := d.Get("app").(string)
|
||||
url := d.Get("url").(string)
|
||||
|
||||
log.Printf("[DEBUG] Drain create configuration: %#v, %#v", app, url)
|
||||
|
||||
dr, err := client.LogDrainCreate(app, url)
|
||||
|
||||
if err != nil {
|
||||
return s, err
|
||||
return err
|
||||
}
|
||||
|
||||
rs.ID = dr.Id
|
||||
rs.Attributes["url"] = dr.URL
|
||||
rs.Attributes["token"] = dr.Token
|
||||
d.SetId(dr.Id)
|
||||
d.Set("url", dr.URL)
|
||||
d.Set("token", dr.Token)
|
||||
|
||||
log.Printf("[INFO] Drain ID: %s", rs.ID)
|
||||
|
||||
return rs, nil
|
||||
log.Printf("[INFO] Drain ID: %s", d.Id())
|
||||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_drain_update(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
func resourceHerokuDrainDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
panic("Cannot update drain")
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func resource_heroku_drain_destroy(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) error {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
|
||||
log.Printf("[INFO] Deleting drain: %s", s.ID)
|
||||
|
||||
// Destroy the app
|
||||
err := client.LogDrainDelete(s.Attributes["app"], s.ID)
|
||||
log.Printf("[INFO] Deleting drain: %s", d.Id())
|
||||
|
||||
// Destroy the drain
|
||||
err := client.LogDrainDelete(d.Get("app").(string), d.Id())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error deleting drain: %s", err)
|
||||
}
|
||||
|
@ -69,58 +70,16 @@ func resource_heroku_drain_destroy(
|
|||
return nil
|
||||
}
|
||||
|
||||
func resource_heroku_drain_refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
p := meta.(*ResourceProvider)
|
||||
client := p.client
|
||||
func resourceHerokuDrainRead(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*heroku.Client)
|
||||
|
||||
drain, err := resource_heroku_drain_retrieve(s.Attributes["app"], s.ID, client)
|
||||
dr, err := client.LogDrainInfo(d.Get("app").(string), d.Id())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("Error retrieving drain: %s", err)
|
||||
}
|
||||
|
||||
s.Attributes["url"] = drain.URL
|
||||
s.Attributes["token"] = drain.Token
|
||||
d.Set("url", dr.URL)
|
||||
d.Set("token", dr.Token)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func resource_heroku_drain_diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig,
|
||||
meta interface{}) (*terraform.ResourceDiff, error) {
|
||||
|
||||
b := &diff.ResourceBuilder{
|
||||
Attrs: map[string]diff.AttrType{
|
||||
"url": diff.AttrTypeCreate,
|
||||
"app": diff.AttrTypeCreate,
|
||||
},
|
||||
|
||||
ComputedAttrs: []string{
|
||||
"token",
|
||||
},
|
||||
}
|
||||
|
||||
return b.Diff(s, c)
|
||||
}
|
||||
|
||||
func resource_heroku_drain_retrieve(app string, id string, client *heroku.Client) (*heroku.LogDrain, error) {
|
||||
drain, err := client.LogDrainInfo(app, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving drain: %s", err)
|
||||
}
|
||||
|
||||
return drain, nil
|
||||
}
|
||||
|
||||
func resource_heroku_drain_validation() *config.Validator {
|
||||
return &config.Validator{
|
||||
Required: []string{
|
||||
"url",
|
||||
"app",
|
||||
},
|
||||
Optional: []string{},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func TestAccHerokuDrain_Basic(t *testing.T) {
|
|||
}
|
||||
|
||||
func testAccCheckHerokuDrainDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
for _, rs := range s.Resources {
|
||||
if rs.Type != "heroku_drain" {
|
||||
|
@ -77,7 +77,7 @@ func testAccCheckHerokuDrainExists(n string, Drain *heroku.LogDrain) resource.Te
|
|||
return fmt.Errorf("No Drain ID is set")
|
||||
}
|
||||
|
||||
client := testAccProvider.client
|
||||
client := testAccProvider.Meta().(*heroku.Client)
|
||||
|
||||
foundDrain, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID)
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
package heroku
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/bgentry/heroku-go"
|
||||
"github.com/hashicorp/terraform/helper/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
type ResourceProvider struct {
|
||||
Config Config
|
||||
|
||||
client *heroku.Client
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
v := &config.Validator{
|
||||
Required: []string{
|
||||
"email",
|
||||
"api_key",
|
||||
},
|
||||
}
|
||||
|
||||
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 Heroku 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()
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package heroku
|
||||
|
||||
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{
|
||||
"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,
|
||||
Destroy: resource_heroku_app_destroy,
|
||||
Diff: resource_heroku_app_diff,
|
||||
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,
|
||||
},
|
||||
|
||||
"heroku_drain": resource.Resource{
|
||||
ConfigValidator: resource_heroku_drain_validation(),
|
||||
Create: resource_heroku_drain_create,
|
||||
Destroy: resource_heroku_drain_destroy,
|
||||
Diff: resource_heroku_drain_diff,
|
||||
Refresh: resource_heroku_drain_refresh,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# Terraform Helper Lib: schema
|
||||
|
||||
The `schema` package provides a high-level interface for writing resource
|
||||
providers for Terraform.
|
||||
|
||||
If you're writing a resource provider, we recommend you use this package.
|
||||
|
||||
The interface exposed by this package is much friendlier than trying to
|
||||
write to the Terraform API directly. The core Terraform API is low-level
|
||||
and built for maximum flexibility and control, whereas this library is built
|
||||
as a framework around that to more easily write common providers.
|
|
@ -0,0 +1,160 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// Provider represents a Resource provider in Terraform, and properly
|
||||
// implements all of the ResourceProvider API.
|
||||
//
|
||||
// This is a friendlier API than the core Terraform ResourceProvider API,
|
||||
// and is recommended to be used over that.
|
||||
type Provider struct {
|
||||
Schema map[string]*Schema
|
||||
ResourcesMap map[string]*Resource
|
||||
|
||||
ConfigureFunc ConfigureFunc
|
||||
|
||||
meta interface{}
|
||||
}
|
||||
|
||||
// ConfigureFunc is the function used to configure a Provider.
|
||||
//
|
||||
// The interface{} value returned by this function is stored and passed into
|
||||
// the subsequent resources as the meta parameter.
|
||||
type ConfigureFunc func(*ResourceData) (interface{}, error)
|
||||
|
||||
// InternalValidate should be called to validate the structure
|
||||
// of the provider.
|
||||
//
|
||||
// This should be called in a unit test for any provider to verify
|
||||
// before release that a provider is properly configured for use with
|
||||
// this library.
|
||||
func (p *Provider) InternalValidate() error {
|
||||
if p == nil {
|
||||
return errors.New("provider is nil")
|
||||
}
|
||||
|
||||
if err := schemaMap(p.Schema).InternalValidate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for k, r := range p.ResourcesMap {
|
||||
if err := r.InternalValidate(); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Meta returns the metadata associated with this provider that was
|
||||
// returned by the Configure call. It will be nil until Configure is called.
|
||||
func (p *Provider) Meta() interface{} {
|
||||
return p.meta
|
||||
}
|
||||
|
||||
// Validate validates the provider configuration against the schema.
|
||||
func (p *Provider) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return schemaMap(p.Schema).Validate(c)
|
||||
}
|
||||
|
||||
// ValidateResource validates the resource configuration against the
|
||||
// proper schema.
|
||||
func (p *Provider) ValidateResource(
|
||||
t string, c *terraform.ResourceConfig) ([]string, []error) {
|
||||
r, ok := p.ResourcesMap[t]
|
||||
if !ok {
|
||||
return nil, []error{fmt.Errorf(
|
||||
"Provider doesn't support resource: %s", t)}
|
||||
}
|
||||
|
||||
return r.Validate(c)
|
||||
}
|
||||
|
||||
// Configure implementation of terraform.ResourceProvider interface.
|
||||
func (p *Provider) Configure(c *terraform.ResourceConfig) error {
|
||||
// No configuration
|
||||
if p.ConfigureFunc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sm := schemaMap(p.Schema)
|
||||
|
||||
// Get a ResourceData for this configuration. To do this, we actually
|
||||
// generate an intermediary "diff" although that is never exposed.
|
||||
diff, err := sm.Diff(nil, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := sm.Data(nil, diff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta, err := p.ConfigureFunc(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.meta = meta
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply implementation of terraform.ResourceProvider interface.
|
||||
func (p *Provider) Apply(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff) (*terraform.ResourceState, error) {
|
||||
r, ok := p.ResourcesMap[s.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown resource type: %s", s.Type)
|
||||
}
|
||||
|
||||
return r.Apply(s, d, p.meta)
|
||||
}
|
||||
|
||||
// Diff implementation of terraform.ResourceProvider interface.
|
||||
func (p *Provider) Diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
||||
r, ok := p.ResourcesMap[s.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown resource type: %s", s.Type)
|
||||
}
|
||||
|
||||
return r.Diff(s, c)
|
||||
}
|
||||
|
||||
// Refresh implementation of terraform.ResourceProvider interface.
|
||||
func (p *Provider) Refresh(
|
||||
s *terraform.ResourceState) (*terraform.ResourceState, error) {
|
||||
r, ok := p.ResourcesMap[s.Type]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown resource type: %s", s.Type)
|
||||
}
|
||||
|
||||
return r.Refresh(s, p.meta)
|
||||
}
|
||||
|
||||
// Resources implementation of terraform.ResourceProvider interface.
|
||||
func (p *Provider) Resources() []terraform.ResourceType {
|
||||
keys := make([]string, 0, len(p.ResourcesMap))
|
||||
for k, _ := range p.ResourcesMap {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
result := make([]terraform.ResourceType, 0, len(keys))
|
||||
for _, k := range keys {
|
||||
result = append(result, terraform.ResourceType{
|
||||
Name: k,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = new(Provider)
|
||||
}
|
||||
|
||||
func TestProviderConfigure(t *testing.T) {
|
||||
cases := []struct {
|
||||
P *Provider
|
||||
Config map[string]interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
P: &Provider{},
|
||||
Config: nil,
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
P: &Provider{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
|
||||
ConfigureFunc: func(d *ResourceData) (interface{}, error) {
|
||||
if d.Get("foo").(int) == 42 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("nope")
|
||||
},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"foo": 42,
|
||||
},
|
||||
Err: false,
|
||||
},
|
||||
|
||||
{
|
||||
P: &Provider{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
|
||||
ConfigureFunc: func(d *ResourceData) (interface{}, error) {
|
||||
if d.Get("foo").(int) == 42 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("nope")
|
||||
},
|
||||
},
|
||||
Config: map[string]interface{}{
|
||||
"foo": 52,
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
c, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
err = tc.P.Configure(terraform.NewResourceConfig(c))
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("%d: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderResources(t *testing.T) {
|
||||
cases := []struct {
|
||||
P *Provider
|
||||
Result []terraform.ResourceType
|
||||
}{
|
||||
{
|
||||
P: &Provider{},
|
||||
Result: []terraform.ResourceType{},
|
||||
},
|
||||
|
||||
{
|
||||
P: &Provider{
|
||||
ResourcesMap: map[string]*Resource{
|
||||
"foo": nil,
|
||||
"bar": nil,
|
||||
},
|
||||
},
|
||||
Result: []terraform.ResourceType{
|
||||
terraform.ResourceType{Name: "bar"},
|
||||
terraform.ResourceType{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
actual := tc.P.Resources()
|
||||
if !reflect.DeepEqual(actual, tc.Result) {
|
||||
t.Fatalf("%d: %#v", i, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProviderValidateResource(t *testing.T) {
|
||||
cases := []struct {
|
||||
P *Provider
|
||||
Type string
|
||||
Config map[string]interface{}
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
P: &Provider{},
|
||||
Type: "foo",
|
||||
Config: nil,
|
||||
Err: true,
|
||||
},
|
||||
|
||||
{
|
||||
P: &Provider{
|
||||
ResourcesMap: map[string]*Resource{
|
||||
"foo": &Resource{},
|
||||
},
|
||||
},
|
||||
Type: "foo",
|
||||
Config: nil,
|
||||
Err: false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
c, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
_, es := tc.P.ValidateResource(tc.Type, terraform.NewResourceConfig(c))
|
||||
if (len(es) > 0) != tc.Err {
|
||||
t.Fatalf("%d: %#v", i, es)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// The functions below are the CRUD function types for a Resource.
|
||||
//
|
||||
// The second parameter is the meta value sent to the resource when
|
||||
// different operations are called.
|
||||
type CreateFunc func(*ResourceData, interface{}) error
|
||||
type ReadFunc func(*ResourceData, interface{}) error
|
||||
type UpdateFunc func(*ResourceData, interface{}) error
|
||||
type DeleteFunc func(*ResourceData, interface{}) error
|
||||
|
||||
// Resource represents a thing in Terraform that has a set of configurable
|
||||
// attributes and generally also has a lifecycle (create, read, update,
|
||||
// delete).
|
||||
//
|
||||
// The Resource schema is an abstraction that allows provider writers to
|
||||
// worry only about CRUD operations while off-loading validation, diff
|
||||
// generation, etc. to this higher level library.
|
||||
type Resource struct {
|
||||
Schema map[string]*Schema
|
||||
|
||||
Create CreateFunc
|
||||
Read ReadFunc
|
||||
Update UpdateFunc
|
||||
Delete DeleteFunc
|
||||
}
|
||||
|
||||
// Apply creates, updates, and/or deletes a resource.
|
||||
func (r *Resource) Apply(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
data, err := schemaMap(r.Schema).Data(s, d)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
// The Terraform API dictates that this should never happen, but
|
||||
// it doesn't hurt to be safe in this case.
|
||||
s = new(terraform.ResourceState)
|
||||
}
|
||||
|
||||
if d.Destroy || d.RequiresNew() {
|
||||
if s.ID != "" {
|
||||
// Destroy the resource since it is created
|
||||
if err := r.Delete(data, meta); err != nil {
|
||||
return data.State(), err
|
||||
}
|
||||
|
||||
// Reset the data to be empty
|
||||
data, err = schemaMap(r.Schema).Data(nil, d)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// If we're only destroying, and not creating, then return
|
||||
// now since we're done!
|
||||
if !d.RequiresNew() {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
if s.ID == "" {
|
||||
// We're creating, it is a new resource.
|
||||
err = r.Create(data, meta)
|
||||
} else {
|
||||
if r.Update == nil {
|
||||
return s, fmt.Errorf("%s doesn't support update", s.Type)
|
||||
}
|
||||
|
||||
err = r.Update(data, meta)
|
||||
}
|
||||
|
||||
// Always set the ID attribute if it is set. We also always collapse
|
||||
// the state since even partial states need to be returned.
|
||||
state := data.State()
|
||||
if state.ID != "" {
|
||||
if state.Attributes == nil {
|
||||
state.Attributes = make(map[string]string)
|
||||
}
|
||||
state.Attributes["id"] = state.ID
|
||||
}
|
||||
|
||||
return state, err
|
||||
}
|
||||
|
||||
// Diff returns a diff of this resource and is API compatible with the
|
||||
// ResourceProvider interface.
|
||||
func (r *Resource) Diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
||||
return schemaMap(r.Schema).Diff(s, c)
|
||||
}
|
||||
|
||||
// Validate validates the resource configuration against the schema.
|
||||
func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return schemaMap(r.Schema).Validate(c)
|
||||
}
|
||||
|
||||
// Refresh refreshes the state of the resource.
|
||||
func (r *Resource) Refresh(
|
||||
s *terraform.ResourceState,
|
||||
meta interface{}) (*terraform.ResourceState, error) {
|
||||
data, err := schemaMap(r.Schema).Data(s, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = r.Read(data, meta)
|
||||
return data.State(), err
|
||||
}
|
||||
|
||||
// InternalValidate should be called to validate the structure
|
||||
// of the resource.
|
||||
//
|
||||
// This should be called in a unit test for any resource to verify
|
||||
// before release that a resource is properly configured for use with
|
||||
// this library.
|
||||
func (r *Resource) InternalValidate() error {
|
||||
if r == nil {
|
||||
return errors.New("resource is nil")
|
||||
}
|
||||
|
||||
return schemaMap(r.Schema).InternalValidate()
|
||||
}
|
|
@ -0,0 +1,629 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// getSource represents the level we want to get for a value (internally).
|
||||
// Any source less than or equal to the level will be loaded (whichever
|
||||
// has a value first).
|
||||
type getSource byte
|
||||
|
||||
const (
|
||||
getSourceState getSource = iota
|
||||
getSourceDiff
|
||||
getSourceSet
|
||||
)
|
||||
|
||||
// ResourceData is used to query and set the attributes of a resource.
|
||||
type ResourceData struct {
|
||||
schema map[string]*Schema
|
||||
state *terraform.ResourceState
|
||||
diff *terraform.ResourceDiff
|
||||
|
||||
setMap map[string]string
|
||||
newState *terraform.ResourceState
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// Get returns the data for the given key, or nil if the key doesn't exist.
|
||||
//
|
||||
// The type of the data returned will be according to the schema specified.
|
||||
// Primitives will be their respective types in Go, lists will always be
|
||||
// []interface{}, and sub-resources will be map[string]interface{}.
|
||||
func (d *ResourceData) Get(key string) interface{} {
|
||||
var parts []string
|
||||
if key != "" {
|
||||
parts = strings.Split(key, ".")
|
||||
}
|
||||
|
||||
return d.getObject("", parts, d.schema, getSourceSet)
|
||||
}
|
||||
|
||||
// GetChange returns the old and new value for a given key.
|
||||
//
|
||||
// If there is no change, then old and new will simply be the same.
|
||||
func (d *ResourceData) GetChange(key string) (interface{}, interface{}) {
|
||||
var parts []string
|
||||
if key != "" {
|
||||
parts = strings.Split(key, ".")
|
||||
}
|
||||
|
||||
o := d.getObject("", parts, d.schema, getSourceState)
|
||||
n := d.getObject("", parts, d.schema, getSourceDiff)
|
||||
return o, n
|
||||
}
|
||||
|
||||
// HasChange returns whether or not the given key has been changed.
|
||||
func (d *ResourceData) HasChange(key string) bool {
|
||||
o, n := d.GetChange(key)
|
||||
return !reflect.DeepEqual(o, n)
|
||||
}
|
||||
|
||||
// Set sets the value for the given key.
|
||||
//
|
||||
// If the key is invalid or the value is not a correct type, an error
|
||||
// will be returned.
|
||||
func (d *ResourceData) Set(key string, value interface{}) error {
|
||||
if d.setMap == nil {
|
||||
d.setMap = make(map[string]string)
|
||||
}
|
||||
|
||||
parts := strings.Split(key, ".")
|
||||
return d.setObject("", parts, d.schema, value)
|
||||
}
|
||||
|
||||
// Id returns the ID of the resource.
|
||||
func (d *ResourceData) Id() string {
|
||||
var result string
|
||||
|
||||
if d.state != nil {
|
||||
result = d.state.ID
|
||||
}
|
||||
|
||||
if d.newState != nil {
|
||||
result = d.newState.ID
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Dependencies returns the dependencies in this state.
|
||||
func (d *ResourceData) Dependencies() []terraform.ResourceDependency {
|
||||
if d.newState != nil {
|
||||
return d.newState.Dependencies
|
||||
}
|
||||
|
||||
if d.state != nil {
|
||||
return d.state.Dependencies
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetId sets the ID of the resource. If the value is blank, then the
|
||||
// resource is destroyed.
|
||||
func (d *ResourceData) SetId(v string) {
|
||||
d.once.Do(d.init)
|
||||
d.newState.ID = v
|
||||
}
|
||||
|
||||
// SetDependencies sets the dependencies of a resource.
|
||||
func (d *ResourceData) SetDependencies(ds []terraform.ResourceDependency) {
|
||||
d.once.Do(d.init)
|
||||
d.newState.Dependencies = ds
|
||||
}
|
||||
|
||||
// State returns the new ResourceState after the diff and any Set
|
||||
// calls.
|
||||
func (d *ResourceData) State() *terraform.ResourceState {
|
||||
var result terraform.ResourceState
|
||||
result.ID = d.Id()
|
||||
result.Attributes = d.stateObject("", d.schema)
|
||||
result.Dependencies = d.Dependencies()
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
func (d *ResourceData) init() {
|
||||
var copyState terraform.ResourceState
|
||||
if d.state != nil {
|
||||
copyState = *d.state
|
||||
}
|
||||
|
||||
d.newState = ©State
|
||||
}
|
||||
|
||||
func (d *ResourceData) get(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
source getSource) interface{} {
|
||||
switch schema.Type {
|
||||
case TypeList:
|
||||
return d.getList(k, parts, schema, source)
|
||||
case TypeMap:
|
||||
return d.getMap(k, parts, schema, source)
|
||||
default:
|
||||
return d.getPrimitive(k, parts, schema, source)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) getMap(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
source getSource) interface{} {
|
||||
elemSchema := &Schema{Type: TypeString}
|
||||
|
||||
result := make(map[string]interface{})
|
||||
prefix := k + "."
|
||||
|
||||
if d.state != nil && source >= getSourceState {
|
||||
for k, _ := range d.state.Attributes {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
single := k[len(prefix):]
|
||||
result[single] = d.getPrimitive(k, nil, elemSchema, source)
|
||||
}
|
||||
}
|
||||
|
||||
if d.diff != nil && source >= getSourceDiff {
|
||||
for k, v := range d.diff.Attributes {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
single := k[len(prefix):]
|
||||
|
||||
if v.NewRemoved {
|
||||
delete(result, single)
|
||||
} else {
|
||||
result[single] = d.getPrimitive(k, nil, elemSchema, source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if d.setMap != nil && source >= getSourceSet {
|
||||
cleared := false
|
||||
for k, _ := range d.setMap {
|
||||
if !strings.HasPrefix(k, prefix) {
|
||||
continue
|
||||
}
|
||||
if !cleared {
|
||||
// We clear the results if they are in the set map
|
||||
result = make(map[string]interface{})
|
||||
cleared = true
|
||||
}
|
||||
|
||||
single := k[len(prefix):]
|
||||
result[single] = d.getPrimitive(k, nil, elemSchema, source)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) getObject(
|
||||
k string,
|
||||
parts []string,
|
||||
schema map[string]*Schema,
|
||||
source getSource) interface{} {
|
||||
if len(parts) > 0 {
|
||||
// We're requesting a specific key in an object
|
||||
key := parts[0]
|
||||
parts = parts[1:]
|
||||
s, ok := schema[key]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if k != "" {
|
||||
// If we're not at the root, then we need to append
|
||||
// the key to get the full key path.
|
||||
key = fmt.Sprintf("%s.%s", k, key)
|
||||
}
|
||||
|
||||
return d.get(key, parts, s, source)
|
||||
}
|
||||
|
||||
// Get the entire object
|
||||
result := make(map[string]interface{})
|
||||
for field, _ := range schema {
|
||||
result[field] = d.getObject(k, []string{field}, schema, source)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) getList(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
source getSource) interface{} {
|
||||
if len(parts) > 0 {
|
||||
// We still have parts left over meaning we're accessing an
|
||||
// element of this list.
|
||||
idx := parts[0]
|
||||
parts = parts[1:]
|
||||
|
||||
// Special case if we're accessing the count of the list
|
||||
if idx == "#" {
|
||||
schema := &Schema{Type: TypeInt}
|
||||
result := d.get(k+".#", parts, schema, source)
|
||||
if result == nil {
|
||||
result = 0
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.%s", k, idx)
|
||||
switch t := schema.Elem.(type) {
|
||||
case *Resource:
|
||||
return d.getObject(key, parts, t.Schema, source)
|
||||
case *Schema:
|
||||
return d.get(key, parts, t, source)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the entire list.
|
||||
result := make(
|
||||
[]interface{},
|
||||
d.getList(k, []string{"#"}, schema, source).(int))
|
||||
for i, _ := range result {
|
||||
is := strconv.FormatInt(int64(i), 10)
|
||||
result[i] = d.getList(k, []string{is}, schema, source)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) getPrimitive(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
source getSource) interface{} {
|
||||
var result string
|
||||
var resultSet bool
|
||||
if d.state != nil && source >= getSourceState {
|
||||
result, resultSet = d.state.Attributes[k]
|
||||
}
|
||||
|
||||
if d.diff != nil && source >= getSourceDiff {
|
||||
attrD, ok := d.diff.Attributes[k]
|
||||
if ok && !attrD.NewComputed {
|
||||
result = attrD.New
|
||||
resultSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if d.setMap != nil && source >= getSourceSet {
|
||||
if v, ok := d.setMap[k]; ok {
|
||||
result = v
|
||||
resultSet = true
|
||||
}
|
||||
}
|
||||
|
||||
if !resultSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch schema.Type {
|
||||
case TypeString:
|
||||
// Use the value as-is. We just put this case here to be explicit.
|
||||
return result
|
||||
case TypeInt:
|
||||
if result == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
v, err := strconv.ParseInt(result, 0, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return int(v)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) set(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
value interface{}) error {
|
||||
switch schema.Type {
|
||||
case TypeList:
|
||||
return d.setList(k, parts, schema, value)
|
||||
case TypeMap:
|
||||
return d.setMapValue(k, parts, schema, value)
|
||||
default:
|
||||
return d.setPrimitive(k, schema, value)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) setList(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
value interface{}) error {
|
||||
if len(parts) > 0 {
|
||||
// We're setting a specific element
|
||||
idx := parts[0]
|
||||
parts = parts[1:]
|
||||
|
||||
// Special case if we're accessing the count of the list
|
||||
if idx == "#" {
|
||||
return fmt.Errorf("%s: can't set count of list", k)
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("%s.%s", k, idx)
|
||||
switch t := schema.Elem.(type) {
|
||||
case *Resource:
|
||||
return d.setObject(key, parts, t.Schema, value)
|
||||
case *Schema:
|
||||
return d.set(key, parts, t, value)
|
||||
}
|
||||
}
|
||||
|
||||
var vs []interface{}
|
||||
if err := mapstructure.Decode(value, &vs); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
// Set the entire list.
|
||||
var err error
|
||||
for i, elem := range vs {
|
||||
is := strconv.FormatInt(int64(i), 10)
|
||||
err = d.setList(k, []string{is}, schema, elem)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
for i, _ := range vs {
|
||||
is := strconv.FormatInt(int64(i), 10)
|
||||
d.setList(k, []string{is}, schema, nil)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
d.setMap[k+".#"] = strconv.FormatInt(int64(len(vs)), 10)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ResourceData) setMapValue(
|
||||
k string,
|
||||
parts []string,
|
||||
schema *Schema,
|
||||
value interface{}) error {
|
||||
elemSchema := &Schema{Type: TypeString}
|
||||
if len(parts) > 0 {
|
||||
return fmt.Errorf("%s: full map must be set, no a single element", k)
|
||||
}
|
||||
|
||||
// Delete any prior map set
|
||||
/*
|
||||
v := d.getMap(k, nil, schema, getSourceSet)
|
||||
for subKey, _ := range v.(map[string]interface{}) {
|
||||
delete(d.setMap, fmt.Sprintf("%s.%s", k, subKey))
|
||||
}
|
||||
*/
|
||||
|
||||
v := reflect.ValueOf(value)
|
||||
if v.Kind() != reflect.Map {
|
||||
return fmt.Errorf("%s: must be a map", k)
|
||||
}
|
||||
if v.Type().Key().Kind() != reflect.String {
|
||||
return fmt.Errorf("%s: keys must strings", k)
|
||||
}
|
||||
vs := make(map[string]interface{})
|
||||
for _, mk := range v.MapKeys() {
|
||||
mv := v.MapIndex(mk)
|
||||
vs[mk.String()] = mv.Interface()
|
||||
}
|
||||
|
||||
for subKey, v := range vs {
|
||||
err := d.set(fmt.Sprintf("%s.%s", k, subKey), nil, elemSchema, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ResourceData) setObject(
|
||||
k string,
|
||||
parts []string,
|
||||
schema map[string]*Schema,
|
||||
value interface{}) error {
|
||||
if len(parts) > 0 {
|
||||
// We're setting a specific key in an object
|
||||
key := parts[0]
|
||||
parts = parts[1:]
|
||||
|
||||
s, ok := schema[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s (internal): unknown key to set: %s", k, key)
|
||||
}
|
||||
|
||||
if k != "" {
|
||||
// If we're not at the root, then we need to append
|
||||
// the key to get the full key path.
|
||||
key = fmt.Sprintf("%s.%s", k, key)
|
||||
}
|
||||
|
||||
return d.set(key, parts, s, value)
|
||||
}
|
||||
|
||||
// Set the entire object. First decode into a proper structure
|
||||
var v map[string]interface{}
|
||||
if err := mapstructure.Decode(value, &v); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
// Set each element in turn
|
||||
var err error
|
||||
for k1, v1 := range v {
|
||||
err = d.setObject(k, []string{k1}, schema, v1)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
for k1, _ := range v {
|
||||
d.setObject(k, []string{k1}, schema, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *ResourceData) setPrimitive(
|
||||
k string,
|
||||
schema *Schema,
|
||||
v interface{}) error {
|
||||
if v == nil {
|
||||
delete(d.setMap, k)
|
||||
return nil
|
||||
}
|
||||
|
||||
var set string
|
||||
switch schema.Type {
|
||||
case TypeString:
|
||||
if err := mapstructure.Decode(v, &set); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
case TypeInt:
|
||||
var n int
|
||||
if err := mapstructure.Decode(v, &n); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
set = strconv.FormatInt(int64(n), 10)
|
||||
default:
|
||||
return fmt.Errorf("Unknown type: %s", schema.Type)
|
||||
}
|
||||
|
||||
d.setMap[k] = set
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateList(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
countRaw := d.get(prefix, []string{"#"}, schema, getSourceSet)
|
||||
if countRaw == nil {
|
||||
return nil
|
||||
}
|
||||
count := countRaw.(int)
|
||||
|
||||
result := make(map[string]string)
|
||||
if count > 0 {
|
||||
result[prefix+".#"] = strconv.FormatInt(int64(count), 10)
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
key := fmt.Sprintf("%s.%d", prefix, i)
|
||||
|
||||
var m map[string]string
|
||||
switch t := schema.Elem.(type) {
|
||||
case *Resource:
|
||||
m = d.stateObject(key, t.Schema)
|
||||
case *Schema:
|
||||
m = d.stateSingle(key, t)
|
||||
}
|
||||
|
||||
for k, v := range m {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateMap(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
v := d.getMap(prefix, nil, schema, getSourceSet)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
elemSchema := &Schema{Type: TypeString}
|
||||
result := make(map[string]string)
|
||||
for mk, _ := range v.(map[string]interface{}) {
|
||||
mp := fmt.Sprintf("%s.%s", prefix, mk)
|
||||
for k, v := range d.stateSingle(mp, elemSchema) {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateObject(
|
||||
prefix string,
|
||||
schema map[string]*Schema) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for k, v := range schema {
|
||||
key := k
|
||||
if prefix != "" {
|
||||
key = prefix + "." + key
|
||||
}
|
||||
|
||||
for k1, v1 := range d.stateSingle(key, v) {
|
||||
result[k1] = v1
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (d *ResourceData) statePrimitive(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
v := d.getPrimitive(prefix, nil, schema, getSourceSet)
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var vs string
|
||||
switch schema.Type {
|
||||
case TypeString:
|
||||
vs = v.(string)
|
||||
case TypeInt:
|
||||
vs = strconv.FormatInt(int64(v.(int)), 10)
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown type: %s", schema.Type))
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
prefix: vs,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *ResourceData) stateSingle(
|
||||
prefix string,
|
||||
schema *Schema) map[string]string {
|
||||
switch schema.Type {
|
||||
case TypeList:
|
||||
return d.stateList(prefix, schema)
|
||||
case TypeMap:
|
||||
return d.stateMap(prefix, schema)
|
||||
default:
|
||||
return d.statePrimitive(prefix, schema)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,306 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestResourceApply_create(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
called := false
|
||||
r.Create = func(d *ResourceData, m interface{}) error {
|
||||
called = true
|
||||
d.SetId("foo")
|
||||
return nil
|
||||
}
|
||||
|
||||
var s *terraform.ResourceState = nil
|
||||
|
||||
d := &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
New: "42",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("not called")
|
||||
}
|
||||
|
||||
expected := &terraform.ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"id": "foo",
|
||||
"foo": "42",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_destroy(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
called := false
|
||||
r.Delete = func(d *ResourceData, m interface{}) error {
|
||||
called = true
|
||||
return nil
|
||||
}
|
||||
|
||||
s := &terraform.ResourceState{
|
||||
ID: "bar",
|
||||
}
|
||||
|
||||
d := &terraform.ResourceDiff{
|
||||
Destroy: true,
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !called {
|
||||
t.Fatal("delete not called")
|
||||
}
|
||||
|
||||
if actual != nil {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_destroyPartial(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.Delete = func(d *ResourceData, m interface{}) error {
|
||||
d.Set("foo", 42)
|
||||
return fmt.Errorf("some error")
|
||||
}
|
||||
|
||||
s := &terraform.ResourceState{
|
||||
ID: "bar",
|
||||
Attributes: map[string]string{
|
||||
"foo": "12",
|
||||
},
|
||||
}
|
||||
|
||||
d := &terraform.ResourceDiff{
|
||||
Destroy: true,
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
expected := &terraform.ResourceState{
|
||||
ID: "bar",
|
||||
Attributes: map[string]string{
|
||||
"foo": "42",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_update(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.Update = func(d *ResourceData, m interface{}) error {
|
||||
d.Set("foo", 42)
|
||||
return nil
|
||||
}
|
||||
|
||||
s := &terraform.ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"foo": "12",
|
||||
},
|
||||
}
|
||||
|
||||
d := &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
New: "13",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
expected := &terraform.ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"id": "foo",
|
||||
"foo": "42",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceApply_updateNoCallback(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.Update = nil
|
||||
|
||||
s := &terraform.ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"foo": "12",
|
||||
},
|
||||
}
|
||||
|
||||
d := &terraform.ResourceDiff{
|
||||
Attributes: map[string]*terraform.ResourceAttrDiff{
|
||||
"foo": &terraform.ResourceAttrDiff{
|
||||
New: "13",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := r.Apply(s, d, nil)
|
||||
if err == nil {
|
||||
t.Fatal("should error")
|
||||
}
|
||||
|
||||
expected := &terraform.ResourceState{
|
||||
ID: "foo",
|
||||
Attributes: map[string]string{
|
||||
"foo": "12",
|
||||
},
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceInternalValidate(t *testing.T) {
|
||||
cases := []struct {
|
||||
In *Resource
|
||||
Err bool
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
// No optional and no required
|
||||
{
|
||||
&Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
Required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
err := tc.In.InternalValidate()
|
||||
if (err != nil) != tc.Err {
|
||||
t.Fatalf("%d: bad: %s", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceRefresh(t *testing.T) {
|
||||
r := &Resource{
|
||||
Schema: map[string]*Schema{
|
||||
"foo": &Schema{
|
||||
Type: TypeInt,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
r.Read = func(d *ResourceData, m interface{}) error {
|
||||
if m != 42 {
|
||||
return fmt.Errorf("meta not passed")
|
||||
}
|
||||
|
||||
return d.Set("foo", d.Get("foo").(int)+1)
|
||||
}
|
||||
|
||||
s := &terraform.ResourceState{
|
||||
ID: "bar",
|
||||
Attributes: map[string]string{
|
||||
"foo": "12",
|
||||
},
|
||||
}
|
||||
|
||||
expected := &terraform.ResourceState{
|
||||
ID: "bar",
|
||||
Attributes: map[string]string{
|
||||
"foo": "13",
|
||||
},
|
||||
}
|
||||
|
||||
actual, err := r.Refresh(s, 42)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,564 @@
|
|||
package schema
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
// ValueType is an enum of the type that can be represented by a schema.
|
||||
type ValueType int
|
||||
|
||||
const (
|
||||
TypeInvalid ValueType = iota
|
||||
TypeBool
|
||||
TypeInt
|
||||
TypeString
|
||||
TypeList
|
||||
TypeMap
|
||||
)
|
||||
|
||||
// Schema is used to describe the structure of a value.
|
||||
type Schema struct {
|
||||
// Type is the type of the value and must be one of the ValueType values.
|
||||
Type ValueType
|
||||
|
||||
// If one of these is set, then this item can come from the configuration.
|
||||
// Both cannot be set. If Optional is set, the value is optional. If
|
||||
// Required is set, the value is required.
|
||||
Optional bool
|
||||
Required bool
|
||||
|
||||
// The fields below relate to diffs.
|
||||
//
|
||||
// If Computed is true, then the result of this value is computed
|
||||
// (unless specified by config) on creation.
|
||||
//
|
||||
// If ForceNew is true, then a change in this resource necessitates
|
||||
// the creation of a new resource.
|
||||
Computed bool
|
||||
ForceNew bool
|
||||
|
||||
// The following fields are only set for a TypeList Type.
|
||||
//
|
||||
// Elem must be either a *Schema or a *Resource only if the Type is
|
||||
// TypeList, and represents what the element type is. If it is *Schema,
|
||||
// the element type is just a simple value. If it is *Resource, the
|
||||
// element type is a complex structure, potentially with its own lifecycle.
|
||||
Elem interface{}
|
||||
|
||||
// ComputedWhen is a set of queries on the configuration. Whenever any
|
||||
// of these things is changed, it will require a recompute (this requires
|
||||
// that Computed is set to true).
|
||||
ComputedWhen []string
|
||||
}
|
||||
|
||||
func (s *Schema) finalizeDiff(
|
||||
d *terraform.ResourceAttrDiff) *terraform.ResourceAttrDiff {
|
||||
if d == nil {
|
||||
return d
|
||||
}
|
||||
|
||||
if s.Computed {
|
||||
if d.Old != "" && d.New == "" {
|
||||
// This is a computed value with an old value set already,
|
||||
// just let it go.
|
||||
return nil
|
||||
}
|
||||
|
||||
if d.New == "" {
|
||||
// Computed attribute without a new value set
|
||||
d.NewComputed = true
|
||||
}
|
||||
}
|
||||
|
||||
if s.ForceNew {
|
||||
// Force new, set it to true in the diff
|
||||
d.RequiresNew = true
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// schemaMap is a wrapper that adds nice functions on top of schemas.
|
||||
type schemaMap map[string]*Schema
|
||||
|
||||
// Data returns a ResourceData for the given schema, state, and diff.
|
||||
//
|
||||
// The diff is optional.
|
||||
func (m schemaMap) Data(
|
||||
s *terraform.ResourceState,
|
||||
d *terraform.ResourceDiff) (*ResourceData, error) {
|
||||
return &ResourceData{
|
||||
schema: m,
|
||||
state: s,
|
||||
diff: d,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Diff returns the diff for a resource given the schema map,
|
||||
// state, and configuration.
|
||||
func (m schemaMap) Diff(
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) (*terraform.ResourceDiff, error) {
|
||||
result := new(terraform.ResourceDiff)
|
||||
result.Attributes = make(map[string]*terraform.ResourceAttrDiff)
|
||||
|
||||
for k, schema := range m {
|
||||
err := m.diff(k, schema, result, s, c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any nil diffs just to keep things clean
|
||||
for k, v := range result.Attributes {
|
||||
if v == nil {
|
||||
delete(result.Attributes, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Go through and detect all of the ComputedWhens now that we've
|
||||
// finished the diff.
|
||||
// TODO
|
||||
|
||||
if result.Empty() {
|
||||
// If we don't have any diff elements, just return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Validate validates the configuration against this schema mapping.
|
||||
func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
|
||||
return m.validateObject("", m, c)
|
||||
}
|
||||
|
||||
// InternalValidate validates the format of this schema. This should be called
|
||||
// from a unit test (and not in user-path code) to verify that a schema
|
||||
// is properly built.
|
||||
func (m schemaMap) InternalValidate() error {
|
||||
for k, v := range m {
|
||||
if v.Type == TypeInvalid {
|
||||
return fmt.Errorf("%s: Type must be specified", k)
|
||||
}
|
||||
|
||||
if v.Optional && v.Required {
|
||||
return fmt.Errorf("%s: Optional or Required must be set, not both", k)
|
||||
}
|
||||
|
||||
if v.Required && v.Computed {
|
||||
return fmt.Errorf("%s: Cannot be both Required and Computed", k)
|
||||
}
|
||||
|
||||
if len(v.ComputedWhen) > 0 && !v.Computed {
|
||||
return fmt.Errorf("%s: ComputedWhen can only be set with Computed", k)
|
||||
}
|
||||
|
||||
if v.Type == TypeList {
|
||||
if v.Elem == nil {
|
||||
return fmt.Errorf("%s: Elem must be set for lists", k)
|
||||
}
|
||||
|
||||
switch t := v.Elem.(type) {
|
||||
case *Resource:
|
||||
if err := t.InternalValidate(); err != nil {
|
||||
return err
|
||||
}
|
||||
case *Schema:
|
||||
bad := t.Computed || t.Optional || t.Required
|
||||
if bad {
|
||||
return fmt.Errorf(
|
||||
"%s: Elem must have only Type set", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m schemaMap) diff(
|
||||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
var err error
|
||||
switch schema.Type {
|
||||
case TypeBool:
|
||||
fallthrough
|
||||
case TypeInt:
|
||||
fallthrough
|
||||
case TypeString:
|
||||
err = m.diffString(k, schema, diff, s, c)
|
||||
case TypeList:
|
||||
err = m.diffList(k, schema, diff, s, c)
|
||||
case TypeMap:
|
||||
err = m.diffMap(k, schema, diff, s, c)
|
||||
default:
|
||||
err = fmt.Errorf("%s: unknown type %s", k, schema.Type)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (m schemaMap) diffList(
|
||||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
var vs []interface{}
|
||||
|
||||
v, ok := c.Get(k)
|
||||
if ok {
|
||||
// We have to use reflection to build the []interface{} list
|
||||
rawV := reflect.ValueOf(v)
|
||||
if rawV.Kind() != reflect.Slice {
|
||||
return fmt.Errorf("%s: must be a list", k)
|
||||
}
|
||||
vs = make([]interface{}, rawV.Len())
|
||||
for i, _ := range vs {
|
||||
vs[i] = rawV.Index(i).Interface()
|
||||
}
|
||||
}
|
||||
|
||||
// If this field is required, then it must also be non-empty
|
||||
if len(vs) == 0 && schema.Required {
|
||||
return fmt.Errorf("%s: required field is not set", k)
|
||||
}
|
||||
|
||||
// Get the counts
|
||||
var oldLen, newLen int
|
||||
if s != nil {
|
||||
if v, ok := s.Attributes[k+".#"]; ok {
|
||||
old64, err := strconv.ParseInt(v, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
oldLen = int(old64)
|
||||
}
|
||||
}
|
||||
newLen = len(vs)
|
||||
|
||||
// If the counts are not the same, then record that diff
|
||||
changed := oldLen != newLen
|
||||
computed := oldLen == 0 && newLen == 0 && schema.Computed
|
||||
if changed || computed {
|
||||
countSchema := &Schema{
|
||||
Type: TypeInt,
|
||||
Computed: schema.Computed,
|
||||
ForceNew: schema.ForceNew,
|
||||
}
|
||||
|
||||
oldStr := ""
|
||||
newStr := ""
|
||||
if !computed {
|
||||
oldStr = strconv.FormatInt(int64(oldLen), 10)
|
||||
newStr = strconv.FormatInt(int64(newLen), 10)
|
||||
}
|
||||
|
||||
diff.Attributes[k+".#"] = countSchema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: oldStr,
|
||||
New: newStr,
|
||||
})
|
||||
}
|
||||
|
||||
// Figure out the maximum
|
||||
maxLen := oldLen
|
||||
if newLen > maxLen {
|
||||
maxLen = newLen
|
||||
}
|
||||
|
||||
switch t := schema.Elem.(type) {
|
||||
case *Schema:
|
||||
// Copy the schema so that we can set Computed/ForceNew from
|
||||
// the parent schema (the TypeList).
|
||||
t2 := *t
|
||||
t2.ForceNew = schema.ForceNew
|
||||
|
||||
// This is just a primitive element, so go through each and
|
||||
// just diff each.
|
||||
for i := 0; i < maxLen; i++ {
|
||||
subK := fmt.Sprintf("%s.%d", k, i)
|
||||
err := m.diff(subK, &t2, diff, s, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case *Resource:
|
||||
// This is a complex resource
|
||||
for i := 0; i < maxLen; i++ {
|
||||
for k2, schema := range t.Schema {
|
||||
subK := fmt.Sprintf("%s.%d.%s", k, i, k2)
|
||||
err := m.diff(subK, schema, diff, s, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%s: unknown element type (internal)", k)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m schemaMap) diffMap(
|
||||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
//elemSchema := &Schema{Type: TypeString}
|
||||
prefix := k + "."
|
||||
|
||||
// First get all the values from the state
|
||||
stateMap := make(map[string]string)
|
||||
if s != nil {
|
||||
for sk, sv := range s.Attributes {
|
||||
if !strings.HasPrefix(sk, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
stateMap[sk[len(prefix):]] = sv
|
||||
}
|
||||
}
|
||||
|
||||
// Then get all the values from the configuration
|
||||
configMap := make(map[string]string)
|
||||
if c != nil {
|
||||
if raw, ok := c.Get(k); ok {
|
||||
for k, v := range raw.(map[string]interface{}) {
|
||||
configMap[k] = v.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now we compare, preferring values from the config map
|
||||
for k, v := range configMap {
|
||||
old := stateMap[k]
|
||||
delete(stateMap, k)
|
||||
|
||||
if old == v {
|
||||
continue
|
||||
}
|
||||
|
||||
diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: old,
|
||||
New: v,
|
||||
})
|
||||
}
|
||||
for k, v := range stateMap {
|
||||
diff.Attributes[prefix+k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: v,
|
||||
NewRemoved: true,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m schemaMap) diffString(
|
||||
k string,
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState,
|
||||
c *terraform.ResourceConfig) error {
|
||||
var old, n string
|
||||
if s != nil {
|
||||
old = s.Attributes[k]
|
||||
}
|
||||
|
||||
v, ok := c.Get(k)
|
||||
if !ok {
|
||||
// We don't have a value, if it is required then it is an error
|
||||
if schema.Required {
|
||||
return fmt.Errorf("%s: required field not set", k)
|
||||
}
|
||||
|
||||
// If we don't have an old value, just return
|
||||
if old == "" && !schema.Computed {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
if err := mapstructure.WeakDecode(v, &n); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
if old == n {
|
||||
// They're the same value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: old,
|
||||
New: n,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m schemaMap) diffPrimitive(
|
||||
k string,
|
||||
nraw interface{},
|
||||
schema *Schema,
|
||||
diff *terraform.ResourceDiff,
|
||||
s *terraform.ResourceState) error {
|
||||
var old, n string
|
||||
if s != nil {
|
||||
old = s.Attributes[k]
|
||||
}
|
||||
|
||||
if err := mapstructure.WeakDecode(nraw, &n); err != nil {
|
||||
return fmt.Errorf("%s: %s", k, err)
|
||||
}
|
||||
|
||||
if old == n {
|
||||
// They're the same value
|
||||
return nil
|
||||
}
|
||||
|
||||
diff.Attributes[k] = schema.finalizeDiff(&terraform.ResourceAttrDiff{
|
||||
Old: old,
|
||||
New: n,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m schemaMap) validate(
|
||||
k string,
|
||||
schema *Schema,
|
||||
c *terraform.ResourceConfig) ([]string, []error) {
|
||||
raw, ok := c.Get(k)
|
||||
if !ok {
|
||||
if schema.Required {
|
||||
return nil, []error{fmt.Errorf(
|
||||
"%s: required field is not set", k)}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !schema.Required && !schema.Optional {
|
||||
// This is a computed-only field
|
||||
return nil, []error{fmt.Errorf(
|
||||
"%s: this field cannot be set", k)}
|
||||
}
|
||||
|
||||
return m.validatePrimitive(k, raw, schema, c)
|
||||
}
|
||||
|
||||
func (m schemaMap) validateList(
|
||||
k string,
|
||||
raw interface{},
|
||||
schema *Schema,
|
||||
c *terraform.ResourceConfig) ([]string, []error) {
|
||||
// We use reflection to verify the slice because you can't
|
||||
// case to []interface{} unless the slice is exactly that type.
|
||||
rawV := reflect.ValueOf(raw)
|
||||
if rawV.Kind() != reflect.Slice {
|
||||
return nil, []error{fmt.Errorf(
|
||||
"%s: should be a list", k)}
|
||||
}
|
||||
|
||||
// Now build the []interface{}
|
||||
raws := make([]interface{}, rawV.Len())
|
||||
for i, _ := range raws {
|
||||
raws[i] = rawV.Index(i).Interface()
|
||||
}
|
||||
|
||||
var ws []string
|
||||
var es []error
|
||||
for i, raw := range raws {
|
||||
key := fmt.Sprintf("%s.%d", k, i)
|
||||
|
||||
var ws2 []string
|
||||
var es2 []error
|
||||
switch t := schema.Elem.(type) {
|
||||
case *Resource:
|
||||
// This is a sub-resource
|
||||
ws2, es2 = m.validateObject(key, t.Schema, c)
|
||||
case *Schema:
|
||||
// This is some sort of primitive
|
||||
ws2, es2 = m.validatePrimitive(key, raw, t, c)
|
||||
}
|
||||
|
||||
if len(ws2) > 0 {
|
||||
ws = append(ws, ws2...)
|
||||
}
|
||||
if len(es2) > 0 {
|
||||
es = append(es, es2...)
|
||||
}
|
||||
}
|
||||
|
||||
return ws, es
|
||||
}
|
||||
|
||||
func (m schemaMap) validateObject(
|
||||
k string,
|
||||
schema map[string]*Schema,
|
||||
c *terraform.ResourceConfig) ([]string, []error) {
|
||||
var ws []string
|
||||
var es []error
|
||||
for subK, s := range schema {
|
||||
key := subK
|
||||
if k != "" {
|
||||
key = fmt.Sprintf("%s.%s", k, subK)
|
||||
}
|
||||
|
||||
ws2, es2 := m.validate(key, s, c)
|
||||
if len(ws2) > 0 {
|
||||
ws = append(ws, ws2...)
|
||||
}
|
||||
if len(es2) > 0 {
|
||||
es = append(es, es2...)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect any extra/unknown keys and report those as errors.
|
||||
prefix := k + "."
|
||||
for configK, _ := range c.Raw {
|
||||
if k != "" {
|
||||
if !strings.HasPrefix(configK, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
configK = configK[len(prefix):]
|
||||
}
|
||||
|
||||
if _, ok := schema[configK]; !ok {
|
||||
es = append(es, fmt.Errorf(
|
||||
"%s: invalid or unknown key: %s", k, configK))
|
||||
}
|
||||
}
|
||||
|
||||
return ws, es
|
||||
}
|
||||
|
||||
func (m schemaMap) validatePrimitive(
|
||||
k string,
|
||||
raw interface{},
|
||||
schema *Schema,
|
||||
c *terraform.ResourceConfig) ([]string, []error) {
|
||||
switch schema.Type {
|
||||
case TypeList:
|
||||
return m.validateList(k, raw, schema, c)
|
||||
case TypeInt:
|
||||
// Verify that we can parse this as an int
|
||||
var n int
|
||||
if err := mapstructure.WeakDecode(raw, &n); err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -75,9 +75,11 @@ func NewContext(opts *ContextOpts) *Context {
|
|||
|
||||
// Calculate all the default variables
|
||||
defaultVars := make(map[string]string)
|
||||
for _, v := range opts.Config.Variables {
|
||||
for k, val := range v.DefaultsMap() {
|
||||
defaultVars[k] = val
|
||||
if opts.Config != nil {
|
||||
for _, v := range opts.Config.Variables {
|
||||
for k, val := range v.DefaultsMap() {
|
||||
defaultVars[k] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,34 +89,14 @@ func (c *ResourceConfig) CheckSet(keys []string) []error {
|
|||
// The second return value is true if the get was successful. Get will
|
||||
// not succeed if the value is being computed.
|
||||
func (c *ResourceConfig) Get(k string) (interface{}, bool) {
|
||||
parts := strings.Split(k, ".")
|
||||
|
||||
var current interface{} = c.Raw
|
||||
for _, part := range parts {
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cv := reflect.ValueOf(current)
|
||||
switch cv.Kind() {
|
||||
case reflect.Map:
|
||||
v := cv.MapIndex(reflect.ValueOf(part))
|
||||
if !v.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
current = v.Interface()
|
||||
case reflect.Slice:
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
current = cv.Index(int(i)).Interface()
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
|
||||
}
|
||||
// First try to get it from c.Config since that has interpolated values
|
||||
result, ok := c.get(k, c.Config)
|
||||
if ok {
|
||||
return result, ok
|
||||
}
|
||||
|
||||
return current, true
|
||||
// Otherwise, just get it from the raw config
|
||||
return c.get(k, c.Raw)
|
||||
}
|
||||
|
||||
// IsSet checks if the key in the configuration is set. A key is set if
|
||||
|
@ -143,6 +123,42 @@ func (c *ResourceConfig) IsSet(k string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (c *ResourceConfig) get(
|
||||
k string, raw map[string]interface{}) (interface{}, bool) {
|
||||
parts := strings.Split(k, ".")
|
||||
|
||||
var current interface{} = raw
|
||||
for _, part := range parts {
|
||||
if current == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
cv := reflect.ValueOf(current)
|
||||
switch cv.Kind() {
|
||||
case reflect.Map:
|
||||
v := cv.MapIndex(reflect.ValueOf(part))
|
||||
if !v.IsValid() {
|
||||
return nil, false
|
||||
}
|
||||
current = v.Interface()
|
||||
case reflect.Slice:
|
||||
if part == "#" {
|
||||
current = cv.Len()
|
||||
} else {
|
||||
i, err := strconv.ParseInt(part, 0, 0)
|
||||
if err != nil {
|
||||
return nil, false
|
||||
}
|
||||
current = cv.Index(int(i)).Interface()
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
|
||||
}
|
||||
}
|
||||
|
||||
return current, true
|
||||
}
|
||||
|
||||
func (c *ResourceConfig) interpolate(ctx *Context) error {
|
||||
if c == nil {
|
||||
return nil
|
||||
|
|
|
@ -3,6 +3,8 @@ package terraform
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/config"
|
||||
)
|
||||
|
||||
func TestResource_Vars(t *testing.T) {
|
||||
|
@ -29,3 +31,49 @@ func TestResource_Vars(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceConfigGet(t *testing.T) {
|
||||
cases := []struct {
|
||||
Config map[string]interface{}
|
||||
Vars map[string]string
|
||||
Key string
|
||||
Value interface{}
|
||||
}{
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Key: "foo",
|
||||
Value: "${var.foo}",
|
||||
},
|
||||
|
||||
{
|
||||
Config: map[string]interface{}{
|
||||
"foo": "${var.foo}",
|
||||
},
|
||||
Vars: map[string]string{"foo": "bar"},
|
||||
Key: "foo",
|
||||
Value: "bar",
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range cases {
|
||||
rawC, err := config.NewRawConfig(tc.Config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
rc := NewResourceConfig(rawC)
|
||||
|
||||
if tc.Vars != nil {
|
||||
ctx := NewContext(&ContextOpts{Variables: tc.Vars})
|
||||
if err := rc.interpolate(ctx); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
v, _ := rc.Get(tc.Key)
|
||||
if !reflect.DeepEqual(v, tc.Value) {
|
||||
t.Fatalf("%d bad: %#v", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue