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 (
"github.com/hashicorp/terraform/builtin/provisioners/chef"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform"
)
func main() {
plugin.Serve(&plugin.ServeOpts{
ProvisionerFunc: func() terraform.ResourceProvisioner {
return new(chef.ResourceProvisioner)
},
ProvisionerFunc: chef.Provisioner,
})
}

View File

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

View File

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

View File

@ -2,6 +2,7 @@ package chef
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@ -17,10 +18,10 @@ import (
"github.com/hashicorp/terraform/communicator"
"github.com/hashicorp/terraform/communicator/remote"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/go-homedir"
"github.com/mitchellh/go-linereader"
"github.com/mitchellh/mapstructure"
)
const (
@ -81,81 +82,196 @@ enable_reporting false
{{ end }}
`
// Provisioner represents a Chef provisioner
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"`
type provisionFn func(terraform.UIOutput, communicator.Communicator) error
attributes map[string]interface{}
vaults map[string][]string
type provisioner struct {
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
createConfigFiles func(terraform.UIOutput, communicator.Communicator) error
installChefClient func(terraform.UIOutput, communicator.Communicator) error
fetchChefCertificates func(terraform.UIOutput, communicator.Communicator) error
generateClientKey func(terraform.UIOutput, communicator.Communicator) error
configureVaults func(terraform.UIOutput, communicator.Communicator) error
runChefClient func(terraform.UIOutput, communicator.Communicator) error
createConfigFiles provisionFn
installChefClient provisionFn
fetchChefCertificates provisionFn
generateClientKey provisionFn
configureVaults provisionFn
runChefClient provisionFn
useSudo bool
// Deprecated Fields
ValidationClientName string `mapstructure:"validation_client_name"`
ValidationKey string `mapstructure:"validation_key"`
}
// ResourceProvisioner represents a generic chef provisioner
type ResourceProvisioner struct{}
// Provisioner returns a Chef provisioner
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 {
// Noop for now. TODO in the future.
return nil
"attributes_json": &schema.Schema{
Type: schema.TypeString,
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
func (r *ResourceProvisioner) Apply(
o terraform.UIOutput,
s *terraform.InstanceState,
c *terraform.ResourceConfig) error {
// TODO: Support context cancelling (Provisioner Stop)
func applyFn(ctx context.Context) error {
o := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
d := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
// Decode the raw config for this provisioner
p, err := r.decodeConfig(c)
p, err := decodeConfig(d)
if err != nil {
return err
}
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
p.OSType = "linux"
case "winrm":
p.OSType = "windows"
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.configureVaults = p.configureVaultsFunc(linuxGemCmd, linuxKnifeCmd, 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":
p.cleanupUserKeyCmd = fmt.Sprintf("cd %s && del /F /Q %s", windowsConfDir, p.UserName+".pem")
p.createConfigFiles = p.windowsCreateConfigFiles
@ -184,15 +300,14 @@ func (r *ResourceProvisioner) Apply(
}
// Get a new communicator
comm, err := communicator.New(s)
comm, err := communicator.New(d.State())
if err != nil {
return err
}
// Wait and retry until we establish the connection
err = retryFunc(comm.Timeout(), func() error {
err := comm.Connect(o)
return err
return comm.Connect(o)
})
if err != nil {
return err
@ -234,7 +349,7 @@ func (r *ResourceProvisioner) Apply(
}
}
if p.VaultJSON != "" {
if p.Vaults != nil {
o.Output("Configure Chef vaults...")
if err := p.configureVaults(o, comm); err != nil {
return err
@ -253,152 +368,27 @@ func (r *ResourceProvisioner) Apply(
return nil
}
// Validate checks if the required arguments are configured
func (r *ResourceProvisioner) Validate(c *terraform.ResourceConfig) (ws []string, es []error) {
p, err := r.decodeConfig(c)
func validateFn(d *schema.ResourceData) (ws []string, es []error) {
p, err := decodeConfig(d)
if err != nil {
es = append(es, err)
return ws, es
}
if p.NodeName == "" {
es = append(es, errors.New("Key not found: node_name"))
}
if !p.UsePolicyfile && p.RunList == nil {
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 == "" {
es = append(es, errors.New("Policyfile enabled but key not found: policy_name"))
}
if p.UsePolicyfile && p.PolicyGroup == "" {
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
}
func (r *ResourceProvisioner) decodeConfig(c *terraform.ResourceConfig) (*Provisioner, 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 {
func (p *provisioner) deployConfigFiles(o terraform.UIOutput, comm communicator.Communicator, confDir string) error {
// Copy the user key to the new instance
pk := strings.NewReader(p.UserKey)
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
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)
}
// Create a map with first boot settings
fb := make(map[string]interface{})
if p.attributes != nil {
fb = p.attributes
if p.Attributes != nil {
fb = p.Attributes
}
// 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
}
func (p *Provisioner) deployOhaiHints(
o terraform.UIOutput,
comm communicator.Communicator,
hintDir string) error {
func (p *provisioner) deployOhaiHints(o terraform.UIOutput, comm communicator.Communicator, hintDir string) error {
for _, hint := range p.OhaiHints {
// Open the hint file
f, err := os.Open(hint)
@ -490,7 +477,7 @@ func (p *Provisioner) deployOhaiHints(
return nil
}
func (p *Provisioner) fetchChefCertificatesFunc(
func (p *provisioner) fetchChefCertificatesFunc(
knifeCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error {
return func(o terraform.UIOutput, comm communicator.Communicator) error {
@ -501,10 +488,7 @@ func (p *Provisioner) fetchChefCertificatesFunc(
}
}
func (p *Provisioner) generateClientKeyFunc(
knifeCmd string,
confDir string,
noOutput string) func(terraform.UIOutput, communicator.Communicator) error {
func (p *provisioner) generateClientKeyFunc(knifeCmd string, confDir string, noOutput string) provisionFn {
return func(o terraform.UIOutput, comm communicator.Communicator) error {
options := fmt.Sprintf("-c %s -u %s --key %s",
path.Join(confDir, clienrb),
@ -562,10 +546,7 @@ func (p *Provisioner) generateClientKeyFunc(
}
}
func (p *Provisioner) configureVaultsFunc(
gemCmd string,
knifeCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error {
func (p *provisioner) configureVaultsFunc(gemCmd string, knifeCmd string, confDir string) provisionFn {
return func(o terraform.UIOutput, comm communicator.Communicator) error {
if err := p.runCommand(o, comm, fmt.Sprintf("%s install chef-vault", gemCmd)); err != nil {
return err
@ -577,7 +558,7 @@ func (p *Provisioner) configureVaultsFunc(
path.Join(confDir, p.UserName+".pem"),
)
for vault, items := range p.vaults {
for vault, items := range p.Vaults {
for _, item := range items {
updateCmd := fmt.Sprintf("%s vault update %s %s -C %s -M client %s",
knifeCmd,
@ -596,9 +577,7 @@ func (p *Provisioner) configureVaultsFunc(
}
}
func (p *Provisioner) runChefClientFunc(
chefCmd string,
confDir string) func(terraform.UIOutput, communicator.Communicator) error {
func (p *provisioner) runChefClientFunc(chefCmd string, confDir string) provisionFn {
return func(o terraform.UIOutput, comm communicator.Communicator) error {
fb := path.Join(confDir, firstBoot)
var cmd string
@ -634,7 +613,7 @@ func (p *Provisioner) runChefClientFunc(
}
// Output implementation of terraform.UIOutput interface
func (p *Provisioner) Output(output string) {
func (p *provisioner) Output(output string) {
logFile := path.Join(logfileDir, p.NodeName)
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0666)
if err != nil {
@ -660,10 +639,7 @@ func (p *Provisioner) Output(output string) {
}
// runCommand is used to run already prepared commands
func (p *Provisioner) runCommand(
o terraform.UIOutput,
comm communicator.Communicator,
command string) error {
func (p *provisioner) runCommand(o terraform.UIOutput, comm communicator.Communicator, command string) error {
// Unless prevented, prefix the command with sudo
if p.useSudo {
command = "sudo " + command
@ -702,7 +678,7 @@ func (p *Provisioner) runCommand(
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)
lr := linereader.New(r)
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/config"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
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) {
@ -23,8 +30,8 @@ func TestResourceProvider_Validate_good(t *testing.T) {
"user_name": "bob",
"user_key": "USER-KEY",
})
r := new(ResourceProvisioner)
warn, errs := r.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
@ -37,8 +44,8 @@ func TestResourceProvider_Validate_bad(t *testing.T) {
c := testConfig(t, map[string]interface{}{
"invalid": "nope",
})
p := new(ResourceProvisioner)
warn, errs := p.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
@ -59,8 +66,8 @@ func TestResourceProvider_Validate_computedValues(t *testing.T) {
"user_key": "USER-KEY",
"attributes_json": config.UnknownVariableValue,
})
r := new(ResourceProvisioner)
warn, errs := r.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
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) {
cases := map[string]struct {
Config *terraform.ResourceConfig
Config map[string]interface{}
ChefCmd string
ConfDir string
Commands map[string]bool
}{
"Sudo": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local",
"user_name": "bob",
"user_key": "USER-KEY",
}),
},
ChefCmd: linuxChefCmd,
@ -106,14 +104,14 @@ func TestResourceProvider_runChefClient(t *testing.T) {
},
"NoSudo": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"node_name": "nodename1",
"prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local",
"user_name": "bob",
"user_key": "USER-KEY",
}),
},
ChefCmd: linuxChefCmd,
@ -127,7 +125,7 @@ func TestResourceProvider_runChefClient(t *testing.T) {
},
"Environment": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"environment": "production",
"node_name": "nodename1",
"prevent_sudo": true,
@ -135,7 +133,7 @@ func TestResourceProvider_runChefClient(t *testing.T) {
"server_url": "https://chef.local",
"user_name": "bob",
"user_key": "USER-KEY",
}),
},
ChefCmd: windowsChefCmd,
@ -149,14 +147,15 @@ func TestResourceProvider_runChefClient(t *testing.T) {
},
}
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator)
for k, tc := range cases {
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 {
t.Fatalf("Error: %v", err)
}
@ -173,20 +172,20 @@ func TestResourceProvider_runChefClient(t *testing.T) {
func TestResourceProvider_fetchChefCertificates(t *testing.T) {
cases := map[string]struct {
Config *terraform.ResourceConfig
Config map[string]interface{}
KnifeCmd string
ConfDir string
Commands map[string]bool
}{
"Sudo": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"fetch_chef_certificates": true,
"node_name": "nodename1",
"run_list": []interface{}{"cookbook::recipe"},
"server_url": "https://chef.local",
"user_name": "bob",
"user_key": "USER-KEY",
}),
},
KnifeCmd: linuxKnifeCmd,
@ -200,7 +199,7 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
},
"NoSudo": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"fetch_chef_certificates": true,
"node_name": "nodename1",
"prevent_sudo": true,
@ -208,7 +207,7 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
"server_url": "https://chef.local",
"user_name": "bob",
"user_key": "USER-KEY",
}),
},
KnifeCmd: windowsKnifeCmd,
@ -222,14 +221,15 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
},
}
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator)
for k, tc := range cases {
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 {
t.Fatalf("Error: %v", err)
}
@ -246,14 +246,14 @@ func TestResourceProvider_fetchChefCertificates(t *testing.T) {
func TestResourceProvider_configureVaults(t *testing.T) {
cases := map[string]struct {
Config *terraform.ResourceConfig
Config map[string]interface{}
GemCmd string
KnifeCmd string
ConfDir string
Commands map[string]bool
}{
"Linux Vault string": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"node_name": "nodename1",
"prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"},
@ -261,7 +261,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob",
"user_key": "USER-KEY",
"vault_json": `{"vault1": "item1"}`,
}),
},
GemCmd: linuxGemCmd,
KnifeCmd: linuxKnifeCmd,
@ -275,7 +275,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
},
"Linux Vault []string": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"fetch_chef_certificates": true,
"node_name": "nodename1",
"prevent_sudo": true,
@ -284,7 +284,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob",
"user_key": "USER-KEY",
"vault_json": `{"vault1": ["item1", "item2"]}`,
}),
},
GemCmd: linuxGemCmd,
KnifeCmd: linuxKnifeCmd,
@ -300,7 +300,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
},
"Windows Vault string": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"node_name": "nodename1",
"prevent_sudo": true,
"run_list": []interface{}{"cookbook::recipe"},
@ -308,7 +308,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob",
"user_key": "USER-KEY",
"vault_json": `{"vault1": "item1"}`,
}),
},
GemCmd: windowsGemCmd,
KnifeCmd: windowsKnifeCmd,
@ -322,7 +322,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
},
"Windows Vault []string": {
Config: testConfig(t, map[string]interface{}{
Config: map[string]interface{}{
"fetch_chef_certificates": true,
"node_name": "nodename1",
"prevent_sudo": true,
@ -331,7 +331,7 @@ func TestResourceProvider_configureVaults(t *testing.T) {
"user_name": "bob",
"user_key": "USER-KEY",
"vault_json": `{"vault1": ["item1", "item2"]}`,
}),
},
GemCmd: windowsGemCmd,
KnifeCmd: windowsKnifeCmd,
@ -347,14 +347,15 @@ func TestResourceProvider_configureVaults(t *testing.T) {
},
}
r := new(ResourceProvisioner)
o := new(terraform.MockUIOutput)
c := new(communicator.MockCommunicator)
for k, tc := range cases {
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 {
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
`
func (p *Provisioner) windowsInstallChefClient(
o terraform.UIOutput,
comm communicator.Communicator) error {
func (p *provisioner) windowsInstallChefClient(o terraform.UIOutput, comm communicator.Communicator) error {
script := path.Join(path.Dir(comm.ScriptPath()), "ChefClient.ps1")
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)
}
func (p *Provisioner) windowsCreateConfigFiles(
o terraform.UIOutput,
comm communicator.Communicator) error {
func (p *provisioner) windowsCreateConfigFiles(o terraform.UIOutput, comm communicator.Communicator) error {
// Make sure the config directory exists
cmd := fmt.Sprintf("cmd /c if not exist %q mkdir %q", windowsConfDir, windowsConfDir)
if err := p.runCommand(o, comm, cmd); err != nil {

View File

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

View File

@ -36,6 +36,7 @@ func Provisioner() terraform.ResourceProvisioner {
},
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
func getSrc(data *schema.ResourceData) (string, bool, error) {
src := data.Get("source").(string)

View File

@ -4,16 +4,27 @@ import (
"testing"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/schema"
"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) {
c := testConfig(t, map[string]interface{}{
"source": "/tmp/foo",
"destination": "/tmp/bar",
})
p := Provisioner()
warn, errs := p.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
@ -27,8 +38,8 @@ func TestResourceProvider_Validate_good_content(t *testing.T) {
"content": "value to copy",
"destination": "/tmp/bar",
})
p := Provisioner()
warn, errs := p.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
@ -41,8 +52,22 @@ func TestResourceProvider_Validate_bad_not_destination(t *testing.T) {
c := testConfig(t, map[string]interface{}{
"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 {
t.Fatalf("Warnings: %v", warn)
}
@ -57,8 +82,8 @@ func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
"content": "value to copy",
"destination": "/tmp/bar",
})
p := Provisioner()
warn, errs := p.Validate(c)
warn, errs := Provisioner().Validate(c)
if len(warn) > 0 {
t.Fatalf("Warnings: %v", warn)
}
@ -67,12 +92,11 @@ func TestResourceProvider_Validate_bad_to_many_src(t *testing.T) {
}
}
func testConfig(
t *testing.T,
c map[string]interface{}) *terraform.ResourceConfig {
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

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

View File

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

View File

@ -75,6 +75,7 @@ import (
vaultprovider "github.com/hashicorp/terraform/builtin/providers/vault"
vcdprovider "github.com/hashicorp/terraform/builtin/providers/vcd"
vsphereprovider "github.com/hashicorp/terraform/builtin/providers/vsphere"
chefprovisioner "github.com/hashicorp/terraform/builtin/provisioners/chef"
fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file"
localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec"
remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec"
@ -84,9 +85,6 @@ import (
//New Provider Builds
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{
@ -162,16 +160,13 @@ var InternalProviders = map[string]plugin.ProviderFunc{
}
var InternalProvisioners = map[string]plugin.ProvisionerFunc{
"chef": chefprovisioner.Provisioner,
"file": fileprovisioner.Provisioner,
"local-exec": localexecprovisioner.Provisioner,
"remote-exec": remoteexecprovisioner.Provisioner,
}
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
InternalProviders["opc"] = func() terraform.ResourceProvider { return opcprovider.Provider() }
}

View File

@ -41,6 +41,10 @@ type Provisioner struct {
// information.
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
stopCtxCancel context.CancelFunc
stopOnce sync.Once
@ -117,8 +121,30 @@ func (p *Provisioner) Stop() error {
return nil
}
func (p *Provisioner) Validate(c *terraform.ResourceConfig) ([]string, []error) {
return schemaMap(p.Schema).Validate(c)
func (p *Provisioner) Validate(config *terraform.ResourceConfig) ([]string, []error) {
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.

View File

@ -3,6 +3,7 @@ package schema
import (
"context"
"fmt"
"reflect"
"testing"
"time"
@ -14,13 +15,35 @@ func TestProvisioner_impl(t *testing.T) {
var _ terraform.ResourceProvisioner = new(Provisioner)
}
func noopApply(ctx context.Context) error {
return nil
}
func TestProvisionerValidate(t *testing.T) {
cases := []struct {
Name string
P *Provisioner
Config map[string]interface{}
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",
&Provisioner{
@ -30,9 +53,11 @@ func TestProvisionerValidate(t *testing.T) {
Type: TypeString,
},
},
ApplyFunc: noopApply,
},
nil,
true,
nil,
},
{
@ -44,11 +69,57 @@ func TestProvisionerValidate(t *testing.T) {
Type: TypeString,
},
},
ApplyFunc: noopApply,
},
map[string]interface{}{
"foo": "bar",
},
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)
}
_, es := tc.P.Validate(terraform.NewResourceConfig(c))
ws, es := tc.P.Validate(terraform.NewResourceConfig(c))
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:
//
// "file": func() terraform.ResourceProvisioner { return new(file.ResourceProvisioner) },
// "local-exec": func() terraform.ResourceProvisioner { return new(localexec.ResourceProvisioner) },
// "remote-exec": func() terraform.ResourceProvisioner { return new(remoteexec.ResourceProvisioner) },
// "chef": chefprovisioner.Provisioner,
// "file": fileprovisioner.Provisioner,
// "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 {
output := ""
for _, item := range items {
@ -273,9 +272,6 @@ IMPORTS
//New Provider Builds
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{
@ -287,12 +283,7 @@ PROVISIONERS
}
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
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.
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__.