Merge pull request #14681 from svanharmelen/f-review

Use `helpers.shema.Provisoner` in Chef provisioner V2
This commit is contained in:
Jake Champlin 2017-05-30 14:26:51 -04:00 committed by GitHub
commit 7894478c8c
16 changed files with 601 additions and 388 deletions

View File

@ -3,13 +3,10 @@ package main
import ( import (
"github.com/hashicorp/terraform/builtin/provisioners/chef" "github.com/hashicorp/terraform/builtin/provisioners/chef"
"github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform"
) )
func main() { func main() {
plugin.Serve(&plugin.ServeOpts{ plugin.Serve(&plugin.ServeOpts{
ProvisionerFunc: func() terraform.ResourceProvisioner { ProvisionerFunc: chef.Provisioner,
return new(chef.ResourceProvisioner)
},
}) })
} }

View File

@ -14,10 +14,7 @@ const (
installURL = "https://www.chef.io/chef/install.sh" installURL = "https://www.chef.io/chef/install.sh"
) )
func (p *Provisioner) linuxInstallChefClient( func (p *provisioner) linuxInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
o terraform.UIOutput,
comm communicator.Communicator) error {
// Build up the command prefix // Build up the command prefix
prefix := "" prefix := ""
if p.HTTPProxy != "" { if p.HTTPProxy != "" {
@ -26,7 +23,7 @@ func (p *Provisioner) linuxInstallChefClient(
if p.HTTPSProxy != "" { if p.HTTPSProxy != "" {
prefix += fmt.Sprintf("https_proxy='%s' ", p.HTTPSProxy) prefix += fmt.Sprintf("https_proxy='%s' ", p.HTTPSProxy)
} }
if p.NOProxy != nil { if len(p.NOProxy) > 0 {
prefix += fmt.Sprintf("no_proxy='%s' ", strings.Join(p.NOProxy, ",")) prefix += fmt.Sprintf("no_proxy='%s' ", strings.Join(p.NOProxy, ","))
} }
@ -46,9 +43,7 @@ func (p *Provisioner) linuxInstallChefClient(
return p.runCommand(o, comm, fmt.Sprintf("%srm -f install.sh", prefix)) return p.runCommand(o, comm, fmt.Sprintf("%srm -f install.sh", prefix))
} }
func (p *Provisioner) linuxCreateConfigFiles( func (p *provisioner) linuxCreateConfigFiles(o terraform.UIOutput, comm communicator.Communicator) error {
o terraform.UIOutput,
comm communicator.Communicator) error {
// Make sure the config directory exists // Make sure the config directory exists
if err := p.runCommand(o, comm, "mkdir -p "+linuxConfDir); err != nil { if err := p.runCommand(o, comm, "mkdir -p "+linuxConfDir); err != nil {
return err return err

View File

@ -6,22 +6,23 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvider_linuxInstallChefClient(t *testing.T) { func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
Commands map[string]bool Commands map[string]bool
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"sudo curl -LO https://www.chef.io/chef/install.sh": true, "sudo curl -LO https://www.chef.io/chef/install.sh": true,
@ -31,7 +32,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -39,7 +40,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"curl -LO https://www.chef.io/chef/install.sh": true, "curl -LO https://www.chef.io/chef/install.sh": true,
@ -49,7 +50,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
"HTTPProxy": { "HTTPProxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -57,7 +58,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"http_proxy='http://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true, "http_proxy='http://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true,
@ -67,7 +68,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
"HTTPSProxy": { "HTTPSProxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -75,7 +76,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"https_proxy='https://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true, "https_proxy='https://proxy.local' curl -LO https://www.chef.io/chef/install.sh": true,
@ -85,7 +86,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
"NoProxy": { "NoProxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"no_proxy": []interface{}{"http://local.local", "http://local.org"}, "no_proxy": []interface{}{"http://local.local", "http://local.org"},
"node_name": "nodename1", "node_name": "nodename1",
@ -94,7 +95,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " + "http_proxy='http://proxy.local' no_proxy='http://local.local,http://local.org' " +
@ -107,7 +108,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
"Version": { "Version": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -115,7 +116,7 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"version": "11.18.6", "version": "11.18.6",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"curl -LO https://www.chef.io/chef/install.sh": true, "curl -LO https://www.chef.io/chef/install.sh": true,
@ -125,14 +126,15 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
for k, tc := range cases { for k, tc := range cases {
c.Commands = tc.Commands c.Commands = tc.Commands
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
@ -148,12 +150,12 @@ func TestResourceProvider_linuxInstallChefClient(t *testing.T) {
func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) { func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
Commands map[string]bool Commands map[string]bool
Uploads map[string]string Uploads map[string]string
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -161,7 +163,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"sudo mkdir -p " + linuxConfDir: true, "sudo mkdir -p " + linuxConfDir: true,
@ -188,7 +190,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
}, },
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -196,7 +198,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"mkdir -p " + linuxConfDir: true, "mkdir -p " + linuxConfDir: true,
@ -211,7 +213,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
}, },
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"no_proxy": []interface{}{"http://local.local", "https://local.local"}, "no_proxy": []interface{}{"http://local.local", "https://local.local"},
@ -223,7 +225,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
"ssl_verify_mode": "verify_none", "ssl_verify_mode": "verify_none",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"mkdir -p " + linuxConfDir: true, "mkdir -p " + linuxConfDir: true,
@ -238,7 +240,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
}, },
"Attributes JSON": { "Attributes JSON": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`,
"node_name": "nodename1", "node_name": "nodename1",
@ -248,7 +250,7 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"mkdir -p " + linuxConfDir: true, "mkdir -p " + linuxConfDir: true,
@ -264,7 +266,6 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
@ -272,7 +273,9 @@ func TestResourceProvider_linuxCreateConfigFiles(t *testing.T) {
c.Commands = tc.Commands c.Commands = tc.Commands
c.Uploads = tc.Uploads c.Uploads = tc.Uploads
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }

View File

@ -2,6 +2,7 @@ package chef
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -17,10 +18,10 @@ import (
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/communicator/remote" "github.com/hashicorp/terraform/communicator/remote"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/mitchellh/go-linereader" "github.com/mitchellh/go-linereader"
"github.com/mitchellh/mapstructure"
) )
const ( const (
@ -81,81 +82,196 @@ enable_reporting false
{{ end }} {{ end }}
` `
// Provisioner represents a Chef provisioner type provisionFn func(terraform.UIOutput, communicator.Communicator) error
type Provisioner struct {
AttributesJSON string `mapstructure:"attributes_json"`
ClientOptions []string `mapstructure:"client_options"`
DisableReporting bool `mapstructure:"disable_reporting"`
Environment string `mapstructure:"environment"`
FetchChefCertificates bool `mapstructure:"fetch_chef_certificates"`
LogToFile bool `mapstructure:"log_to_file"`
UsePolicyfile bool `mapstructure:"use_policyfile"`
PolicyGroup string `mapstructure:"policy_group"`
PolicyName string `mapstructure:"policy_name"`
HTTPProxy string `mapstructure:"http_proxy"`
HTTPSProxy string `mapstructure:"https_proxy"`
NamedRunList string `mapstructure:"named_run_list"`
NOProxy []string `mapstructure:"no_proxy"`
NodeName string `mapstructure:"node_name"`
OhaiHints []string `mapstructure:"ohai_hints"`
OSType string `mapstructure:"os_type"`
RecreateClient bool `mapstructure:"recreate_client"`
PreventSudo bool `mapstructure:"prevent_sudo"`
RunList []string `mapstructure:"run_list"`
SecretKey string `mapstructure:"secret_key"`
ServerURL string `mapstructure:"server_url"`
SkipInstall bool `mapstructure:"skip_install"`
SkipRegister bool `mapstructure:"skip_register"`
SSLVerifyMode string `mapstructure:"ssl_verify_mode"`
UserName string `mapstructure:"user_name"`
UserKey string `mapstructure:"user_key"`
VaultJSON string `mapstructure:"vault_json"`
Version string `mapstructure:"version"`
attributes map[string]interface{} type provisioner struct {
vaults map[string][]string Attributes map[string]interface{}
ClientOptions []string
DisableReporting bool
Environment string
FetchChefCertificates bool
LogToFile bool
UsePolicyfile bool
PolicyGroup string
PolicyName string
HTTPProxy string
HTTPSProxy string
NamedRunList string
NOProxy []string
NodeName string
OhaiHints []string
OSType string
RecreateClient bool
PreventSudo bool
RunList []string
SecretKey string
ServerURL string
SkipInstall bool
SkipRegister bool
SSLVerifyMode string
UserName string
UserKey string
Vaults map[string][]string
Version string
cleanupUserKeyCmd string cleanupUserKeyCmd string
createConfigFiles func(terraform.UIOutput, communicator.Communicator) error createConfigFiles provisionFn
installChefClient func(terraform.UIOutput, communicator.Communicator) error installChefClient provisionFn
fetchChefCertificates func(terraform.UIOutput, communicator.Communicator) error fetchChefCertificates provisionFn
generateClientKey func(terraform.UIOutput, communicator.Communicator) error generateClientKey provisionFn
configureVaults func(terraform.UIOutput, communicator.Communicator) error configureVaults provisionFn
runChefClient func(terraform.UIOutput, communicator.Communicator) error runChefClient provisionFn
useSudo bool useSudo bool
// Deprecated Fields
ValidationClientName string `mapstructure:"validation_client_name"`
ValidationKey string `mapstructure:"validation_key"`
} }
// ResourceProvisioner represents a generic chef provisioner // Provisioner returns a Chef provisioner
type ResourceProvisioner struct{} func Provisioner() terraform.ResourceProvisioner {
return &schema.Provisioner{
Schema: map[string]*schema.Schema{
"node_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"server_url": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"user_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"user_key": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
func (r *ResourceProvisioner) Stop() error { "attributes_json": &schema.Schema{
// Noop for now. TODO in the future. Type: schema.TypeString,
return nil Optional: true,
},
"client_options": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"disable_reporting": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"environment": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: defaultEnv,
},
"fetch_chef_certificates": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"log_to_file": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"use_policyfile": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"policy_group": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"policy_name": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"http_proxy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"https_proxy": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"no_proxy": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"named_run_list": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"ohai_hints": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"os_type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"recreate_client": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"prevent_sudo": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"run_list": &schema.Schema{
Type: schema.TypeList,
Elem: &schema.Schema{Type: schema.TypeString},
Optional: true,
},
"secret_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"skip_install": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"skip_register": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
},
"ssl_verify_mode": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"vault_json": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
"version": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},
},
ApplyFunc: applyFn,
ValidateFunc: validateFn,
}
} }
// Apply executes the file provisioner // TODO: Support context cancelling (Provisioner Stop)
func (r *ResourceProvisioner) Apply( func applyFn(ctx context.Context) error {
o terraform.UIOutput, o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
s *terraform.InstanceState, d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
c *terraform.ResourceConfig) error {
// Decode the raw config for this provisioner // Decode the raw config for this provisioner
p, err := r.decodeConfig(c) p, err := decodeConfig(d)
if err != nil { if err != nil {
return err return err
} }
if p.OSType == "" { if p.OSType == "" {
switch s.Ephemeral.ConnInfo["type"] { switch t := d.State().Ephemeral.ConnInfo["type"]; t {
case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh case "ssh", "": // The default connection type is ssh, so if the type is empty assume ssh
p.OSType = "linux" p.OSType = "linux"
case "winrm": case "winrm":
p.OSType = "windows" p.OSType = "windows"
default: default:
return fmt.Errorf("Unsupported connection type: %s", s.Ephemeral.ConnInfo["type"]) return fmt.Errorf("Unsupported connection type: %s", t)
} }
} }
@ -169,7 +285,7 @@ func (r *ResourceProvisioner) Apply(
p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput) p.generateClientKey = p.generateClientKeyFunc(linuxKnifeCmd, linuxConfDir, linuxNoOutput)
p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir) p.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, linuxConfDir)
p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir) p.runChefClient = p.runChefClientFunc(linuxChefCmd, linuxConfDir)
p.useSudo = !p.PreventSudo && s.Ephemeral.ConnInfo["user"] != "root" p.useSudo = !p.PreventSudo && d.State().Ephemeral.ConnInfo["user"] != "root"
case "windows": case "windows":
p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem") p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem")
p.createConfigFiles = p.windowsCreateConfigFiles p.createConfigFiles = p.windowsCreateConfigFiles
@ -184,15 +300,14 @@ func (r *ResourceProvisioner) Apply(
} }
// Get a new communicator // Get a new communicator
comm, err := communicator.New(s) comm, err := communicator.New(d.State())
if err != nil { if err != nil {
return err return err
} }
// Wait and retry until we establish the connection // Wait and retry until we establish the connection
err = retryFunc(comm.Timeout(), func() error { err = retryFunc(comm.Timeout(), func() error {
err := comm.Connect(o) return comm.Connect(o)
return err
}) })
if err != nil { if err != nil {
return err return err
@ -234,7 +349,7 @@ func (r *ResourceProvisioner) Apply(
} }
} }
if p.VaultJSON != "" { if p.Vaults != nil {
o.Output("Configure Chef vaults...") o.Output("Configure Chef vaults...")
if err := p.configureVaults(o, comm); err != nil { if err := p.configureVaults(o, comm); err != nil {
return err return err
@ -253,152 +368,27 @@ func (r *ResourceProvisioner) Apply(
return nil return nil
} }
// Validate checks if the required arguments are configured func validateFn(d *schema.ResourceData) (ws []string, es []error) {
func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) { p, err := decodeConfig(d)
p, err := r.decodeConfig(c)
if err != nil { if err != nil {
es = append(es, err) es = append(es, err)
return ws, es return ws, es
} }
if p.NodeName == "" {
es = append(es, errors.New("Key not found: node_name"))
}
if !p.UsePolicyfile && p.RunList == nil { if !p.UsePolicyfile && p.RunList == nil {
es = append(es, errors.New("Key not found: run_list")) es = append(es, errors.New("Key not found: run_list"))
} }
if p.ServerURL == "" {
es = append(es, errors.New("Key not found: server_url"))
}
if p.UsePolicyfile && p.PolicyName == "" { if p.UsePolicyfile && p.PolicyName == "" {
es = append(es, errors.New("Policyfile enabled but key not found: policy_name")) es = append(es, errors.New("Policyfile enabled but key not found: policy_name"))
} }
if p.UsePolicyfile && p.PolicyGroup == "" { if p.UsePolicyfile && p.PolicyGroup == "" {
es = append(es, errors.New("Policyfile enabled but key not found: policy_group")) es = append(es, errors.New("Policyfile enabled but key not found: policy_group"))
} }
if p.UserName == "" && p.ValidationClientName == "" {
es = append(es, errors.New(
"One of user_name or the deprecated validation_client_name must be provided"))
}
if p.UserKey == "" && p.ValidationKey == "" {
es = append(es, errors.New(
"One of user_key or the deprecated validation_key must be provided"))
}
if p.ValidationClientName != "" {
ws = append(ws, "validation_client_name is deprecated, please use user_name instead")
}
if p.ValidationKey != "" {
ws = append(ws, "validation_key is deprecated, please use user_key instead")
if p.RecreateClient {
es = append(es, errors.New(
"Cannot use recreate_client=true with the deprecated validation_key, please provide a user_key"))
}
if p.VaultJSON != "" {
es = append(es, errors.New(
"Cannot configure chef vaults using the deprecated validation_key, please provide a user_key"))
}
}
return ws, es return ws, es
} }
func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provisioner, error) { func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator.Communicator, confDir string) error {
p := new(Provisioner)
decConf := &mapstructure.DecoderConfig{
ErrorUnused: true,
WeaklyTypedInput: true,
Result: p,
}
dec, err := mapstructure.NewDecoder(decConf)
if err != nil {
return nil, err
}
// We need to merge both configs into a single map first. Order is
// important as we need to make sure interpolated values are used
// over raw values. This makes sure that all values are there even
// if some still need to be interpolated later on. Without this
// the validation will fail when using a variable for a required
// parameter (the node_name for example).
m := make(map[string]interface{})
for k, v := range c.Raw {
m[k] = v
}
for k, v := range c.Config {
m[k] = v
}
if err := dec.Decode(m); err != nil {
return nil, err
}
// Make sure the supplied URL has a trailing slash
p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/"
if p.Environment == "" {
p.Environment = defaultEnv
}
for i, hint := range p.OhaiHints {
hintPath, err := homedir.Expand(hint)
if err != nil {
return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err)
}
p.OhaiHints[i] = hintPath
}
if p.UserName == "" && p.ValidationClientName != "" {
p.UserName = p.ValidationClientName
}
if p.UserKey == "" && p.ValidationKey != "" {
p.UserKey = p.ValidationKey
}
if attrs, ok := c.Config["attributes_json"].(string); ok && !c.IsComputed("attributes_json") {
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrs), &m); err != nil {
return nil, fmt.Errorf("Error parsing attributes_json: %v", err)
}
p.attributes = m
}
if vaults, ok := c.Config["vault_json"].(string); ok && !c.IsComputed("vault_json") {
var m map[string]interface{}
if err := json.Unmarshal([]byte(vaults), &m); err != nil {
return nil, fmt.Errorf("Error parsing vault_json: %v", err)
}
v := make(map[string][]string)
for vault, items := range m {
switch items := items.(type) {
case []interface{}:
for _, item := range items {
if item, ok := item.(string); ok {
v[vault] = append(v[vault], item)
}
}
case interface{}:
if item, ok := items.(string); ok {
v[vault] = append(v[vault], item)
}
}
}
p.vaults = v
}
return p, nil
}
func (p *Provisioner) deployConfigFiles(
o terraform.UIOutput,
comm communicator.Communicator,
confDir string) error {
// Copy the user key to the new instance // Copy the user key to the new instance
pk := strings.NewReader(p.UserKey) pk := strings.NewReader(p.UserKey)
if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil { if err := comm.Upload(path.Join(confDir, p.UserName+".pem"), pk); err != nil {
@ -433,14 +423,14 @@ func (p *Provisioner) deployConfigFiles(
} }
// Copy the client config to the new instance // Copy the client config to the new instance
if err := comm.Upload(path.Join(confDir, clienrb), &buf); err != nil { if err = comm.Upload(path.Join(confDir, clienrb), &buf); err != nil {
return fmt.Errorf("Uploading %s failed: %v", clienrb, err) return fmt.Errorf("Uploading %s failed: %v", clienrb, err)
} }
// Create a map with first boot settings // Create a map with first boot settings
fb := make(map[string]interface{}) fb := make(map[string]interface{})
if p.attributes != nil { if p.Attributes != nil {
fb = p.attributes fb = p.Attributes
} }
// Check if the run_list was also in the attributes and if so log a warning // Check if the run_list was also in the attributes and if so log a warning
@ -469,10 +459,7 @@ func (p *Provisioner) deployConfigFiles(
return nil return nil
} }
func (p *Provisioner) deployOhaiHints( func (p *provisioner) deployOhaiHints(o terraform.UIOutput, comm communicator.Communicator, hintDir string) error {
o terraform.UIOutput,
comm communicator.Communicator,
hintDir string) error {
for _, hint := range p.OhaiHints { for _, hint := range p.OhaiHints {
// Open the hint file // Open the hint file
f, err := os.Open(hint) f, err := os.Open(hint)
@ -490,7 +477,7 @@ func (p *Provisioner) deployOhaiHints(
return nil return nil
} }
func (p *Provisioner) fetchChefCertificatesFunc( func (p *provisioner) fetchChefCertificatesFunc(
knifeCmd string, knifeCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error { confDir string) func(terraform.UIOutput, communicator.Communicator) error {
return func(o terraform.UIOutput, comm communicator.Communicator) error { return func(o terraform.UIOutput, comm communicator.Communicator) error {
@ -501,10 +488,7 @@ func (p *Provisioner) fetchChefCertificatesFunc(
} }
} }
func (p *Provisioner) generateClientKeyFunc( func (p *provisioner) generateClientKeyFunc(knifeCmd string, confDir string, noOutput string) provisionFn {
knifeCmd string,
confDir string,
noOutput string) func(terraform.UIOutput, communicator.Communicator) error {
return func(o terraform.UIOutput, comm communicator.Communicator) error { return func(o terraform.UIOutput, comm communicator.Communicator) error {
options := fmt.Sprintf("-c %s -u %s --key %s", options := fmt.Sprintf("-c %s -u %s --key %s",
path.Join(confDir, clienrb), path.Join(confDir, clienrb),
@ -562,10 +546,7 @@ func (p *Provisioner) generateClientKeyFunc(
} }
} }
func (p *Provisioner) configureVaultsFunc( func (p *provisioner) configureVaultsFunc(gemCmd string, knifeCmd string, confDir string) provisionFn {
gemCmd string,
knifeCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error {
return func(o terraform.UIOutput, comm communicator.Communicator) error { return func(o terraform.UIOutput, comm communicator.Communicator) error {
if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil { if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil {
return err return err
@ -577,7 +558,7 @@ func (p *Provisioner) configureVaultsFunc(
path.Join(confDir, p.UserName+".pem"), path.Join(confDir, p.UserName+".pem"),
) )
for vault, items := range p.vaults { for vault, items := range p.Vaults {
for _, item := range items { for _, item := range items {
updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s", updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s",
knifeCmd, knifeCmd,
@ -596,9 +577,7 @@ func (p *Provisioner) configureVaultsFunc(
} }
} }
func (p *Provisioner) runChefClientFunc( func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn {
chefCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error {
return func(o terraform.UIOutput, comm communicator.Communicator) error { return func(o terraform.UIOutput, comm communicator.Communicator) error {
fb := path.Join(confDir, firstBoot) fb := path.Join(confDir, firstBoot)
var cmd string var cmd string
@ -634,7 +613,7 @@ func (p *Provisioner) runChefClientFunc(
} }
// Output implementation of terraform.UIOutput interface // Output implementation of terraform.UIOutput interface
func (p *Provisioner) Output(output string) { func (p *provisioner) Output(output string) {
logFile := path.Join(logfileDir, p.NodeName) logFile := path.Join(logfileDir, p.NodeName)
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666) f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666)
if err != nil { if err != nil {
@ -660,10 +639,7 @@ func (p *Provisioner) Output(output string) {
} }
// runCommand is used to run already prepared commands // runCommand is used to run already prepared commands
func (p *Provisioner) runCommand( func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error {
o terraform.UIOutput,
comm communicator.Communicator,
command string) error {
// Unless prevented, prefix the command with sudo // Unless prevented, prefix the command with sudo
if p.useSudo { if p.useSudo {
command = "sudo " + command command = "sudo " + command
@ -702,7 +678,7 @@ func (p *Provisioner) runCommand(
return err return err
} }
func (p *Provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) { func (p *provisioner) copyOutput(o terraform.UIOutput, r io.Reader, doneCh chan<- struct{}) {
defer close(doneCh) defer close(doneCh)
lr := linereader.New(r) lr := linereader.New(r)
for line := range lr.Ch { for line := range lr.Ch {
@ -727,3 +703,98 @@ func retryFunc(timeout time.Duration, f func() error) error {
} }
} }
} }
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
p := &provisioner{
ClientOptions: getStringList(d.Get("client_options")),
DisableReporting: d.Get("disable_reporting").(bool),
Environment: d.Get("environment").(string),
FetchChefCertificates: d.Get("fetch_chef_certificates").(bool),
LogToFile: d.Get("log_to_file").(bool),
UsePolicyfile: d.Get("use_policyfile").(bool),
PolicyGroup: d.Get("policy_group").(string),
PolicyName: d.Get("policy_name").(string),
HTTPProxy: d.Get("http_proxy").(string),
HTTPSProxy: d.Get("https_proxy").(string),
NOProxy: getStringList(d.Get("no_proxy")),
NamedRunList: d.Get("named_run_list").(string),
NodeName: d.Get("node_name").(string),
OhaiHints: getStringList(d.Get("ohai_hints")),
OSType: d.Get("os_type").(string),
RecreateClient: d.Get("recreate_client").(bool),
PreventSudo: d.Get("prevent_sudo").(bool),
RunList: getStringList(d.Get("run_list")),
SecretKey: d.Get("secret_key").(string),
ServerURL: d.Get("server_url").(string),
SkipInstall: d.Get("skip_install").(bool),
SkipRegister: d.Get("skip_register").(bool),
SSLVerifyMode: d.Get("ssl_verify_mode").(string),
UserName: d.Get("user_name").(string),
UserKey: d.Get("user_key").(string),
Version: d.Get("version").(string),
}
// Make sure the supplied URL has a trailing slash
p.ServerURL = strings.TrimSuffix(p.ServerURL, "/") + "/"
for i, hint := range p.OhaiHints {
hintPath, err := homedir.Expand(hint)
if err != nil {
return nil, fmt.Errorf("Error expanding the path %s: %v", hint, err)
}
p.OhaiHints[i] = hintPath
}
if attrs, ok := d.GetOk("attributes_json"); ok {
var m map[string]interface{}
if err := json.Unmarshal([]byte(attrs.(string)), &m); err != nil {
return nil, fmt.Errorf("Error parsing attributes_json: %v", err)
}
p.Attributes = m
}
if vaults, ok := d.GetOk("vault_json"); ok {
var m map[string]interface{}
if err := json.Unmarshal([]byte(vaults.(string)), &m); err != nil {
return nil, fmt.Errorf("Error parsing vault_json: %v", err)
}
v := make(map[string][]string)
for vault, items := range m {
switch items := items.(type) {
case []interface{}:
for _, item := range items {
if item, ok := item.(string); ok {
v[vault] = append(v[vault], item)
}
}
case interface{}:
if item, ok := items.(string); ok {
v[vault] = append(v[vault], item)
}
}
}
p.Vaults = v
}
return p, nil
}
func getStringList(v interface{}) []string {
if v == nil {
return nil
}
switch l := v.(type) {
case []string:
return l
case []interface{}:
arr := make([]string, len(l))
for i, x := range l {
arr[i] = x.(string)
}
return arr
default:
panic(fmt.Sprintf("Unsupported type: %T", v))
}
}

View File

@ -7,11 +7,18 @@ import (
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvisioner_impl(t *testing.T) { func TestResourceProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = new(ResourceProvisioner) var _ terraform.ResourceProvisioner = Provisioner()
}
func TestProvisioner(t *testing.T) {
if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
} }
func TestResourceProvider_Validate_good(t *testing.T) { func TestResourceProvider_Validate_good(t *testing.T) {
@ -23,8 +30,8 @@ func TestResourceProvider_Validate_good(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}) })
r := new(ResourceProvisioner)
warn, errs := r.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -37,8 +44,8 @@ func TestResourceProvider_Validate_bad(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"invalid": "nope", "invalid": "nope",
}) })
p := new(ResourceProvisioner)
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -59,8 +66,8 @@ func TestResourceProvider_Validate_computedValues(t *testing.T) {
"user_key": "USER-KEY", "user_key": "USER-KEY",
"attributes_json": config.UnknownVariableValue, "attributes_json": config.UnknownVariableValue,
}) })
r := new(ResourceProvisioner)
warn, errs := r.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -69,30 +76,21 @@ func TestResourceProvider_Validate_computedValues(t *testing.T) {
} }
} }
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c)
if err != nil {
t.Fatalf("bad: %s", err)
}
return terraform.NewResourceConfig(r)
}
func TestResourceProvider_runChefClient(t *testing.T) { func TestResourceProvider_runChefClient(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
ChefCmd string ChefCmd string
ConfDir string ConfDir string
Commands map[string]bool Commands map[string]bool
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
ChefCmd: linuxChefCmd, ChefCmd: linuxChefCmd,
@ -106,14 +104,14 @@ func TestResourceProvider_runChefClient(t *testing.T) {
}, },
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
ChefCmd: linuxChefCmd, ChefCmd: linuxChefCmd,
@ -127,7 +125,7 @@ func TestResourceProvider_runChefClient(t *testing.T) {
}, },
"Environment": { "Environment": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"environment": "production", "environment": "production",
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -135,7 +133,7 @@ func TestResourceProvider_runChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
ChefCmd: windowsChefCmd, ChefCmd: windowsChefCmd,
@ -149,14 +147,15 @@ func TestResourceProvider_runChefClient(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
for k, tc := range cases { for k, tc := range cases {
c.Commands = tc.Commands c.Commands = tc.Commands
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
@ -173,20 +172,20 @@ func TestResourceProvider_runChefClient(t *testing.T) {
func TestResourceProvider_fetchChefCertificates(t *testing.T) { func TestResourceProvider_fetchChefCertificates(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
KnifeCmd string KnifeCmd string
ConfDir string ConfDir string
Commands map[string]bool Commands map[string]bool
}{ }{
"Sudo": { "Sudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"fetch_chef_certificates": true, "fetch_chef_certificates": true,
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
KnifeCmd: linuxKnifeCmd, KnifeCmd: linuxKnifeCmd,
@ -200,7 +199,7 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
}, },
"NoSudo": { "NoSudo": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"fetch_chef_certificates": true, "fetch_chef_certificates": true,
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -208,7 +207,7 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
KnifeCmd: windowsKnifeCmd, KnifeCmd: windowsKnifeCmd,
@ -222,14 +221,15 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
for k, tc := range cases { for k, tc := range cases {
c.Commands = tc.Commands c.Commands = tc.Commands
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
@ -246,14 +246,14 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
func TestResourceProvider_configureVaults(t *testing.T) { func TestResourceProvider_configureVaults(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
GemCmd string GemCmd string
KnifeCmd string KnifeCmd string
ConfDir string ConfDir string
Commands map[string]bool Commands map[string]bool
}{ }{
"Linux Vault string": { "Linux Vault string": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -261,7 +261,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"vault_json": `{"vault1": "item1"}`, "vault_json": `{"vault1": "item1"}`,
}), },
GemCmd: linuxGemCmd, GemCmd: linuxGemCmd,
KnifeCmd: linuxKnifeCmd, KnifeCmd: linuxKnifeCmd,
@ -275,7 +275,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
}, },
"Linux Vault []string": { "Linux Vault []string": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"fetch_chef_certificates": true, "fetch_chef_certificates": true,
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -284,7 +284,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"vault_json": `{"vault1": ["item1", "item2"]}`, "vault_json": `{"vault1": ["item1", "item2"]}`,
}), },
GemCmd: linuxGemCmd, GemCmd: linuxGemCmd,
KnifeCmd: linuxKnifeCmd, KnifeCmd: linuxKnifeCmd,
@ -300,7 +300,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
}, },
"Windows Vault string": { "Windows Vault string": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -308,7 +308,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"vault_json": `{"vault1": "item1"}`, "vault_json": `{"vault1": "item1"}`,
}), },
GemCmd: windowsGemCmd, GemCmd: windowsGemCmd,
KnifeCmd: windowsKnifeCmd, KnifeCmd: windowsKnifeCmd,
@ -322,7 +322,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
}, },
"Windows Vault []string": { "Windows Vault []string": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"fetch_chef_certificates": true, "fetch_chef_certificates": true,
"node_name": "nodename1", "node_name": "nodename1",
"prevent_sudo": true, "prevent_sudo": true,
@ -331,7 +331,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"vault_json": `{"vault1": ["item1", "item2"]}`, "vault_json": `{"vault1": ["item1", "item2"]}`,
}), },
GemCmd: windowsGemCmd, GemCmd: windowsGemCmd,
KnifeCmd: windowsKnifeCmd, KnifeCmd: windowsKnifeCmd,
@ -347,14 +347,15 @@ func TestResourceProvider_configureVaults(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
for k, tc := range cases { for k, tc := range cases {
c.Commands = tc.Commands c.Commands = tc.Commands
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
@ -368,3 +369,12 @@ func TestResourceProvider_configureVaults(t *testing.T) {
} }
} }
} }
func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c)
if err != nil {
t.Fatalf("bad: %s", err)
}
return terraform.NewResourceConfig(r)
}

View File

@ -46,9 +46,7 @@ Write-Host 'Installing Chef Client...'
Start-Process -FilePath msiexec -ArgumentList /qn, /i, $dest -Wait Start-Process -FilePath msiexec -ArgumentList /qn, /i, $dest -Wait
` `
func (p *Provisioner) windowsInstallChefClient( func (p *provisioner) windowsInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
o terraform.UIOutput,
comm communicator.Communicator) error {
script := path.Join(path.Dir(comm.ScriptPath()), "ChefClient.ps1") script := path.Join(path.Dir(comm.ScriptPath()), "ChefClient.ps1")
content := fmt.Sprintf(installScript, p.Version, p.HTTPProxy, strings.Join(p.NOProxy, ",")) content := fmt.Sprintf(installScript, p.Version, p.HTTPProxy, strings.Join(p.NOProxy, ","))
@ -62,9 +60,7 @@ func (p *Provisioner) windowsInstallChefClient(
return p.runCommand(o, comm, installCmd) return p.runCommand(o, comm, installCmd)
} }
func (p *Provisioner) windowsCreateConfigFiles( func (p *provisioner) windowsCreateConfigFiles(o terraform.UIOutput, comm communicator.Communicator) error {
o terraform.UIOutput,
comm communicator.Communicator) error {
// Make sure the config directory exists // Make sure the config directory exists
cmd := fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir) cmd := fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir)
if err := p.runCommand(o, comm, cmd); err != nil { if err := p.runCommand(o, comm, cmd); err != nil {

View File

@ -6,23 +6,24 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/communicator" "github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvider_windowsInstallChefClient(t *testing.T) { func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
Commands map[string]bool Commands map[string]bool
UploadScripts map[string]string UploadScripts map[string]string
}{ }{
"Default": { "Default": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true, "powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true,
@ -34,7 +35,7 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
}, },
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"no_proxy": []interface{}{"http://local.local", "http://local.org"}, "no_proxy": []interface{}{"http://local.local", "http://local.org"},
"node_name": "nodename1", "node_name": "nodename1",
@ -42,7 +43,7 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true, "powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true,
@ -54,14 +55,14 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
}, },
"Version": { "Version": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
"version": "11.18.6", "version": "11.18.6",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
"powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true, "powershell -NoProfile -ExecutionPolicy Bypass -File ChefClient.ps1": true,
@ -73,7 +74,6 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
@ -81,7 +81,9 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
c.Commands = tc.Commands c.Commands = tc.Commands
c.UploadScripts = tc.UploadScripts c.UploadScripts = tc.UploadScripts
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }
@ -97,12 +99,12 @@ func TestResourceProvider_windowsInstallChefClient(t *testing.T) {
func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) { func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
Config *terraform.ResourceConfig Config map[string]interface{}
Commands map[string]bool Commands map[string]bool
Uploads map[string]string Uploads map[string]string
}{ }{
"Default": { "Default": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"ohai_hints": []interface{}{"test-fixtures/ohaihint.json"}, "ohai_hints": []interface{}{"test-fixtures/ohaihint.json"},
"node_name": "nodename1", "node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"}, "run_list": []interface{}{"cookbook::recipe"},
@ -110,7 +112,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true, fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true,
@ -129,7 +131,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
}, },
"Proxy": { "Proxy": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"http_proxy": "http://proxy.local", "http_proxy": "http://proxy.local",
"https_proxy": "https://proxy.local", "https_proxy": "https://proxy.local",
"no_proxy": []interface{}{"http://local.local", "https://local.local"}, "no_proxy": []interface{}{"http://local.local", "https://local.local"},
@ -140,7 +142,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
"ssl_verify_mode": "verify_none", "ssl_verify_mode": "verify_none",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true, fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true,
@ -155,7 +157,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
}, },
"Attributes JSON": { "Attributes JSON": {
Config: testConfig(t, map[string]interface{}{ Config: map[string]interface{}{
"attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` + "attributes_json": `{"key1":{"subkey1":{"subkey2a":["val1","val2","val3"],` +
`"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`, `"subkey2b":{"subkey3":"value3"}}},"key2":"value2"}`,
"node_name": "nodename1", "node_name": "nodename1",
@ -164,7 +166,7 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
"server_url": "https://chef.local", "server_url": "https://chef.local",
"user_name": "bob", "user_name": "bob",
"user_key": "USER-KEY", "user_key": "USER-KEY",
}), },
Commands: map[string]bool{ Commands: map[string]bool{
fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true, fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir): true,
@ -180,7 +182,6 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
}, },
} }
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput) o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator) c := new(communicator.MockCommunicator)
@ -188,7 +189,9 @@ func TestResourceProvider_windowsCreateConfigFiles(t *testing.T) {
c.Commands = tc.Commands c.Commands = tc.Commands
c.Uploads = tc.Uploads c.Uploads = tc.Uploads
p, err := r.decodeConfig(tc.Config) p, err := decodeConfig(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
)
if err != nil { if err != nil {
t.Fatalf("Error: %v", err) t.Fatalf("Error: %v", err)
} }

View File

@ -36,6 +36,7 @@ func Provisioner() terraform.ResourceProvisioner {
}, },
ApplyFunc: applyFn, ApplyFunc: applyFn,
ValidateFunc: validateFn,
} }
} }
@ -77,6 +78,20 @@ func applyFn(ctx context.Context) error {
} }
} }
func validateFn(d *schema.ResourceData) (ws []string, es []error) {
numSrc := 0
if _, ok := d.GetOk("source"); ok == true {
numSrc++
}
if _, ok := d.GetOk("content"); ok == true {
numSrc++
}
if numSrc != 1 {
es = append(es, fmt.Errorf("Must provide one of 'content' or 'source'"))
}
return
}
// getSrc returns the file to use as source // getSrc returns the file to use as source
func getSrc(data *schema.ResourceData) (string, bool, error) { func getSrc(data *schema.ResourceData) (string, bool, error) {
src := data.Get("source").(string) src := data.Get("source").(string)

View File

@ -4,16 +4,27 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = Provisioner()
}
func TestProvisioner(t *testing.T) {
if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestResourceProvider_Validate_good_source(t *testing.T) { func TestResourceProvider_Validate_good_source(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"source": "/tmp/foo", "source": "/tmp/foo",
"destination": "/tmp/bar", "destination": "/tmp/bar",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -27,8 +38,8 @@ func TestResourceProvider_Validate_good_content(t *testing.T) {
"content": "value to copy", "content": "value to copy",
"destination": "/tmp/bar", "destination": "/tmp/bar",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -41,8 +52,22 @@ func TestResourceProvider_Validate_bad_not_destination(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"source": "nope", "source": "nope",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
if len(errs) == 0 {
t.Fatalf("Should have errors")
}
}
func TestResourceProvider_Validate_bad_no_source(t *testing.T) {
c := testConfig(t, map[string]interface{}{
"destination": "/tmp/bar",
})
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -57,8 +82,8 @@ func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
"content": "value to copy", "content": "value to copy",
"destination": "/tmp/bar", "destination": "/tmp/bar",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -67,12 +92,11 @@ func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
} }
} }
func testConfig( func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
t *testing.T,
c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c) r, err := config.NewRawConfig(c)
if err != nil { if err != nil {
t.Fatalf("bad: %s", err) t.Fatalf("bad: %s", err)
} }
return terraform.NewResourceConfig(r) return terraform.NewResourceConfig(r)
} }

