Merge pull request #7180 from xinau/packer-provisioner-inspec
Added inspec.io provisioner
This commit is contained in:
commit
9fe1366eeb
2
Makefile
2
Makefile
|
@ -11,7 +11,7 @@ GOPATH=$(shell go env GOPATH)
|
|||
# gofmt
|
||||
UNFORMATTED_FILES=$(shell find . -not -path "./vendor/*" -name "*.go" | xargs gofmt -s -l)
|
||||
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/ansible/test-fixtures/exit1')
|
||||
EXECUTABLE_FILES=$(shell find . -type f -executable | egrep -v '^\./(website/[vendor|tmp]|vendor/|\.git|bin/|scripts/|pkg/)' | egrep -v '.*(\.sh|\.bats|\.git)' | egrep -v './provisioner/(ansible|inspec)/test-fixtures/exit1')
|
||||
|
||||
# Get the git commit
|
||||
GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true)
|
||||
|
|
|
@ -71,6 +71,7 @@ import (
|
|||
chefsoloprovisioner "github.com/hashicorp/packer/provisioner/chef-solo"
|
||||
convergeprovisioner "github.com/hashicorp/packer/provisioner/converge"
|
||||
fileprovisioner "github.com/hashicorp/packer/provisioner/file"
|
||||
inspecprovisioner "github.com/hashicorp/packer/provisioner/inspec"
|
||||
powershellprovisioner "github.com/hashicorp/packer/provisioner/powershell"
|
||||
puppetmasterlessprovisioner "github.com/hashicorp/packer/provisioner/puppet-masterless"
|
||||
puppetserverprovisioner "github.com/hashicorp/packer/provisioner/puppet-server"
|
||||
|
@ -130,6 +131,7 @@ var Provisioners = map[string]packer.Provisioner{
|
|||
"chef-solo": new(chefsoloprovisioner.Provisioner),
|
||||
"converge": new(convergeprovisioner.Provisioner),
|
||||
"file": new(fileprovisioner.Provisioner),
|
||||
"inspec": new(inspecprovisioner.Provisioner),
|
||||
"powershell": new(powershellprovisioner.Provisioner),
|
||||
"puppet-masterless": new(puppetmasterlessprovisioner.Provisioner),
|
||||
"puppet-server": new(puppetserverprovisioner.Provisioner),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -17,7 +17,7 @@ import (
|
|||
|
||||
// An adapter satisfies SSH requests (from an Ansible client) by delegating SSH
|
||||
// exec and subsystem commands to a packer.Communicator.
|
||||
type adapter struct {
|
||||
type Adapter struct {
|
||||
done <-chan struct{}
|
||||
l net.Listener
|
||||
config *ssh.ServerConfig
|
||||
|
@ -26,8 +26,8 @@ type adapter struct {
|
|||
comm packer.Communicator
|
||||
}
|
||||
|
||||
func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *adapter {
|
||||
return &adapter{
|
||||
func NewAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig, sftpCmd string, ui packer.Ui, comm packer.Communicator) *Adapter {
|
||||
return &Adapter{
|
||||
done: done,
|
||||
l: l,
|
||||
config: config,
|
||||
|
@ -37,7 +37,7 @@ func newAdapter(done <-chan struct{}, l net.Listener, config *ssh.ServerConfig,
|
|||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Serve() {
|
||||
func (c *Adapter) Serve() {
|
||||
log.Printf("SSH proxy: serving on %s", c.l.Addr())
|
||||
|
||||
for {
|
||||
|
@ -62,7 +62,7 @@ func (c *adapter) Serve() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
||||
func (c *Adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
||||
log.Print("SSH proxy: accepted connection")
|
||||
_, chans, reqs, err := ssh.NewServerConn(conn, c.config)
|
||||
if err != nil {
|
||||
|
@ -89,7 +89,7 @@ func (c *adapter) Handle(conn net.Conn, ui packer.Ui) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
|
||||
func (c *Adapter) handleSession(newChannel ssh.NewChannel) error {
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -182,11 +182,11 @@ func (c *adapter) handleSession(newChannel ssh.NewChannel) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *adapter) Shutdown() {
|
||||
func (c *Adapter) Shutdown() {
|
||||
c.l.Close()
|
||||
}
|
||||
|
||||
func (c *adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
func (c *Adapter) exec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
var exitStatus int
|
||||
switch {
|
||||
case strings.HasPrefix(command, "scp ") && serveSCP(command[4:]):
|
||||
|
@ -206,7 +206,7 @@ func serveSCP(args string) bool {
|
|||
return bytes.IndexAny(opts, "tf") >= 0
|
||||
}
|
||||
|
||||
func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
||||
func (c *Adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
||||
opts, rest := scpOptions(args)
|
||||
|
||||
// remove the quoting that ansible added to rest for shell safety.
|
||||
|
@ -226,7 +226,7 @@ func (c *adapter) scpExec(args string, in io.Reader, out io.Writer) error {
|
|||
return errors.New("no scp mode specified")
|
||||
}
|
||||
|
||||
func (c *adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
func (c *Adapter) remoteExec(command string, in io.Reader, out io.Writer, err io.Writer) int {
|
||||
cmd := &packer.RemoteCmd{
|
||||
Stdin: in,
|
||||
Stdout: out,
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
@ -26,7 +26,7 @@ func TestAdapter_Serve(t *testing.T) {
|
|||
|
||||
ui := new(packer.NoopUi)
|
||||
|
||||
sut := newAdapter(done, &l, config, "", newUi(ui), communicator{})
|
||||
sut := NewAdapter(done, &l, config, "", ui, communicator{})
|
||||
go func() {
|
||||
i := 0
|
||||
for range acceptC {
|
|
@ -1,4 +1,4 @@
|
|||
package ansible
|
||||
package adapter
|
||||
|
||||
import (
|
||||
"bufio"
|
47
packer/ui.go
47
packer/ui.go
|
@ -343,7 +343,7 @@ func (u *MachineReadableUi) ProgressBar() ProgressBar {
|
|||
return new(NoopProgressBar)
|
||||
}
|
||||
|
||||
// TimestampedUi is a UI that wraps another UI implementation and prefixes
|
||||
// TimestampedUi is a UI that wraps another UI implementation and
|
||||
// prefixes each message with an RFC3339 timestamp
|
||||
type TimestampedUi struct {
|
||||
Ui Ui
|
||||
|
@ -376,3 +376,48 @@ func (u *TimestampedUi) ProgressBar() ProgressBar { return u.Ui.ProgressBar() }
|
|||
func (u *TimestampedUi) timestampLine(string string) string {
|
||||
return fmt.Sprintf("%v: %v", time.Now().Format(time.RFC3339), string)
|
||||
}
|
||||
|
||||
// Safe is a UI that wraps another UI implementation and
|
||||
// provides concurrency-safe access
|
||||
type SafeUi struct {
|
||||
Sem chan int
|
||||
Ui Ui
|
||||
}
|
||||
|
||||
var _ Ui = new(SafeUi)
|
||||
|
||||
func (u *SafeUi) Ask(s string) (string, error) {
|
||||
u.Sem <- 1
|
||||
ret, err := u.Ui.Ask(s)
|
||||
<-u.Sem
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (u *SafeUi) Say(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Say(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Message(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Message(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Error(s string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Error(s)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) Machine(t string, args ...string) {
|
||||
u.Sem <- 1
|
||||
u.Ui.Machine(t, args...)
|
||||
<-u.Sem
|
||||
}
|
||||
|
||||
func (u *SafeUi) ProgressBar() ProgressBar {
|
||||
return new(NoopProgressBar)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/adapter"
|
||||
commonhelper "github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
|
@ -63,7 +64,7 @@ type Config struct {
|
|||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
adapter *adapter
|
||||
adapter *adapter.Adapter
|
||||
done chan struct{}
|
||||
ansibleVersion string
|
||||
ansibleMajVersion uint
|
||||
|
@ -285,8 +286,11 @@ func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ui = newUi(ui)
|
||||
p.adapter = newAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm)
|
||||
ui = &packer.SafeUi{
|
||||
Sem: make(chan int, 1),
|
||||
Ui: ui,
|
||||
}
|
||||
p.adapter = adapter.NewAdapter(p.done, localListener, config, p.config.SFTPCmd, ui, comm)
|
||||
|
||||
defer func() {
|
||||
log.Print("shutting down the SSH proxy")
|
||||
|
@ -556,49 +560,3 @@ func getWinRMPassword(buildName string) string {
|
|||
packer.LogSecretFilter.Set(winRMPass)
|
||||
return winRMPass
|
||||
}
|
||||
|
||||
// Ui provides concurrency-safe access to packer.Ui.
|
||||
type Ui struct {
|
||||
sem chan int
|
||||
ui packer.Ui
|
||||
}
|
||||
|
||||
func newUi(ui packer.Ui) packer.Ui {
|
||||
return &Ui{sem: make(chan int, 1), ui: ui}
|
||||
}
|
||||
|
||||
func (ui *Ui) Ask(s string) (string, error) {
|
||||
ui.sem <- 1
|
||||
ret, err := ui.ui.Ask(s)
|
||||
<-ui.sem
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (ui *Ui) Say(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Say(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Message(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Message(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Error(s string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Error(s)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) Machine(t string, args ...string) {
|
||||
ui.sem <- 1
|
||||
ui.ui.Machine(t, args...)
|
||||
<-ui.sem
|
||||
}
|
||||
|
||||
func (ui *Ui) ProgressBar() packer.ProgressBar {
|
||||
return new(packer.NoopProgressBar)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,524 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/common/adapter"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
var SupportedBackends = map[string]bool{"docker": true, "local": true, "ssh": true, "winrm": true}
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
ctx interpolate.Context
|
||||
|
||||
// The command to run inspec
|
||||
Command string
|
||||
SubCommand string
|
||||
|
||||
// Extra options to pass to the inspec command
|
||||
ExtraArguments []string `mapstructure:"extra_arguments"`
|
||||
InspecEnvVars []string `mapstructure:"inspec_env_vars"`
|
||||
|
||||
// The profile to execute.
|
||||
Profile string `mapstructure:"profile"`
|
||||
AttributesDirectory string `mapstructure:"attributes_directory"`
|
||||
AttributesFiles []string `mapstructure:"attributes"`
|
||||
Backend string `mapstructure:"backend"`
|
||||
User string `mapstructure:"user"`
|
||||
Host string `mapstructure:"host"`
|
||||
LocalPort uint `mapstructure:"local_port"`
|
||||
SSHHostKeyFile string `mapstructure:"ssh_host_key_file"`
|
||||
SSHAuthorizedKeyFile string `mapstructure:"ssh_authorized_key_file"`
|
||||
}
|
||||
|
||||
type Provisioner struct {
|
||||
config Config
|
||||
adapter *adapter.Adapter
|
||||
done chan struct{}
|
||||
inspecVersion string
|
||||
inspecMajVersion uint
|
||||
}
|
||||
|
||||
func (p *Provisioner) Prepare(raws ...interface{}) error {
|
||||
p.done = make(chan struct{})
|
||||
|
||||
err := config.Decode(&p.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &p.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{},
|
||||
},
|
||||
}, raws...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Defaults
|
||||
if p.config.Command == "" {
|
||||
p.config.Command = "inspec"
|
||||
}
|
||||
|
||||
if p.config.SubCommand == "" {
|
||||
p.config.SubCommand = "exec"
|
||||
}
|
||||
|
||||
var errs *packer.MultiError
|
||||
err = validateProfileConfig(p.config.Profile)
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
|
||||
// Check that the authorized key file exists
|
||||
if len(p.config.SSHAuthorizedKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHAuthorizedKeyFile, "ssh_authorized_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHAuthorizedKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
if len(p.config.SSHHostKeyFile) > 0 {
|
||||
err = validateFileConfig(p.config.SSHHostKeyFile, "ssh_host_key_file", true)
|
||||
if err != nil {
|
||||
log.Println(p.config.SSHHostKeyFile, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.Backend == "" {
|
||||
p.config.Backend = "ssh"
|
||||
}
|
||||
|
||||
if _, ok := SupportedBackends[p.config.Backend]; !ok {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("backend: %s must be a valid backend", p.config.Backend))
|
||||
}
|
||||
|
||||
if p.config.Backend == "docker" && p.config.Host == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("backend: host must be specified for docker backend"))
|
||||
}
|
||||
|
||||
if p.config.Host == "" {
|
||||
p.config.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
if p.config.LocalPort > 65535 {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("local_port: %d must be a valid port", p.config.LocalPort))
|
||||
}
|
||||
|
||||
if len(p.config.AttributesDirectory) > 0 {
|
||||
err = validateDirectoryConfig(p.config.AttributesDirectory, "attrs")
|
||||
if err != nil {
|
||||
log.Println(p.config.AttributesDirectory, "does not exist")
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.config.User == "" {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
errs = packer.MultiErrorAppend(errs, err)
|
||||
} else {
|
||||
p.config.User = usr.Username
|
||||
}
|
||||
}
|
||||
if p.config.User == "" {
|
||||
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user: could not determine current user from environment."))
|
||||
}
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) getVersion() error {
|
||||
out, err := exec.Command(p.config.Command, "version").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf(
|
||||
"Error running \"%s version\": %s", p.config.Command, err.Error())
|
||||
}
|
||||
|
||||
versionRe := regexp.MustCompile(`\w (\d+\.\d+[.\d+]*)`)
|
||||
matches := versionRe.FindStringSubmatch(string(out))
|
||||
if matches == nil {
|
||||
return fmt.Errorf(
|
||||
"Could not find %s version in output:\n%s", p.config.Command, string(out))
|
||||
}
|
||||
|
||||
version := matches[1]
|
||||
log.Printf("%s version: %s", p.config.Command, version)
|
||||
p.inspecVersion = version
|
||||
|
||||
majVer, err := strconv.ParseUint(strings.Split(version, ".")[0], 10, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not parse major version from \"%s\".", version)
|
||||
}
|
||||
p.inspecMajVersion = uint(majVer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Provisioner) Provision(ui packer.Ui, comm packer.Communicator) error {
|
||||
ui.Say("Provisioning with Inspec...")
|
||||
|
||||
for i, envVar := range p.config.InspecEnvVars {
|
||||
envVar, err := interpolate.Render(envVar, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec env vars: %s", err)
|
||||
}
|
||||
p.config.InspecEnvVars[i] = envVar
|
||||
}
|
||||
|
||||
for i, arg := range p.config.ExtraArguments {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec extra arguments: %s", err)
|
||||
}
|
||||
p.config.ExtraArguments[i] = arg
|
||||
}
|
||||
|
||||
for i, arg := range p.config.AttributesFiles {
|
||||
arg, err := interpolate.Render(arg, &p.config.ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not interpolate inspec attributes: %s", err)
|
||||
}
|
||||
p.config.AttributesFiles[i] = arg
|
||||
}
|
||||
|
||||
k, err := newUserKey(p.config.SSHAuthorizedKeyFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hostSigner, err := newSigner(p.config.SSHHostKeyFile)
|
||||
// Remove the private key file
|
||||
if len(k.privKeyFile) > 0 {
|
||||
defer os.Remove(k.privKeyFile)
|
||||
}
|
||||
|
||||
keyChecker := ssh.CertChecker{
|
||||
UserKeyFallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if user := conn.User(); user != p.config.User {
|
||||
return nil, errors.New(fmt.Sprintf("authentication failed: %s is not a valid user", user))
|
||||
}
|
||||
|
||||
if !bytes.Equal(k.Marshal(), pubKey.Marshal()) {
|
||||
return nil, errors.New("authentication failed: unauthorized key")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
|
||||
config := &ssh.ServerConfig{
|
||||
AuthLogCallback: func(conn ssh.ConnMetadata, method string, err error) {
|
||||
log.Printf("authentication attempt from %s to %s as %s using %s", conn.RemoteAddr(), conn.LocalAddr(), conn.User(), method)
|
||||
},
|
||||
PublicKeyCallback: keyChecker.Authenticate,
|
||||
//NoClientAuth: true,
|
||||
}
|
||||
|
||||
config.AddHostKey(hostSigner)
|
||||
|
||||
localListener, err := func() (net.Listener, error) {
|
||||
|
||||
port := p.config.LocalPort
|
||||
tries := 1
|
||||
if port != 0 {
|
||||
tries = 10
|
||||
}
|
||||
for i := 0; i < tries; i++ {
|
||||
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port))
|
||||
port++
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
_, portStr, err := net.SplitHostPort(l.Addr().String())
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
portUint64, err := strconv.ParseUint(portStr, 10, 0)
|
||||
if err != nil {
|
||||
ui.Say(err.Error())
|
||||
continue
|
||||
}
|
||||
p.config.LocalPort = uint(portUint64)
|
||||
return l, nil
|
||||
}
|
||||
return nil, errors.New("Error setting up SSH proxy connection")
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ui = &packer.SafeUi{
|
||||
Sem: make(chan int, 1),
|
||||
Ui: ui,
|
||||
}
|
||||
p.adapter = adapter.NewAdapter(p.done, localListener, config, "", ui, comm)
|
||||
|
||||
defer func() {
|
||||
log.Print("shutting down the SSH proxy")
|
||||
close(p.done)
|
||||
p.adapter.Shutdown()
|
||||
}()
|
||||
|
||||
go p.adapter.Serve()
|
||||
|
||||
tf, err := ioutil.TempFile(p.config.AttributesDirectory, "packer-provisioner-inspec.*.yml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
|
||||
w := bufio.NewWriter(tf)
|
||||
w.WriteString(fmt.Sprintf("packer_build_name: %s\n", p.config.PackerBuildName))
|
||||
w.WriteString(fmt.Sprintf("packer_builder_type: %s\n", p.config.PackerBuilderType))
|
||||
|
||||
if err := w.Flush(); err != nil {
|
||||
tf.Close()
|
||||
return fmt.Errorf("Error preparing packer attributes file: %s", err)
|
||||
}
|
||||
tf.Close()
|
||||
p.config.AttributesFiles = append(p.config.AttributesFiles, tf.Name())
|
||||
|
||||
if err := p.executeInspec(ui, comm, k.privKeyFile); err != nil {
|
||||
return fmt.Errorf("Error executing Inspec: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (p *Provisioner) Cancel() {
|
||||
if p.done != nil {
|
||||
close(p.done)
|
||||
}
|
||||
if p.adapter != nil {
|
||||
p.adapter.Shutdown()
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func (p *Provisioner) executeInspec(ui packer.Ui, comm packer.Communicator, privKeyFile string) error {
|
||||
var envvars []string
|
||||
|
||||
args := []string{p.config.SubCommand, p.config.Profile}
|
||||
args = append(args, "--backend", p.config.Backend)
|
||||
args = append(args, "--host", p.config.Host)
|
||||
|
||||
if p.config.Backend == "ssh" {
|
||||
if len(privKeyFile) > 0 {
|
||||
args = append(args, "--key-files", privKeyFile)
|
||||
}
|
||||
args = append(args, "--user", p.config.User)
|
||||
args = append(args, "--port", strconv.FormatUint(uint64(p.config.LocalPort), 10))
|
||||
}
|
||||
|
||||
args = append(args, "--attrs")
|
||||
args = append(args, p.config.AttributesFiles...)
|
||||
args = append(args, p.config.ExtraArguments...)
|
||||
|
||||
if len(p.config.InspecEnvVars) > 0 {
|
||||
envvars = append(envvars, p.config.InspecEnvVars...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(p.config.Command, args...)
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
if len(envvars) > 0 {
|
||||
cmd.Env = append(cmd.Env, envvars...)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
repeat := func(r io.ReadCloser) {
|
||||
reader := bufio.NewReader(r)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if line != "" {
|
||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||
ui.Message(line)
|
||||
}
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else {
|
||||
ui.Error(err.Error())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
wg.Add(2)
|
||||
go repeat(stdout)
|
||||
go repeat(stderr)
|
||||
|
||||
ui.Say(fmt.Sprintf("Executing Inspec: %s", strings.Join(cmd.Args, " ")))
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Wait()
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Non-zero exit status: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateFileConfig(name string, config string, req bool) error {
|
||||
if req {
|
||||
if name == "" {
|
||||
return fmt.Errorf("%s must be specified.", config)
|
||||
}
|
||||
}
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a file", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateProfileConfig(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("profile must be specified.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDirectoryConfig(name string, config string) error {
|
||||
info, err := os.Stat(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %s is invalid: %s", config, name, err)
|
||||
} else if !info.IsDir() {
|
||||
return fmt.Errorf("%s: %s must point to a directory", config, name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type userKey struct {
|
||||
ssh.PublicKey
|
||||
privKeyFile string
|
||||
}
|
||||
|
||||
func newUserKey(pubKeyFile string) (*userKey, error) {
|
||||
userKey := new(userKey)
|
||||
if len(pubKeyFile) > 0 {
|
||||
pubKeyBytes, err := ioutil.ReadFile(pubKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read public key")
|
||||
}
|
||||
userKey.PublicKey, _, _, _, err = ssh.ParseAuthorizedKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse authorized key")
|
||||
}
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate key pair")
|
||||
}
|
||||
userKey.PublicKey, err = ssh.NewPublicKey(key.Public())
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract public key from generated key pair")
|
||||
}
|
||||
|
||||
// To support Inspec calling back to us we need to write
|
||||
// this file down
|
||||
privateKeyDer := x509.MarshalPKCS1PrivateKey(key)
|
||||
privateKeyBlock := pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Headers: nil,
|
||||
Bytes: privateKeyDer,
|
||||
}
|
||||
tf, err := ioutil.TempFile("", "packer-provisioner-inspec.*.key")
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to create temp file for generated key")
|
||||
}
|
||||
_, err = tf.Write(pem.EncodeToMemory(&privateKeyBlock))
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to write private key to temp file")
|
||||
}
|
||||
|
||||
err = tf.Close()
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to close private key temp file")
|
||||
}
|
||||
userKey.privKeyFile = tf.Name()
|
||||
|
||||
return userKey, nil
|
||||
}
|
||||
|
||||
type signer struct {
|
||||
ssh.Signer
|
||||
}
|
||||
|
||||
func newSigner(privKeyFile string) (*signer, error) {
|
||||
signer := new(signer)
|
||||
|
||||
if len(privKeyFile) > 0 {
|
||||
privateBytes, err := ioutil.ReadFile(privKeyFile)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to load private host key")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to parse private host key")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
||||
|
||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to generate server key pair")
|
||||
}
|
||||
|
||||
signer.Signer, err = ssh.NewSignerFromKey(key)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to extract private key from generated key pair")
|
||||
}
|
||||
|
||||
return signer, nil
|
||||
}
|
|
@ -0,0 +1,293 @@
|
|||
package inspec
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
// Be sure to remove the InSpec stub file in each test with:
|
||||
// defer os.Remove(config["command"].(string))
|
||||
func testConfig(t *testing.T) map[string]interface{} {
|
||||
m := make(map[string]interface{})
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
inspec_stub := path.Join(wd, "packer-inspec-stub.sh")
|
||||
|
||||
err = ioutil.WriteFile(inspec_stub, []byte("#!/usr/bin/env bash\necho 2.2.16"), 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
m["command"] = inspec_stub
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func TestProvisioner_Impl(t *testing.T) {
|
||||
var raw interface{}
|
||||
raw = &Provisioner{}
|
||||
if _, ok := raw.(packer.Provisioner); !ok {
|
||||
t.Fatalf("must be a Provisioner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_Defaults(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
err := p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatalf("should have error")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
err = os.Unsetenv("USER")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_ProfileFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["profile"] = profile_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
test_dir, err := ioutil.TempDir("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(test_dir)
|
||||
|
||||
config["profile"] = test_dir
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_HostKeyFile(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = fmt.Sprintf("%x", filename)
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should error if ssh_host_key_file does not exist")
|
||||
}
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_AuthorizedKeyFiles(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
filename := make([]byte, 10)
|
||||
n, err := io.ReadFull(rand.Reader, filename)
|
||||
if n != len(filename) || err != nil {
|
||||
t.Fatal("could not create random file name")
|
||||
}
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
config["ssh_authorized_key_file"] = fmt.Sprintf("%x", filename)
|
||||
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Errorf("should error if ssh_authorized_key_file does not exist")
|
||||
}
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Errorf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvisionerPrepare_LocalPort(t *testing.T) {
|
||||
var p Provisioner
|
||||
config := testConfig(t)
|
||||
defer os.Remove(config["command"].(string))
|
||||
|
||||
hostkey_file, err := ioutil.TempFile("", "hostkey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(hostkey_file.Name())
|
||||
|
||||
publickey_file, err := ioutil.TempFile("", "publickey")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(publickey_file.Name())
|
||||
|
||||
profile_file, err := ioutil.TempFile("", "test")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
defer os.Remove(profile_file.Name())
|
||||
|
||||
config["ssh_host_key_file"] = hostkey_file.Name()
|
||||
config["ssh_authorized_key_file"] = publickey_file.Name()
|
||||
config["profile"] = profile_file.Name()
|
||||
|
||||
config["local_port"] = uint(65537)
|
||||
err = p.Prepare(config)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
config["local_port"] = uint(22222)
|
||||
err = p.Prepare(config)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersion(t *testing.T) {
|
||||
if os.Getenv("PACKER_ACC") == "" {
|
||||
t.Skip("This test is only run with PACKER_ACC=1 and it requires InSpec to be installed")
|
||||
}
|
||||
|
||||
var p Provisioner
|
||||
p.config.Command = "inspec exec"
|
||||
err := p.getVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInspecGetVersionError(t *testing.T) {
|
||||
var p Provisioner
|
||||
p.config.Command = "./test-fixtures/exit1"
|
||||
err := p.getVersion()
|
||||
if err == nil {
|
||||
t.Fatal("Should return error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "./test-fixtures/exit1 version") {
|
||||
t.Fatal("Error message should include command name")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/sh
|
||||
|
||||
exit 1
|
|
@ -25,6 +25,7 @@ still distributed with Packer.
|
|||
## Provisioners
|
||||
|
||||
- File
|
||||
- InSpec
|
||||
- PowerShell
|
||||
- Shell
|
||||
- Windows Restart
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
description: |
|
||||
The inspec Packer provisioner allows inspec profiles to be run to test the
|
||||
machine.
|
||||
layout: docs
|
||||
page_title: 'InSpec - Provisioners'
|
||||
sidebar_current: 'docs-provisioners-inspec'
|
||||
---
|
||||
|
||||
# InSpec Provisioner
|
||||
|
||||
Type: `inspec`
|
||||
|
||||
The `inspec` Packer provisioner runs InSpec profiles. It dynamically creates a
|
||||
target configured to use SSH, runs an SSH server, executes `inspec exec`, and
|
||||
marshals InSpec tests through the SSH server to the machine being provisioned
|
||||
by Packer.
|
||||
|
||||
## Basic Example
|
||||
|
||||
This is a fully functional template that will test an image on DigitalOcean.
|
||||
Replace the mock `api_token` value with your own.
|
||||
|
||||
``` json
|
||||
{
|
||||
"provisioners": [
|
||||
{
|
||||
"type": "inspec",
|
||||
"profile": "https://github.com/dev-sec/linux-baseline"
|
||||
}
|
||||
],
|
||||
|
||||
"builders": [
|
||||
{
|
||||
"type": "digitalocean",
|
||||
"api_token": "<digital ocean api token>",
|
||||
"image": "ubuntu-14-04-x64",
|
||||
"region": "sfo1"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
Required Parameters:
|
||||
|
||||
- `profile` (string) - The profile to be executed by InSpec.
|
||||
|
||||
Optional Parameters:
|
||||
|
||||
- `inspec_env_vars` (array of strings) - Environment variables to set before
|
||||
running InSpec. Usage example:
|
||||
|
||||
``` json
|
||||
"inspec_env_vars": [ "FOO=bar" ]
|
||||
```
|
||||
|
||||
- `command` (string) - The command to invoke InSpec. Defaults to `inspec`.
|
||||
|
||||
- `extra_arguments` (array of strings) - Extra arguments to pass to InSpec.
|
||||
These arguments *will not* be passed through a shell and arguments should
|
||||
not be quoted. Usage example:
|
||||
|
||||
``` json
|
||||
"extra_arguments": [ "--sudo", "--reporter", "json" ]
|
||||
```
|
||||
|
||||
- `attributes` (array of strings) - Attribute Files used by InSpec which will
|
||||
be passed to the `--attrs` argument of the `inspec` command when this
|
||||
provisioner runs InSpec. Specify this if you want a different location.
|
||||
Note using also `"--attrs"` in `extra_arguments` will override this
|
||||
setting.
|
||||
|
||||
- `attributes_directory` (string) - The directory in which to place the
|
||||
temporary generated InSpec Attributes file. By default, this is the
|
||||
system-specific temporary file location. The fully-qualified name of this
|
||||
temporary file will be passed to the `--attrs` argument of the `inspec`
|
||||
command when this provisioner runs InSpec. Specify this if you want a
|
||||
different location.
|
||||
|
||||
- `backend` (string) - Backend used by InSpec for connection. Defaults to
|
||||
SSH.
|
||||
|
||||
- `host` (string) - Host used for by InSpec for connection. Defaults to
|
||||
localhost.
|
||||
|
||||
- `local_port` (uint) - The port on which to attempt to listen for SSH
|
||||
connections. This value is a starting point. The provisioner will attempt to
|
||||
listen for SSH connections on the first available of ten ports, starting at
|
||||
`local_port`. A system-chosen port is used when `local_port` is missing or
|
||||
empty.
|
||||
|
||||
- `ssh_host_key_file` (string) - The SSH key that will be used to run the SSH
|
||||
server on the host machine to forward commands to the target machine.
|
||||
InSpec connects to this server and will validate the identity of the server
|
||||
using the system known\_hosts. The default behavior is to generate and use
|
||||
a onetime key.
|
||||
|
||||
- `ssh_authorized_key_file` (string) - The SSH public key of the InSpec
|
||||
`ssh_user`. The default behavior is to generate and use a onetime key. If
|
||||
this key is generated, the corresponding private key is passed to `inspec`
|
||||
command with the `-i inspec_ssh_private_key_file` option.
|
||||
|
||||
- `user` (string) - The `--user` to use. Defaults to the user running Packer.
|
||||
|
||||
## Default Extra Variables
|
||||
|
||||
In addition to being able to specify extra arguments using the
|
||||
`extra_arguments` configuration, the provisioner automatically defines certain
|
||||
commonly useful InSpec Attributes:
|
||||
|
||||
- `packer_build_name` is set to the name of the build that Packer is running.
|
||||
This is most useful when Packer is making multiple builds and you want to
|
||||
distinguish them slightly when using a common profile.
|
||||
|
||||
- `packer_builder_type` is the type of the builder that was used to create
|
||||
the machine that the script is running on. This is useful if you want to
|
||||
run only certain parts of the profile on systems built with certain
|
||||
builders.
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug underlying issues with InSpec, add `"-l"` to `"extra_arguments"` to
|
||||
enable verbose logging.
|
||||
|
||||
``` json
|
||||
{
|
||||
"extra_arguments": [ "-l", "debug" ]
|
||||
}
|
||||
```
|
|
@ -219,6 +219,9 @@
|
|||
<li<%= sidebar_current("docs-provisioners-file")%>>
|
||||
<a href="/docs/provisioners/file.html">File</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-provisioners-inspec")%>>
|
||||
<a href="/docs/provisioners/inspec.html">InSpec</a>
|
||||
</li>
|
||||
<li<%= sidebar_current("docs-provisioners-powershell")%>>
|
||||
<a href="/docs/provisioners/powershell.html">PowerShell</a>
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue