diff --git a/README.md b/README.md index f4ea9ab..abe242e 100644 --- a/README.md +++ b/README.md @@ -26,24 +26,34 @@ There is an example packer build with goss tests in the `example/` directory. ```json "provisioners" : [ { + # Packer Args "type": "goss", - "version": "0.3.2", + + # Packer Provisioner Args "arch": "amd64", + "downloadPath": "/tmp/goss-VERSION-linux-ARCH", + "inspect": "{{user `inspect_mode`}}", + "password": "", + "skipInstall": false, "url":"https://github.com/aelsabbahy/goss/releases/download/vVERSION/goss-linux-ARCH", + "username": "", + "version": "0.3.2", + + # GOSS Args "tests": [ "goss/goss.yaml" ], - "downloadPath": "/tmp/goss-VERSION-linux-ARCH", "remote_folder": "/tmp", "remote_path": "/tmp/goss", - "skipInstall": false, "skip_ssl": false, "use_sudo": false, "format": "", "goss_file": "", "vars_file": "", - "username": "", - "password": "", + "vars_inline": { + "OS": "centos", + "version": "{{user `version`}}" + }, "retry_timeout": "0s", "sleep": "1s" } diff --git a/packer-provisioner-goss.go b/packer-provisioner-goss.go index 19b418d..01e14d5 100644 --- a/packer-provisioner-goss.go +++ b/packer-provisioner-goss.go @@ -4,6 +4,7 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "os" @@ -28,6 +29,7 @@ type GossConfig struct { Username string Password string SkipInstall bool + Inspect bool // An array of tests to run. Tests []string @@ -51,6 +53,10 @@ type GossConfig struct { // Can be YAML or JSON. VarsFile string `mapstructure:"vars_file"` + // The --vars-inline flag + // Optional inline variables that overrides JSON file vars + VarsInline map[string]string `mapstructure:"vars_inline"` + // The remote folder where the goss tests will be uploaded to. // This should be set to a pre-existing directory, it defaults to /tmp RemoteFolder string `mapstructure:"remote_folder"` @@ -227,6 +233,10 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C } } } + if len(p.config.VarsInline) != 0 { + ui.Message(fmt.Sprintf("Inline variables are %v", p.config.VarsInline)) + ui.Message(fmt.Sprintf("Inline variable string is %s", p.inline_vars())) + } for _, src := range p.config.Tests { s, err := os.Stat(src) @@ -290,19 +300,28 @@ func (p *Provisioner) runGoss(ui packer.Ui, comm packer.Communicator) error { goss := fmt.Sprintf("%s", p.config.DownloadPath) ctx := context.TODO() + strcmd := fmt.Sprintf("cd %s && %s %s %s %s %s validate --retry-timeout %s --sleep %s %s %s", + p.config.RemotePath, p.enableSudo(), goss, p.config.GossFile, + p.vars(), p.inline_vars(), p.retryTimeout(), p.sleep(), p.format(), p.formatOptions()) + ui.Message(fmt.Sprintf("Command : %s", strcmd)) + cmd := &packer.RemoteCmd{ - Command: fmt.Sprintf( - "cd %s && %s %s %s %s validate --retry-timeout %s --sleep %s %s %s", - p.config.RemotePath, p.enableSudo(), goss, p.config.GossFile, - p.vars(), p.retryTimeout(), p.sleep(), p.format(), p.formatOptions()), + Command: strcmd, } if err := cmd.RunWithUi(ctx, comm, ui); err != nil { return err } if cmd.ExitStatus() != 0 { - return fmt.Errorf("goss non-zero exit status") + // Inspect mode is on. Report failure but don't fail. + if p.config.Inspect { + ui.Say(fmt.Sprintf("Goss tests failed")) + ui.Say(fmt.Sprintf("Inpect mode on : proceeding without failing Packer")) + } else { + return fmt.Errorf("goss non-zero exit status") + } + } else { + ui.Say(fmt.Sprintf("Goss tests ran successfully")) } - ui.Say(fmt.Sprintf("Goss tests ran successfully")) return nil } @@ -341,6 +360,18 @@ func (p *Provisioner) vars() string { return "" } +func (p *Provisioner) inline_vars() string { + if len(p.config.VarsInline) != 0 { + inlineVarsJson, err := json.Marshal(p.config.VarsInline) + if err == nil { + return fmt.Sprintf("--vars-inline '%s'", string(inlineVarsJson)) + } else { + fmt.Errorf("Error converting inline vars to json string %v", err) + } + } + return "" +} + func (p *Provisioner) sslFlag(cmdType string) string { if p.config.SkipSSLChk { switch cmdType { diff --git a/packer-provisioner-goss.hcl2spec.go b/packer-provisioner-goss.hcl2spec.go index ea525a6..8ee147f 100644 --- a/packer-provisioner-goss.hcl2spec.go +++ b/packer-provisioner-goss.hcl2spec.go @@ -9,24 +9,26 @@ import ( // FlatGossConfig is an auto-generated flat version of GossConfig. // Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. type FlatGossConfig struct { - Version *string `cty:"version"` - Arch *string `cty:"arch"` - URL *string `cty:"url"` - DownloadPath *string `cty:"download_path"` - Username *string `cty:"username"` - Password *string `cty:"password"` - SkipInstall *bool `cty:"skip_install"` - Tests []string `cty:"tests"` - RetryTimeout *string `mapstructure:"retry_timeout" cty:"retry_timeout"` - Sleep *string `mapstructure:"sleep" cty:"sleep"` - UseSudo *bool `mapstructure:"use_sudo" cty:"use_sudo"` - SkipSSLChk *bool `mapstructure:"skip_ssl" cty:"skip_ssl"` - GossFile *string `mapstructure:"goss_file" cty:"goss_file"` - VarsFile *string `mapstructure:"vars_file" cty:"vars_file"` - RemoteFolder *string `mapstructure:"remote_folder" cty:"remote_folder"` - RemotePath *string `mapstructure:"remote_path" cty:"remote_path"` - Format *string `mapstructure:"format" cty:"format"` - FormatOptions *string `mapstructure:"format_options" cty:"format_options"` + Version *string `cty:"version"` + Arch *string `cty:"arch"` + URL *string `cty:"url"` + DownloadPath *string `cty:"download_path"` + Username *string `cty:"username"` + Password *string `cty:"password"` + SkipInstall *bool `cty:"skip_install"` + Tests []string `cty:"tests"` + RetryTimeout *string `mapstructure:"retry_timeout" cty:"retry_timeout"` + Sleep *string `mapstructure:"sleep" cty:"sleep"` + UseSudo *bool `mapstructure:"use_sudo" cty:"use_sudo"` + SkipSSLChk *bool `mapstructure:"skip_ssl" cty:"skip_ssl"` + GossFile *string `mapstructure:"goss_file" cty:"goss_file"` + VarsFile *string `mapstructure:"vars_file" cty:"vars_file"` + VarsInline map[string]string `mapstructure:"vars_inline" cty:"vars_inline"` + RemoteFolder *string `mapstructure:"remote_folder" cty:"remote_folder"` + RemotePath *string `mapstructure:"remote_path" cty:"remote_path"` + Format *string `mapstructure:"format" cty:"format"` + FormatOptions *string `mapstructure:"format_options" cty:"format_options"` + Inspect *bool `cty:"inspect"` } // FlatMapstructure returns a new FlatGossConfig. @@ -55,10 +57,12 @@ func (*FlatGossConfig) HCL2Spec() map[string]hcldec.Spec { "skip_ssl": &hcldec.AttrSpec{Name: "skip_ssl", Type: cty.Bool, Required: false}, "goss_file": &hcldec.AttrSpec{Name: "goss_file", Type: cty.String, Required: false}, "vars_file": &hcldec.AttrSpec{Name: "vars_file", Type: cty.String, Required: false}, + "vars_inline": &hcldec.AttrSpec{Name: "vars_inline", Type: cty.Map(cty.String), Required: false}, "remote_folder": &hcldec.AttrSpec{Name: "remote_folder", Type: cty.String, Required: false}, "remote_path": &hcldec.AttrSpec{Name: "remote_path", Type: cty.String, Required: false}, "format": &hcldec.AttrSpec{Name: "format", Type: cty.String, Required: false}, "format_options": &hcldec.AttrSpec{Name: "format_options", Type: cty.String, Required: false}, + "inspect": &hcldec.AttrSpec{Name: "inspect", Type: cty.Bool, Required: false}, } return s }