View File

@ -8,9 +8,20 @@ import (
"time" "time"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = Provisioner()
}
func TestProvisioner(t *testing.T) {
if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestResourceProvider_Apply(t *testing.T) { func TestResourceProvider_Apply(t *testing.T) {
defer os.Remove("test_out") defer os.Remove("test_out")
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
@ -19,6 +30,7 @@ func TestResourceProvider_Apply(t *testing.T) {
output := new(terraform.MockUIOutput) output := new(terraform.MockUIOutput)
p := Provisioner() p := Provisioner()
if err := p.Apply(output, nil, c); err != nil { if err := p.Apply(output, nil, c); err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -73,8 +85,8 @@ func TestResourceProvider_Validate_good(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"command": "echo foo", "command": "echo foo",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -85,8 +97,8 @@ func TestResourceProvider_Validate_good(t *testing.T) {
func TestResourceProvider_Validate_missing(t *testing.T) { func TestResourceProvider_Validate_missing(t *testing.T) {
c := testConfig(t, map[string]interface{}{}) c := testConfig(t, map[string]interface{}{})
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -95,9 +107,7 @@ func TestResourceProvider_Validate_missing(t *testing.T) {
} }
} }
func testConfig( func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
t *testing.T,
c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c) r, err := config.NewRawConfig(c)
if err != nil { if err != nil {
t.Fatalf("bad: %s", err) t.Fatalf("bad: %s", err)

View File

@ -16,12 +16,22 @@ import (
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestResourceProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = Provisioner()
}
func TestProvisioner(t *testing.T) {
if err := Provisioner().(*schema.Provisioner).InternalValidate(); err != nil {
t.Fatalf("err: %s", err)
}
}
func TestResourceProvider_Validate_good(t *testing.T) { func TestResourceProvider_Validate_good(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"inline": "echo foo", "inline": "echo foo",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -34,8 +44,8 @@ func TestResourceProvider_Validate_bad(t *testing.T) {
c := testConfig(t, map[string]interface{}{ c := testConfig(t, map[string]interface{}{
"invalid": "nope", "invalid": "nope",
}) })
p := Provisioner()
warn, errs := p.Validate(c) warn, errs := Provisioner().Validate(c)
if len(warn) > 0 { if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn) t.Fatalf("Warnings: %v", warn)
} }
@ -50,7 +60,6 @@ exit 0
` `
func TestResourceProvider_generateScript(t *testing.T) { func TestResourceProvider_generateScript(t *testing.T) {
p := Provisioner().(*schema.Provisioner)
conf := map[string]interface{}{ conf := map[string]interface{}{
"inline": []interface{}{ "inline": []interface{}{
"cd /tmp", "cd /tmp",
@ -58,8 +67,10 @@ func TestResourceProvider_generateScript(t *testing.T) {
"exit 0", "exit 0",
}, },
} }
out, err := generateScripts(schema.TestResourceDataRaw(
t, p.Schema, conf)) out, err := generateScripts(
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf),
)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -91,7 +102,6 @@ func TestResourceProvider_generateScriptEmptyInline(t *testing.T) {
} }
func TestResourceProvider_CollectScripts_inline(t *testing.T) { func TestResourceProvider_CollectScripts_inline(t *testing.T) {
p := Provisioner().(*schema.Provisioner)
conf := map[string]interface{}{ conf := map[string]interface{}{
"inline": []interface{}{ "inline": []interface{}{
"cd /tmp", "cd /tmp",
@ -100,8 +110,9 @@ func TestResourceProvider_CollectScripts_inline(t *testing.T) {
}, },
} }
scripts, err := collectScripts(schema.TestResourceDataRaw( scripts, err := collectScripts(
t, p.Schema, conf)) schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf),
)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -122,13 +133,13 @@ func TestResourceProvider_CollectScripts_inline(t *testing.T) {
} }
func TestResourceProvider_CollectScripts_script(t *testing.T) { func TestResourceProvider_CollectScripts_script(t *testing.T) {
p := Provisioner().(*schema.Provisioner)
conf := map[string]interface{}{ conf := map[string]interface{}{
"script": "test-fixtures/script1.sh", "script": "test-fixtures/script1.sh",
} }
scripts, err := collectScripts(schema.TestResourceDataRaw( scripts, err := collectScripts(
t, p.Schema, conf)) schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf),
)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -149,7 +160,6 @@ func TestResourceProvider_CollectScripts_script(t *testing.T) {
} }
func TestResourceProvider_CollectScripts_scripts(t *testing.T) { func TestResourceProvider_CollectScripts_scripts(t *testing.T) {
p := Provisioner().(*schema.Provisioner)
conf := map[string]interface{}{ conf := map[string]interface{}{
"scripts": []interface{}{ "scripts": []interface{}{
"test-fixtures/script1.sh", "test-fixtures/script1.sh",
@ -158,8 +168,9 @@ func TestResourceProvider_CollectScripts_scripts(t *testing.T) {
}, },
} }
scripts, err := collectScripts(schema.TestResourceDataRaw( scripts, err := collectScripts(
t, p.Schema, conf)) schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, conf),
)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -224,9 +235,7 @@ func TestRetryFunc(t *testing.T) {
} }
} }
func testConfig( func testConfig(t *testing.T, c map[string]interface{}) *terraform.ResourceConfig {
t *testing.T,
c map[string]interface{}) *terraform.ResourceConfig {
r, err := config.NewRawConfig(c) r, err := config.NewRawConfig(c)
if err != nil { if err != nil {
t.Fatalf("bad: %s", err) t.Fatalf("bad: %s", err)

View File

@ -75,6 +75,7 @@ import (
vaultprovider "github.com/hashicorp/terraform/builtin/providers/vault" vaultprovider "github.com/hashicorp/terraform/builtin/providers/vault"
vcdprovider "github.com/hashicorp/terraform/builtin/providers/vcd" vcdprovider "github.com/hashicorp/terraform/builtin/providers/vcd"
vsphereprovider "github.com/hashicorp/terraform/builtin/providers/vsphere" vsphereprovider "github.com/hashicorp/terraform/builtin/providers/vsphere"
chefprovisioner "github.com/hashicorp/terraform/builtin/provisioners/chef"
fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file" fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file"
localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec" localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec"
remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec" remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec"
@ -84,9 +85,6 @@ import (
//New Provider Builds //New Provider Builds
opcprovider "github.com/hashicorp/terraform-provider-opc/opc" opcprovider "github.com/hashicorp/terraform-provider-opc/opc"
// Legacy, will remove once it conforms with new structure
chefprovisioner "github.com/hashicorp/terraform/builtin/provisioners/chef"
) )
var InternalProviders = map[string]plugin.ProviderFunc{ var InternalProviders = map[string]plugin.ProviderFunc{
@ -162,16 +160,13 @@ var InternalProviders = map[string]plugin.ProviderFunc{
} }
var InternalProvisioners = map[string]plugin.ProvisionerFunc{ var InternalProvisioners = map[string]plugin.ProvisionerFunc{
"chef": chefprovisioner.Provisioner,
"file": fileprovisioner.Provisioner, "file": fileprovisioner.Provisioner,
"local-exec": localexecprovisioner.Provisioner, "local-exec": localexecprovisioner.Provisioner,
"remote-exec": remoteexecprovisioner.Provisioner, "remote-exec": remoteexecprovisioner.Provisioner,
} }
func init() { func init() {
// Legacy provisioners that don't match our heuristics for auto-finding
// built-in provisioners.
InternalProvisioners["chef"] = func() terraform.ResourceProvisioner { return new(chefprovisioner.ResourceProvisioner) }
// New Provider Layouts // New Provider Layouts
InternalProviders["opc"] = func() terraform.ResourceProvider { return opcprovider.Provider() } InternalProviders["opc"] = func() terraform.ResourceProvider { return opcprovider.Provider() }
} }

View File

@ -41,6 +41,10 @@ type Provisioner struct {
// information. // information.
ApplyFunc func(ctx context.Context) error ApplyFunc func(ctx context.Context) error
// ValidateFunc is a function for extended validation. This is optional
// and should be used when individual field validation is not enough.
ValidateFunc func(*ResourceData) ([]string, []error)
stopCtx context.Context stopCtx context.Context
stopCtxCancel context.CancelFunc stopCtxCancel context.CancelFunc
stopOnce sync.Once stopOnce sync.Once
@ -117,8 +121,30 @@ func (p *Provisioner) Stop() error {
return nil return nil
} }
func (p *Provisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) { func (p *Provisioner) Validate(config *terraform.ResourceConfig) ([]string, []error) {
return schemaMap(p.Schema).Validate(c) if err := p.InternalValidate(); err != nil {
return nil, []error{fmt.Errorf(
"Internal validation of the provisioner failed! This is always a bug\n"+
"with the provisioner itself, and not a user issue. Please report\n"+
"this bug:\n\n%s", err)}
}
w := []string{}
e := []error{}
if p.Schema != nil {
w2, e2 := schemaMap(p.Schema).Validate(config)
w = append(w, w2...)
e = append(e, e2...)
}
if p.ValidateFunc != nil {
data := &ResourceData{
schema: p.Schema,
config: config,
}
w2, e2 := p.ValidateFunc(data)
w = append(w, w2...)
e = append(e, e2...)
}
return w, e
} }
// Apply implementation of terraform.ResourceProvisioner interface. // Apply implementation of terraform.ResourceProvisioner interface.

View File

@ -3,6 +3,7 @@ package schema
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"testing" "testing"
"time" "time"
@ -14,13 +15,35 @@ func TestProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = new(Provisioner) var _ terraform.ResourceProvisioner = new(Provisioner)
} }
func noopApply(ctx context.Context) error {
return nil
}
func TestProvisionerValidate(t *testing.T) { func TestProvisionerValidate(t *testing.T) {
cases := []struct { cases := []struct {
Name string Name string
P *Provisioner P *Provisioner
Config map[string]interface{} Config map[string]interface{}
Err bool Err bool
Warns []string
}{ }{
{
Name: "No ApplyFunc",
P: &Provisioner{},
Config: nil,
Err: true,
},
{
Name: "Incorrect schema",
P: &Provisioner{
Schema: map[string]*Schema{
"foo": {},
},
ApplyFunc: noopApply,
},
Config: nil,
Err: true,
},
{ {
"Basic required field", "Basic required field",
&Provisioner{ &Provisioner{
@ -30,9 +53,11 @@ func TestProvisionerValidate(t *testing.T) {
Type: TypeString, Type: TypeString,
}, },
}, },
ApplyFunc: noopApply,
}, },
nil, nil,
true, true,
nil,
}, },
{ {
@ -44,11 +69,57 @@ func TestProvisionerValidate(t *testing.T) {
Type: TypeString, Type: TypeString,
}, },
}, },
ApplyFunc: noopApply,
}, },
map[string]interface{}{ map[string]interface{}{
"foo": "bar", "foo": "bar",
}, },
false, false,
nil,
},
{
Name: "Warning from property validation",
P: &Provisioner{
Schema: map[string]*Schema{
"foo": {
Type: TypeString,
Optional: true,
ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
ws = append(ws, "Simple warning from property validation")
return
},
},
},
ApplyFunc: noopApply,
},
Config: map[string]interface{}{
"foo": "",
},
Err: false,
Warns: []string{"Simple warning from property validation"},
},
{
Name: "No schema",
P: &Provisioner{
Schema: nil,
ApplyFunc: noopApply,
},
Config: nil,
Err: false,
},
{
Name: "Warning from provisioner ValidateFunc",
P: &Provisioner{
Schema: nil,
ApplyFunc: noopApply,
ValidateFunc: func(*ResourceData) (ws []string, errors []error) {
ws = append(ws, "Simple warning from provisioner ValidateFunc")
return
},
},
Config: nil,
Err: false,
Warns: []string{"Simple warning from provisioner ValidateFunc"},
}, },
} }
@ -59,9 +130,12 @@ func TestProvisionerValidate(t *testing.T) {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
_, es := tc.P.Validate(terraform.NewResourceConfig(c)) ws, es := tc.P.Validate(terraform.NewResourceConfig(c))
if len(es) > 0 != tc.Err { if len(es) > 0 != tc.Err {
t.Fatalf("%d: %#v", i, es) t.Fatalf("%d: %#v %s", i, es, es)
}
if (tc.Warns != nil || len(ws) != 0) && !reflect.DeepEqual(ws, tc.Warns) {
t.Fatalf("%d: warnings mismatch, actual: %#v", i, ws)
} }
}) })
} }

View File

@ -82,12 +82,11 @@ func makeProviderMap(items []plugin) string {
// makeProvisionerMap creates a map of provisioners like this: // makeProvisionerMap creates a map of provisioners like this:
// //
// "file": func() terraform.ResourceProvisioner { return new(file.ResourceProvisioner) }, // "chef": chefprovisioner.Provisioner,
// "local-exec": func() terraform.ResourceProvisioner { return new(localexec.ResourceProvisioner) }, // "file": fileprovisioner.Provisioner,
// "remote-exec": func() terraform.ResourceProvisioner { return new(remoteexec.ResourceProvisioner) }, // "local-exec": localexecprovisioner.Provisioner,
// "remote-exec": remoteexecprovisioner.Provisioner,
// //
// This is more verbose than the Provider case because there is no corresponding
// Provisioner function.
func makeProvisionerMap(items []plugin) string { func makeProvisionerMap(items []plugin) string {
output := "" output := ""
for _, item := range items { for _, item := range items {
@ -273,9 +272,6 @@ IMPORTS
//New Provider Builds //New Provider Builds
opcprovider "github.com/hashicorp/terraform-provider-opc/opc" opcprovider "github.com/hashicorp/terraform-provider-opc/opc"
// Legacy, will remove once it conforms with new structure
chefprovisioner "github.com/hashicorp/terraform/builtin/provisioners/chef"
) )
var InternalProviders = map[string]plugin.ProviderFunc{ var InternalProviders = map[string]plugin.ProviderFunc{
@ -287,12 +283,7 @@ PROVISIONERS
} }
func init() { func init() {
// Legacy provisioners that don't match our heuristics for auto-finding
// built-in provisioners.
InternalProvisioners["chef"] = func() terraform.ResourceProvisioner { return new(chefprovisioner.ResourceProvisioner) }
// New Provider Layouts // New Provider Layouts
InternalProviders["opc"] = func() terraform.ResourceProvider { return opcprovider.Provider() } InternalProviders["opc"] = func() terraform.ResourceProvider { return opcprovider.Provider() }
} }
` `

View File

@ -157,9 +157,3 @@ The following arguments are supported:
* `version (string)` - (Optional) The Chef Client version to install on the remote machine. * `version (string)` - (Optional) The Chef Client version to install on the remote machine.
If not set, the latest available version will be installed. If not set, the latest available version will be installed.
These options are supported for backwards compatibility and may be removed in a
future version:
* `validation_client_name (string)` - __Deprecated: please use `user_name` instead__.
* `validation_key (string)` - __Deprecated: please use `user_key` instead__.