Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Wouldn't it be nice if you could run [goss](https://github.com/aelsabbahy/goss) tests against an image during a packer build?

Well, I thought it would, so now you can! This currently only works for building a `linux` image since goss only runs in linux.
Well, I thought it would, so now you can!

This runs during the provisioning process since the machine being provisioned is only available at that time.

Expand Down Expand Up @@ -50,6 +50,7 @@ There is an example packer build with goss tests in the `example/` directory.
"format": "",
"goss_file": "",
"vars_file": "",
"targetOs": "Linux",
"vars_env": {
"ARCH": "amd64",
"PROVIDER": "{{user `cloud-provider`}}"
Expand All @@ -67,6 +68,10 @@ There is an example packer build with goss tests in the `example/` directory.
## Spec files
Goss spec file and debug spec file (`goss render -d`) are downloaded to `/tmp` folder on local machine from the remote VM. These files are exact specs GOSS validated on the VM. The downloaded GOSS spec can be used to validate any other VM image for equivalency.

## Windows support

This now has support for Windows. Set the optional parameter `targetOs` to `Windows`. Currently, the `vars_env` parameter must include `GOSS_USE_ALPHA=1` as specified in [goss's feature parity document](https://github.com/aelsabbahy/goss/blob/master/docs/platform-feature-parity.md#platform-feature-parity). In the future when goss come of of alpha for Windows this parameter will not be required.

## Installation

1. Download the most recent release for your platform from [here.](https://github.com/YaleUniversity/packer-provisioner-goss/releases).
Expand All @@ -91,6 +96,7 @@ Goss spec file and debug spec file (`goss render -d`) are downloaded to `/tmp` f

```bash
docker run --rm -it -v "$PWD":/usr/src/packer-provisioner-goss -w /usr/src/packer-provisioner-goss -e 'VERSION=v1.0.0' golang:1.13 bash
go test ./...
for GOOS in darwin linux windows; do
for GOARCH in 386 amd64; do
export GOOS GOARCH
Expand Down
94 changes: 79 additions & 15 deletions packer-provisioner-goss.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ import (
"github.com/hashicorp/packer/template/interpolate"
)

const gossSpecFile = "/tmp/goss-spec.yaml"
const gossDebugSpecFile = "/tmp/debug-goss-spec.yaml"
const (
gossSpecFile = "/tmp/goss-spec.yaml"
gossDebugSpecFile = "/tmp/debug-goss-spec.yaml"
linux = "Linux"
windows = "Windows"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand why you did this, but I'm inclined to simplify this and just use a string and get rid of the const. It will simplify the code below as well. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which logic? I think we still need all the checks in place as well as the ToLower to ensure the url is written properly. I could replace the OsType type and make it just a string?


// GossConfig holds the config data coming in from the packer template
type GossConfig struct {
Expand All @@ -33,6 +37,7 @@ type GossConfig struct {
Password string
SkipInstall bool
Inspect bool
TargetOs string

// An array of tests to run.
Tests []string
Expand Down Expand Up @@ -126,20 +131,31 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
p.config.Arch = "amd64"
}

if p.config.TargetOs == "" {
p.config.TargetOs = linux
}

if p.config.URL == "" {
p.config.URL = fmt.Sprintf(
"https://github.com/aelsabbahy/goss/releases/download/v%s/goss-linux-%s",
p.config.Version, p.config.Arch)
p.config.URL = p.getDownloadUrl()
}

if p.config.DownloadPath == "" {
os := strings.ToLower(p.config.TargetOs)
if p.config.URL == "" {
p.config.DownloadPath = fmt.Sprintf("/tmp/goss-%s-linux-%s", p.config.Version, p.config.Arch)
p.config.DownloadPath = fmt.Sprintf("/tmp/goss-%s-%s-%s", p.config.Version, os, p.config.Arch)
} else {
list := strings.Split(p.config.URL, "/")
arch := strings.Split(list[len(list)-1], "-")[2]

file := strings.Split(list[len(list)-1], "-")
arch := file[2]
if p.isGossAlpha() {
// The format of the alpha files includes an additional entry
// ex: goss-alpha-windows-amd64.exe
arch = file[3]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the comment confused me at first (I thought maybe this was only to support windows with the.exe - please strike it or clarify that the format of the alpha file is different.

}

version := strings.TrimPrefix(list[len(list)-2], "v")
p.config.DownloadPath = fmt.Sprintf("/tmp/goss-%s-linux-%s", version, arch)
p.config.DownloadPath = fmt.Sprintf("/tmp/goss-%s-%s-%s", version, os, arch)
}
}

Expand Down Expand Up @@ -202,6 +218,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
}

if p.config.TargetOs != linux && p.config.TargetOs != windows {
errs = packer.MultiErrorAppend(errs,
fmt.Errorf("Os must be %s or %s", linux, windows))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


if errs != nil && len(errs.Errors) > 0 {
return errs
}
Expand All @@ -212,6 +233,12 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
// Provision runs the Goss Provisioner
func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, generatedData map[string]interface{}) error {
ui.Say("Provisioning with Goss")
ui.Say(fmt.Sprintf("Configured to run on %s", string(p.config.TargetOs)))

// For Windows need to create the target directory before download
if err := p.createDir(ui, comm, p.config.RemotePath); err != nil {
return fmt.Errorf("Error creating remote directory: %s", err)
}

if !p.config.SkipInstall {
if err := p.installGoss(ui, comm); err != nil {
Expand All @@ -222,10 +249,6 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
}

ui.Say("Uploading goss tests...")
if err := p.createDir(ui, comm, p.config.RemotePath); err != nil {
return fmt.Errorf("Error creating remote directory: %s", err)
}

if p.config.VarsFile != "" {
vf, err := os.Stat(p.config.VarsFile)
if err != nil {
Expand Down Expand Up @@ -416,18 +439,50 @@ 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))
switch p.config.TargetOs {
case windows:
// don't include single quotes which confused cmd parsing
return fmt.Sprintf("--vars-inline %s", string(inlineVarsJson))
default:
return fmt.Sprintf("--vars-inline '%s'", string(inlineVarsJson))
}
} else {
fmt.Errorf("Error converting inline vars to json string %v", err)
}
}
return ""
}

func (p *Provisioner) getDownloadUrl() string {
os := strings.ToLower(string(p.config.TargetOs))
filename := fmt.Sprintf("goss-%s-%s", os, p.config.Arch)

if p.isGossAlpha() {
filename = fmt.Sprintf("goss-alpha-%s-%s", os, p.config.Arch)
}

if p.config.TargetOs == windows {
filename = fmt.Sprintf("%s.exe", filename)
}

return fmt.Sprintf("https://github.com/aelsabbahy/goss/releases/download/v%s/%s", p.config.Version, filename)
}

func (p *Provisioner) isGossAlpha() bool {
return p.config.VarsEnv["GOSS_USE_ALPHA"] == "1"
}

func (p *Provisioner) envVars() string {
var sb strings.Builder
for env_var, value := range p.config.VarsEnv {
sb.WriteString(fmt.Sprintf("%s=\"%s\" ", env_var, value))
switch p.config.TargetOs {
case windows:
// Windows requires a call to "set" as separate command seperated by && for each env variable
sb.WriteString(fmt.Sprintf("set \"%s=%s\" && ", env_var, value))
default:
sb.WriteString(fmt.Sprintf("%s=\"%s\" ", env_var, value))
}

}
return sb.String()
}
Expand Down Expand Up @@ -481,7 +536,7 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
ctx := context.TODO()

cmd := &packer.RemoteCmd{
Command: fmt.Sprintf("mkdir -p '%s'", dir),
Command: p.mkDir(dir),
}
if err := cmd.RunWithUi(ctx, comm, ui); err != nil {
return err
Expand All @@ -492,6 +547,15 @@ func (p *Provisioner) createDir(ui packer.Ui, comm packer.Communicator, dir stri
return nil
}

func (p *Provisioner) mkDir(dir string) string {
switch p.config.TargetOs {
case windows:
return fmt.Sprintf("powershell /c mkdir -p '%s'", dir)
default:
return fmt.Sprintf("mkdir -p '%s'", dir)
}
}

// uploadFile uploads a file
func (p *Provisioner) uploadFile(ui packer.Ui, comm packer.Communicator, dst, src string) error {
f, err := os.Open(src)
Expand Down
2 changes: 2 additions & 0 deletions packer-provisioner-goss.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading