provisioner: new Puppet provisioner (#18851)
* Basic Puppet provisioner * (fixup) fix snake_case use in Bolt * (fixup) Remove unused ValidateFunc * (fixup) Check bolt result status * (lint) go fmt * Requested changes * Remove PE autodetection * Apply suggestions from @svanharmelen Co-Authored-By: rodjek <tim@sharpe.id.au> * Tag all JSON fields in bolt output * Defer comm.Disconnect() as suggested * Make bolt timeout configurable * Update builtin/provisioners/puppet/resource_provisioner.go Co-Authored-By: rodjek <tim@sharpe.id.au> * Make extension_requests and custom_attributes configurable
This commit is contained in:
parent
0abb0a05fb
commit
615110e13e
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/builtin/provisioners/puppet"
|
||||
"github.com/hashicorp/terraform/plugin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
plugin.Serve(&plugin.ServeOpts{
|
||||
ProvisionerFunc: puppet.Provisioner,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package bolt
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Items []struct {
|
||||
Node string `json:"node"`
|
||||
Status string `json:"status"`
|
||||
Result map[string]string `json:"result"`
|
||||
} `json:"items"`
|
||||
NodeCount int `json:"node_count"`
|
||||
ElapsedTime int `json:"elapsed_time"`
|
||||
}
|
||||
|
||||
func runCommand(command string, timeout time.Duration) ([]byte, error) {
|
||||
var cmdargs []string
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
cmdargs = []string{"cmd", "/C"}
|
||||
} else {
|
||||
cmdargs = []string{"/bin/sh", "-c"}
|
||||
}
|
||||
cmdargs = append(cmdargs, command)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, cmdargs[0], cmdargs[1:]...)
|
||||
return cmd.Output()
|
||||
}
|
||||
|
||||
func Task(connInfo map[string]string, timeout time.Duration, sudo bool, task string, args map[string]string) (*Result, error) {
|
||||
cmdargs := []string{
|
||||
"bolt", "task", "run", "--nodes", connInfo["type"] + "://" + connInfo["host"], "-u", connInfo["user"],
|
||||
}
|
||||
|
||||
if connInfo["type"] == "winrm" {
|
||||
cmdargs = append(cmdargs, "-p", "\""+connInfo["password"]+"\"", "--no-ssl")
|
||||
} else {
|
||||
if sudo {
|
||||
cmdargs = append(cmdargs, "--run-as", "root")
|
||||
}
|
||||
|
||||
cmdargs = append(cmdargs, "--no-host-key-check")
|
||||
}
|
||||
|
||||
cmdargs = append(cmdargs, "--format", "json", "--connect-timeout", "120", task)
|
||||
|
||||
if args != nil {
|
||||
for key, value := range args {
|
||||
cmdargs = append(cmdargs, strings.Join([]string{key, value}, "="))
|
||||
}
|
||||
}
|
||||
|
||||
out, err := runCommand(strings.Join(cmdargs, " "), timeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Bolt: \"%s\": %s: %s", strings.Join(cmdargs, " "), out, err)
|
||||
}
|
||||
|
||||
result := new(Result)
|
||||
if err = json.Unmarshal(out, result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package puppet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
)
|
||||
|
||||
func (p *provisioner) linuxUploadFile(f io.Reader, dir string, filename string) error {
|
||||
_, err := p.runCommand("mkdir -p " + dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to make directory %s: %s", dir, err)
|
||||
}
|
||||
|
||||
err = p.comm.Upload("/tmp/"+filename, f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to upload %s to /tmp: %s", filename, err)
|
||||
}
|
||||
|
||||
_, err = p.runCommand(fmt.Sprintf("mv /tmp/%s %s/%s", filename, dir, filename))
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *provisioner) linuxDefaultCertname() (string, error) {
|
||||
certname, err := p.runCommand("hostname -f")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return certname, nil
|
||||
}
|
||||
|
||||
func (p *provisioner) linuxInstallPuppetAgent() error {
|
||||
_, err := p.runCommand(fmt.Sprintf("curl -kO https://%s:8140/packages/current/install.bash", p.Server))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.runCommand("bash -- ./install.bash --puppet-service-ensure stopped")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.runCommand("rm -f install.bash")
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *provisioner) linuxRunPuppetAgent() error {
|
||||
_, err := p.runCommand(fmt.Sprintf(
|
||||
"/opt/puppetlabs/puppet/bin/puppet agent --test --server %s --environment %s",
|
||||
p.Server,
|
||||
p.Environment,
|
||||
))
|
||||
|
||||
// Puppet exits 2 if changes have been successfully made.
|
||||
if err != nil {
|
||||
errStruct, _ := err.(*remote.ExitError)
|
||||
if errStruct.ExitStatus == 2 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,379 @@
|
|||
package puppet
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator"
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestResourceProvisioner_linuxUploadFile(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
Uploads map[string]string
|
||||
File io.Reader
|
||||
Dir string
|
||||
Filename string
|
||||
}{
|
||||
"Successful upload": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"mkdir -p /etc/puppetlabs/puppet": true,
|
||||
"mv /tmp/csr_attributes.yaml /etc/puppetlabs/puppet/csr_attributes.yaml": true,
|
||||
},
|
||||
Uploads: map[string]string{
|
||||
"/tmp/csr_attributes.yaml": "",
|
||||
},
|
||||
Dir: "/etc/puppetlabs/puppet",
|
||||
Filename: "csr_attributes.yaml",
|
||||
File: strings.NewReader(""),
|
||||
},
|
||||
"Failure when creating the directory": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"mkdir -p /etc/puppetlabs/puppet": true,
|
||||
},
|
||||
Dir: "/etc/puppetlabs/puppet",
|
||||
Filename: "csr_attributes.yaml",
|
||||
File: strings.NewReader(""),
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "mkdir -p /etc/puppetlabs/puppet",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
c.Uploads = tc.Uploads
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.linuxUploadFile(tc.File, tc.Dir, tc.Filename)
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_linuxDefaultCertname(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"No sudo": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"hostname -f": true,
|
||||
},
|
||||
},
|
||||
"With sudo": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": true,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"sudo hostname -f": true,
|
||||
},
|
||||
},
|
||||
"Failed execution": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"hostname -f": true,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
if r.Command == "hostname -f" {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "hostname -f",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
} else {
|
||||
r.SetExitStatus(0, nil)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
_, err = p.linuxDefaultCertname()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_linuxInstallPuppetAgent(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"Everything runs succcessfully": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"curl -kO https://puppet.test.com:8140/packages/current/install.bash": true,
|
||||
"bash -- ./install.bash --puppet-service-ensure stopped": true,
|
||||
"rm -f install.bash": true,
|
||||
},
|
||||
},
|
||||
"Respects the use_sudo config flag": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": true,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"sudo curl -kO https://puppet.test.com:8140/packages/current/install.bash": true,
|
||||
"sudo bash -- ./install.bash --puppet-service-ensure stopped": true,
|
||||
"sudo rm -f install.bash": true,
|
||||
},
|
||||
},
|
||||
"When the curl command fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"curl -kO https://puppet.test.com:8140/packages/current/install.bash": true,
|
||||
"bash -- ./install.bash --puppet-service-ensure stopped": false,
|
||||
"rm -f install.bash": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
if r.Command == "curl -kO https://puppet.test.com:8140/packages/current/install.bash" {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "curl -kO https://puppet.test.com:8140/packages/current/install.bash",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
} else {
|
||||
r.SetExitStatus(0, nil)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
"When the install script fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"curl -kO https://puppet.test.com:8140/packages/current/install.bash": true,
|
||||
"bash -- ./install.bash --puppet-service-ensure stopped": true,
|
||||
"rm -f install.bash": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
if r.Command == "bash -- ./install.bash --puppet-service-ensure stopped" {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "bash -- ./install.bash --puppet-service-ensure stopped",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
} else {
|
||||
r.SetExitStatus(0, nil)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
"When the cleanup rm fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"curl -kO https://puppet.test.com:8140/packages/current/install.bash": true,
|
||||
"bash -- ./install.bash --puppet-service-ensure stopped": true,
|
||||
"rm -f install.bash": true,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
if r.Command == "rm -f install.bash" {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "rm -f install.bash",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
} else {
|
||||
r.SetExitStatus(0, nil)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.linuxInstallPuppetAgent()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_linuxRunPuppetAgent(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"When puppet returns 0": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
"/opt/puppetlabs/puppet/bin/puppet agent --test --server puppet.test.com --environment production": true,
|
||||
},
|
||||
},
|
||||
"When puppet returns 2 (changes applied without error)": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(2, &remote.ExitError{
|
||||
Command: "/opt/puppetlabs/puppet/bin/puppet agent --test --server puppet.test.com",
|
||||
ExitStatus: 2,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
ExpectedError: false,
|
||||
},
|
||||
"When puppet returns something not 0 or 2": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: "/opt/puppetlabs/puppet/bin/puppet agent --test --server puppet.test.com",
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.linuxRunPuppetAgent()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,332 @@
|
|||
package puppet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/builtin/provisioners/puppet/bolt"
|
||||
"github.com/hashicorp/terraform/communicator"
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/helper/validation"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/go-linereader"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
type provisioner struct {
|
||||
Server string
|
||||
ServerUser string
|
||||
OSType string
|
||||
Certname string
|
||||
Environment string
|
||||
Autosign bool
|
||||
OpenSource bool
|
||||
UseSudo bool
|
||||
BoltTimeout time.Duration
|
||||
CustomAttributes map[string]interface{}
|
||||
ExtensionRequests map[string]interface{}
|
||||
|
||||
runPuppetAgent func() error
|
||||
installPuppetAgent func() error
|
||||
uploadFile func(f io.Reader, dir string, filename string) error
|
||||
defaultCertname func() (string, error)
|
||||
|
||||
instanceState *terraform.InstanceState
|
||||
output terraform.UIOutput
|
||||
comm communicator.Communicator
|
||||
}
|
||||
|
||||
type csrAttributes struct {
|
||||
CustomAttributes map[string]string `yaml:"custom_attributes"`
|
||||
ExtensionRequests map[string]string `yaml:"extension_requests"`
|
||||
}
|
||||
|
||||
// Provisioner returns a Puppet resource provisioner.
|
||||
func Provisioner() terraform.ResourceProvisioner {
|
||||
return &schema.Provisioner{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"server": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"server_user": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "root",
|
||||
},
|
||||
"os_type": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"linux", "windows"}, false),
|
||||
},
|
||||
"use_sudo": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
"autosign": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
"open_source": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
"certname": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"extension_requests": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
"custom_attributes": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
},
|
||||
"environment": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Default: "production",
|
||||
Optional: true,
|
||||
},
|
||||
"bolt_timeout": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Default: "5m",
|
||||
Optional: true,
|
||||
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
|
||||
_, err := time.ParseDuration(val.(string))
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return warns, errs
|
||||
},
|
||||
},
|
||||
},
|
||||
ApplyFunc: applyFn,
|
||||
}
|
||||
}
|
||||
|
||||
func applyFn(ctx context.Context) error {
|
||||
output := ctx.Value(schema.ProvOutputKey).(terraform.UIOutput)
|
||||
state := ctx.Value(schema.ProvRawStateKey).(*terraform.InstanceState)
|
||||
configData := ctx.Value(schema.ProvConfigDataKey).(*schema.ResourceData)
|
||||
|
||||
p, err := decodeConfig(configData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.instanceState = state
|
||||
p.output = output
|
||||
|
||||
if p.OSType == "" {
|
||||
switch connType := state.Ephemeral.ConnInfo["type"]; connType {
|
||||
case "ssh", "":
|
||||
p.OSType = "linux"
|
||||
case "winrm":
|
||||
p.OSType = "windows"
|
||||
default:
|
||||
return fmt.Errorf("Unsupported connection type: %s", connType)
|
||||
}
|
||||
}
|
||||
|
||||
switch p.OSType {
|
||||
case "linux":
|
||||
p.runPuppetAgent = p.linuxRunPuppetAgent
|
||||
p.installPuppetAgent = p.linuxInstallPuppetAgent
|
||||
p.uploadFile = p.linuxUploadFile
|
||||
p.defaultCertname = p.linuxDefaultCertname
|
||||
case "windows":
|
||||
p.runPuppetAgent = p.windowsRunPuppetAgent
|
||||
p.installPuppetAgent = p.windowsInstallPuppetAgent
|
||||
p.uploadFile = p.windowsUploadFile
|
||||
p.UseSudo = false
|
||||
p.defaultCertname = p.windowsDefaultCertname
|
||||
default:
|
||||
return fmt.Errorf("Unsupported OS type: %s", p.OSType)
|
||||
}
|
||||
|
||||
comm, err := communicator.New(state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
retryCtx, cancel := context.WithTimeout(ctx, comm.Timeout())
|
||||
defer cancel()
|
||||
|
||||
err = communicator.Retry(retryCtx, func() error {
|
||||
return comm.Connect(output)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer comm.Disconnect()
|
||||
|
||||
p.comm = comm
|
||||
|
||||
if p.OpenSource {
|
||||
p.installPuppetAgent = p.installPuppetAgentOpenSource
|
||||
}
|
||||
|
||||
csrAttrs := new(csrAttributes)
|
||||
csrAttrs.CustomAttributes = make(map[string]string)
|
||||
for k, v := range p.CustomAttributes {
|
||||
csrAttrs.CustomAttributes[k] = v.(string)
|
||||
}
|
||||
|
||||
csrAttrs.ExtensionRequests = make(map[string]string)
|
||||
for k, v := range p.ExtensionRequests {
|
||||
csrAttrs.ExtensionRequests[k] = v.(string)
|
||||
}
|
||||
|
||||
if p.Autosign {
|
||||
if p.Certname == "" {
|
||||
p.Certname, _ = p.defaultCertname()
|
||||
}
|
||||
|
||||
autosignToken, err := p.generateAutosignToken(p.Certname)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to generate an autosign token: %s", err)
|
||||
}
|
||||
csrAttrs.CustomAttributes["challengePassword"] = autosignToken
|
||||
}
|
||||
|
||||
if err = p.writeCSRAttributes(csrAttrs); err != nil {
|
||||
return fmt.Errorf("Failed to write csr_attributes.yaml: %s", err)
|
||||
}
|
||||
|
||||
if err = p.installPuppetAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = p.runPuppetAgent(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provisioner) writeCSRAttributes(attrs *csrAttributes) (rerr error) {
|
||||
content, err := yaml.Marshal(attrs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to marshal CSR attributes to YAML: %s", err)
|
||||
}
|
||||
|
||||
configDir := map[string]string{
|
||||
"linux": "/etc/puppetlabs/puppet",
|
||||
"windows": "C:\\ProgramData\\PuppetLabs\\Puppet\\etc",
|
||||
}
|
||||
|
||||
return p.uploadFile(bytes.NewBuffer(content), configDir[p.OSType], "csr_attributes.yaml")
|
||||
}
|
||||
|
||||
func (p *provisioner) generateAutosignToken(certname string) (string, error) {
|
||||
task := "autosign::generate_token"
|
||||
|
||||
masterConnInfo := map[string]string{
|
||||
"type": "ssh",
|
||||
"host": p.Server,
|
||||
"user": p.ServerUser,
|
||||
}
|
||||
|
||||
result, err := bolt.Task(
|
||||
masterConnInfo,
|
||||
p.BoltTimeout,
|
||||
p.ServerUser != "root",
|
||||
task,
|
||||
map[string]string{"certname": certname},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.Items[0].Status != "success" {
|
||||
return "", fmt.Errorf("Bolt %s failed on %s: %v",
|
||||
task,
|
||||
result.Items[0].Node,
|
||||
result.Items[0].Result["_error"],
|
||||
)
|
||||
}
|
||||
|
||||
return result.Items[0].Result["_output"], nil
|
||||
}
|
||||
|
||||
func (p *provisioner) installPuppetAgentOpenSource() error {
|
||||
result, err := bolt.Task(
|
||||
p.instanceState.Ephemeral.ConnInfo,
|
||||
p.BoltTimeout,
|
||||
p.UseSudo,
|
||||
"puppet_agent::install",
|
||||
nil,
|
||||
)
|
||||
|
||||
if err != nil || result.Items[0].Status != "success" {
|
||||
return fmt.Errorf("puppet_agent::install failed: %s\n%+v", err, result)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *provisioner) runCommand(command string) (stdout string, err error) {
|
||||
if p.UseSudo {
|
||||
command = "sudo " + command
|
||||
}
|
||||
|
||||
var stdoutBuffer bytes.Buffer
|
||||
outR, outW := io.Pipe()
|
||||
errR, errW := io.Pipe()
|
||||
outTee := io.TeeReader(outR, &stdoutBuffer)
|
||||
go p.copyToOutput(outTee)
|
||||
go p.copyToOutput(errR)
|
||||
defer outW.Close()
|
||||
defer errW.Close()
|
||||
|
||||
cmd := &remote.Cmd{
|
||||
Command: command,
|
||||
Stdout: outW,
|
||||
Stderr: errW,
|
||||
}
|
||||
|
||||
err = p.comm.Start(cmd)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error executing command %q: %v", cmd.Command, err)
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
err = cmd.Wait()
|
||||
stdout = strings.TrimSpace(stdoutBuffer.String())
|
||||
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
func (p *provisioner) copyToOutput(reader io.Reader) {
|
||||
lr := linereader.New(reader)
|
||||
for line := range lr.Ch {
|
||||
p.output.Output(line)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeConfig(d *schema.ResourceData) (*provisioner, error) {
|
||||
p := &provisioner{
|
||||
UseSudo: d.Get("use_sudo").(bool),
|
||||
Server: d.Get("server").(string),
|
||||
ServerUser: d.Get("server_user").(string),
|
||||
OSType: strings.ToLower(d.Get("os_type").(string)),
|
||||
Autosign: d.Get("autosign").(bool),
|
||||
OpenSource: d.Get("open_source").(bool),
|
||||
Certname: strings.ToLower(d.Get("certname").(string)),
|
||||
ExtensionRequests: d.Get("extension_requests").(map[string]interface{}),
|
||||
CustomAttributes: d.Get("custom_attributes").(map[string]interface{}),
|
||||
Environment: d.Get("environment").(string),
|
||||
}
|
||||
p.BoltTimeout, _ = time.ParseDuration(d.Get("bolt_timeout").(string))
|
||||
|
||||
return p, nil
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package puppet
|
||||
|
||||
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 TestProvisioner_Validate_good_server(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("Errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_bad_no_server(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Should have errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_bad_os_type(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"os_type": "OS/2",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Should have errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_good_os_type_linux(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"os_type": "linux",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("Errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_good_os_type_windows(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"os_type": "windows",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("Errors: %v", errs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_bad_bolt_timeout(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"bolt_timeout": "123oeau",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) == 0 {
|
||||
t.Fatalf("Should have errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisioner_Validate_good_bolt_timeout(t *testing.T) {
|
||||
c := testConfig(t, map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"bolt_timeout": "123m",
|
||||
})
|
||||
|
||||
warn, errs := Provisioner().Validate(c)
|
||||
if len(warn) > 0 {
|
||||
t.Fatalf("Warnings: %v", warn)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("Errors: %v", warn)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package puppet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
)
|
||||
|
||||
const (
|
||||
getHostByName = "([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname"
|
||||
domainQuery = "(Get-WmiObject -Query 'select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True').DNSDomain"
|
||||
)
|
||||
|
||||
func (p *provisioner) windowsUploadFile(f io.Reader, dir string, filename string) error {
|
||||
_, err := p.runCommand("powershell.exe new-item -itemtype directory -force -path " + dir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to make directory %s: %s", dir, err)
|
||||
}
|
||||
|
||||
return p.comm.Upload(dir+"\\"+filename, f)
|
||||
}
|
||||
|
||||
func (p *provisioner) windowsDefaultCertname() (string, error) {
|
||||
certname, err := p.runCommand(fmt.Sprintf(`powershell -Command "& {%s}"`, getHostByName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Sometimes System.Net.Dns::GetHostByName does not return a full FQDN, so
|
||||
// we have to look up the domain separately.
|
||||
if strings.Contains(certname, ".") {
|
||||
return certname, nil
|
||||
}
|
||||
|
||||
domain, err := p.runCommand(fmt.Sprintf(`powershell -Command "& {%s}"`, domainQuery))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return strings.ToLower(certname + "." + domain), nil
|
||||
}
|
||||
|
||||
func (p *provisioner) windowsInstallPuppetAgent() error {
|
||||
_, err := p.runCommand(fmt.Sprintf(
|
||||
`powershell -Command "& {[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; `+
|
||||
`(New-Object System.Net.WebClient).DownloadFile('https://%s:8140/packages/current/install.ps1', `+
|
||||
`'install.ps1')}"`,
|
||||
p.Server,
|
||||
))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = p.runCommand(`powershell -Command "& .\install.ps1 -PuppetServiceEnsure stopped"`)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *provisioner) windowsRunPuppetAgent() error {
|
||||
_, err := p.runCommand(fmt.Sprintf("puppet agent --test --server %s --environment %s", p.Server, p.Environment))
|
||||
if err != nil {
|
||||
errStruct, _ := err.(*remote.ExitError)
|
||||
if errStruct.ExitStatus == 2 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
package puppet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/communicator"
|
||||
"github.com/hashicorp/terraform/communicator/remote"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
const (
|
||||
getHostByNameCmd = `powershell -Command "& {([System.Net.Dns]::GetHostByName(($env:computerName))).Hostname}"`
|
||||
domainQueryCmd = `powershell -Command "& {(Get-WmiObject -Query 'select DNSDomain from Win32_NetworkAdapterConfiguration where IPEnabled = True').DNSDomain}"`
|
||||
downloadInstallerCmd = `powershell -Command "& {[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}; (New-Object System.Net.WebClient).DownloadFile('https://puppet.test.com:8140/packages/current/install.ps1', 'install.ps1')}"`
|
||||
runInstallerCmd = `powershell -Command "& .\install.ps1 -PuppetServiceEnsure stopped"`
|
||||
runPuppetCmd = "puppet agent --test --server puppet.test.com --environment production"
|
||||
)
|
||||
|
||||
func TestResourceProvisioner_windowsUploadFile(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
Uploads map[string]string
|
||||
File io.Reader
|
||||
Dir string
|
||||
Filename string
|
||||
}{
|
||||
"Successful upload": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
`powershell.exe new-item -itemtype directory -force -path C:\ProgramData\PuppetLabs\puppet\etc`: true,
|
||||
},
|
||||
Uploads: map[string]string{
|
||||
`C:\ProgramData\PuppetLabs\puppet\etc\csr_attributes.yaml`: "",
|
||||
},
|
||||
Dir: `C:\ProgramData\PuppetLabs\puppet\etc`,
|
||||
Filename: "csr_attributes.yaml",
|
||||
File: strings.NewReader(""),
|
||||
},
|
||||
"Failure when creating the directory": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
`powershell.exe new-item -itemtype directory -force -path C:\ProgramData\PuppetLabs\puppet\etc`: true,
|
||||
},
|
||||
Dir: `C:\ProgramData\PuppetLabs\puppet\etc`,
|
||||
Filename: "csr_attributes.yaml",
|
||||
File: strings.NewReader(""),
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: `powershell.exe new-item -itemtype directory -force -path C:\ProgramData\PuppetLabs\puppet\etc`,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
c.Uploads = tc.Uploads
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.windowsUploadFile(tc.File, tc.Dir, tc.Filename)
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_windowsDefaultCertname(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"GetHostByName failure": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case getHostByNameCmd:
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: getHostByNameCmd,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
"GetHostByName returns FQDN": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case getHostByNameCmd:
|
||||
r.Stdout.Write([]byte("example.test.com\n"))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
r.SetExitStatus(0, nil)
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"GetHostByName returns hostname, DNSDomain query succeeds": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case getHostByNameCmd:
|
||||
r.Stdout.Write([]byte("example\n"))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
r.SetExitStatus(0, nil)
|
||||
case domainQueryCmd:
|
||||
r.Stdout.Write([]byte("test.com\n"))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
r.SetExitStatus(0, nil)
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"GetHostByName returns hostname, DNSDomain query fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case getHostByNameCmd:
|
||||
r.Stdout.Write([]byte("example\n"))
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
r.SetExitStatus(0, nil)
|
||||
case domainQueryCmd:
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: domainQueryCmd,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
_, err = p.windowsDefaultCertname()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_windowsInstallPuppetAgent(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"Everything runs succcessfully": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
downloadInstallerCmd: true,
|
||||
runInstallerCmd: true,
|
||||
},
|
||||
},
|
||||
"Installer download fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": true,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case downloadInstallerCmd:
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: downloadInstallerCmd,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
"Install script fails": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
switch r.Command {
|
||||
case downloadInstallerCmd:
|
||||
r.SetExitStatus(0, nil)
|
||||
case runInstallerCmd:
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: runInstallerCmd,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("Command not found!")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.windowsInstallPuppetAgent()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_windowsRunPuppetAgent(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
Config map[string]interface{}
|
||||
Commands map[string]bool
|
||||
CommandFunc func(*remote.Cmd) error
|
||||
ExpectedError bool
|
||||
}{
|
||||
"When puppet returns 0": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
Commands: map[string]bool{
|
||||
runPuppetCmd: true,
|
||||
},
|
||||
},
|
||||
"When puppet returns 2 (changes applied without error)": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(2, &remote.ExitError{
|
||||
Command: runPuppetCmd,
|
||||
ExitStatus: 2,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
},
|
||||
"When puppet returns something not 0 or 2": {
|
||||
Config: map[string]interface{}{
|
||||
"server": "puppet.test.com",
|
||||
"use_sudo": false,
|
||||
},
|
||||
CommandFunc: func(r *remote.Cmd) error {
|
||||
r.SetExitStatus(1, &remote.ExitError{
|
||||
Command: runPuppetCmd,
|
||||
ExitStatus: 1,
|
||||
Err: nil,
|
||||
})
|
||||
return nil
|
||||
},
|
||||
ExpectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range cases {
|
||||
p, err := decodeConfig(
|
||||
schema.TestResourceDataRaw(t, Provisioner().(*schema.Provisioner).Schema, tc.Config),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("Error: %v", err)
|
||||
}
|
||||
|
||||
c := new(communicator.MockCommunicator)
|
||||
c.Commands = tc.Commands
|
||||
if tc.CommandFunc != nil {
|
||||
c.CommandFunc = tc.CommandFunc
|
||||
}
|
||||
p.comm = c
|
||||
p.output = new(terraform.MockUIOutput)
|
||||
|
||||
err = p.windowsRunPuppetAgent()
|
||||
if tc.ExpectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, but no error returned")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Fatalf("Test %q failed: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
fileprovisioner "github.com/hashicorp/terraform/builtin/provisioners/file"
|
||||
habitatprovisioner "github.com/hashicorp/terraform/builtin/provisioners/habitat"
|
||||
localexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/local-exec"
|
||||
puppetprovisioner "github.com/hashicorp/terraform/builtin/provisioners/puppet"
|
||||
remoteexecprovisioner "github.com/hashicorp/terraform/builtin/provisioners/remote-exec"
|
||||
saltmasterlessprovisioner "github.com/hashicorp/terraform/builtin/provisioners/salt-masterless"
|
||||
|
||||
|
@ -21,6 +22,7 @@ var InternalProvisioners = map[string]plugin.ProvisionerFunc{
|
|||
"file": fileprovisioner.Provisioner,
|
||||
"habitat": habitatprovisioner.Provisioner,
|
||||
"local-exec": localexecprovisioner.Provisioner,
|
||||
"puppet": puppetprovisioner.Provisioner,
|
||||
"remote-exec": remoteexecprovisioner.Provisioner,
|
||||
"salt-masterless": saltmasterlessprovisioner.Provisioner,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue