Skip to content

Commit

Permalink
Add support for Bolt project path and installing modules
Browse files Browse the repository at this point in the history
  • Loading branch information
martezr committed Sep 18, 2020
1 parent 3c5bdf1 commit 4533442
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 8 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ Optional Parameters:
|------|------|-------------|
|bolt_module_path| string |The path that Bolt should look for modules |
|bolt_params | json | The parameters to pass the Bolt Task or Plan. |
|install_modules|boolean|Whether to install modules from a Bolt project using the Puppetfile|
|inventory_file | string | The inventory file to use during provisioning. When unspecified, Packer will create a temporary inventory file and will use the host_alias.|
|local_port|uint|The port on which to attempt to listen for SSH connections. This value is a starting point. The provisioner will attempt 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.|
|user|string|The bolt_user to use. Defaults to the user running packer.|
|log_level|string|The level of logging (debug, error, info, notice, warn, fatal, any)|

|project_path|string|The path that Bolt should look for a Bolt project directory|
|user|string|The bolt_user to use. Defaults to the user running packer.|

SSH Settings
------------
Expand Down
97 changes: 91 additions & 6 deletions bolt/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type Config struct {
// The bolt module path
BoltModulePath string `mapstructure:"bolt_module_path"`

// The bolt project path
ProjectPath string `mapstructure:"project_path"`

// The bolt inventory file
InventoryFile string `mapstructure:"inventory_file"`

Expand All @@ -95,6 +98,7 @@ type Config struct {
NoWinRMSSLVerify bool `mapstructure:"no_winrm_ssl_verify"`
NoWinRMSSL bool `mapstructure:"no_winrm_ssl"`
LogLevel string `mapstructure:"log_level"`
InstallModules bool `mapstructure:"install_modules"`
}

// Provisioner data passed to the provision operation
Expand Down Expand Up @@ -165,13 +169,22 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {

// Validate that the specified module path exists
if len(p.config.BoltModulePath) > 0 {
err = validateDirectoryConfig(p.config.BoltModulePath)
err = validateDirectoryConfig(p.config.BoltModulePath, "bolt_module_path")
if err != nil {
log.Println(p.config.BoltModulePath, "does not exist")
errs = packer.MultiErrorAppend(errs, err)
}
}

// Validate that the specified project path exists
if len(p.config.ProjectPath) > 0 {
err = validateDirectoryConfig(p.config.ProjectPath, "project_path")
if err != nil {
log.Println(p.config.ProjectPath, "does not exist")
errs = packer.MultiErrorAppend(errs, err)
}
}

// Validate that the specified log level is valid
validLogLevels := []string{"debug", "info", "notice", "warn", "error", "fatal", "any"}
if p.config.LogLevel != "" {
Expand Down Expand Up @@ -209,7 +222,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}

if len(p.config.InventoryDirectory) > 0 {
err = validateDirectoryConfig(p.config.InventoryDirectory)
err = validateDirectoryConfig(p.config.InventoryDirectory, "inventory_directory")
if err != nil {
log.Println(p.config.InventoryDirectory, "does not exist")
errs = packer.MultiErrorAppend(errs, err)
Expand All @@ -222,6 +235,66 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
return nil
}

func (p *Provisioner) installModules(ui packer.Ui) error {
args := []string{"puppetfile", "install"}
boltprojectpath := p.config.ProjectPath
if p.config.ProjectPath != "" {
args = append(args, "--project", boltprojectpath)
}

cmd := exec.Command(p.config.Command, args...)
var envvars []string
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("Installing Puppet modules: %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 (p *Provisioner) getVersion() error {
out, err := exec.Command(p.config.Command, "--version").Output()
if err != nil {
Expand Down Expand Up @@ -254,6 +327,13 @@ func (p *Provisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.C
ui.Say("Provisioning with Puppet Bolt...")
p.config.ctx.Data = generatedData

if p.config.InstallModules {
err := p.installModules(ui)
if err != nil {
return fmt.Errorf("error installing modules: %s", err)
}
}

if p.config.Backend == "" {
p.config.Backend = generatedData["ConnType"].(string)
}
Expand Down Expand Up @@ -383,6 +463,7 @@ func (p *Provisioner) executeBolt(ui packer.Ui, comm packer.Communicator, privKe
bolttask := p.config.BoltTask
boltplan := p.config.BoltPlan
boltmodulepath := p.config.BoltModulePath
boltprojectpath := p.config.ProjectPath
boltparams := p.config.BoltParams

var envvars []string
Expand Down Expand Up @@ -413,6 +494,10 @@ func (p *Provisioner) executeBolt(ui packer.Ui, comm packer.Communicator, privKe
args = append(args, "--modulepath", boltmodulepath)
}

if p.config.ProjectPath != "" {
args = append(args, "--project", boltprojectpath)
}

args = append(args, "--targets", target)
args = append(args, "--user", p.config.User)

Expand Down Expand Up @@ -575,7 +660,7 @@ func convertParams(m map[interface{}]interface{}) map[string]interface{} {
func validateFileConfig(name string, config string, req bool) error {
if req {
if name == "" {
return fmt.Errorf("%s must be specified.", config)
return fmt.Errorf("%s must be specified", config)
}
}
info, err := os.Stat(name)
Expand All @@ -587,12 +672,12 @@ func validateFileConfig(name string, config string, req bool) error {
return nil
}

func validateDirectoryConfig(name string) error {
func validateDirectoryConfig(name string, setting string) error {
info, err := os.Stat(name)
if err != nil {
return fmt.Errorf("Directory: %s is invalid: %s", name, err)
return fmt.Errorf("Directory: %s specified for %s is invalid: %s", name, setting, err)
} else if !info.IsDir() {
return fmt.Errorf("Directory: %s must point to a directory", name)
return fmt.Errorf("Directory: %s specified for %s must point to a directory", name, setting)
}
return nil
}
Expand Down
4 changes: 4 additions & 0 deletions bolt/provisioner.hcl2spec.go

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

41 changes: 41 additions & 0 deletions bolt/provisioner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,47 @@ func TestProvisionerPrepare_InventoryDirectory(t *testing.T) {
}
}

func TestProvisionerPrepare_ProjectDirectory(t *testing.T) {
var p Provisioner
config := testConfig(t)
defer os.Remove(config["command"].(string))

hostkeyFile, err := ioutil.TempFile("", "hostkey")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(hostkeyFile.Name())

publickeyFile, err := ioutil.TempFile("", "publickey")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(publickeyFile.Name())

config["ssh_host_key_file"] = hostkeyFile.Name()
config["ssh_authorized_key_file"] = publickeyFile.Name()
config["user"] = "root"
config["bolt_task"] = "facts"

config["project_path"] = "doesnotexist"
err = p.Prepare(config)
if err == nil {
t.Errorf("should error if project_path does not exist")
}

projectDirectory, err := ioutil.TempDir("", "some_project_dir")
if err != nil {
t.Fatalf("err: %s", err)
}
defer os.Remove(projectDirectory)

config["project_path"] = projectDirectory
err = p.Prepare(config)
if err != nil {
t.Fatalf("err: %s", err)
}
}

func TestProvisionerPrepare_BoltTask(t *testing.T) {
var p Provisioner
config := testConfig(t)
Expand Down

0 comments on commit 4533442

Please sign in to comment.