Skip to content

Commit

Permalink
Add support for using goss to target Windows (#39)
Browse files Browse the repository at this point in the history
* Add support for using goss to target Windows
  • Loading branch information
jsturtevant authored Feb 25, 2021
1 parent c4cfdcd commit 0705d97
Show file tree
Hide file tree
Showing 4 changed files with 362 additions and 16 deletions.
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"
)

// 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]
}

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))
}

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

0 comments on commit 0705d97

Please sign in to comment.