implemented null buider
The null builder is not really a bulider, it just setups a SSH connection and runs the provisioners. It can be used to debug provisioners without incurring high wait times. It does not create any kind of image or artifact.
This commit is contained in:
parent
dc21bf011a
commit
b879ec85cc
|
@ -0,0 +1,29 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dummy Artifact implementation - does nothing
|
||||||
|
type NullArtifact struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NullArtifact) BuilderId() string {
|
||||||
|
return BuilderId
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NullArtifact) Files() []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*NullArtifact) Id() string {
|
||||||
|
return "Null"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NullArtifact) String() string {
|
||||||
|
return fmt.Sprintf("Did not export anything. This is the null builder")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *NullArtifact) Destroy() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNullArtifact(t *testing.T) {
|
||||||
|
var _ packer.Artifact = new(NullArtifact)
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const BuilderId = "fnoeding.null"
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
config *Config
|
||||||
|
runner multistep.Runner
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||||
|
c, warnings, errs := NewConfig(raws...)
|
||||||
|
if errs != nil {
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
b.config = c
|
||||||
|
|
||||||
|
return warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||||
|
steps := []multistep.Step{
|
||||||
|
&common.StepConnectSSH{
|
||||||
|
SSHAddress: SSHAddress(b.config.Host, b.config.Port),
|
||||||
|
SSHConfig: SSHConfig(b.config.SSHUsername, b.config.SSHPassword, b.config.SSHPrivateKeyFile),
|
||||||
|
SSHWaitTimeout: 1 * time.Minute,
|
||||||
|
},
|
||||||
|
&common.StepProvision{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup the state bag and initial state for the steps
|
||||||
|
state := new(multistep.BasicStateBag)
|
||||||
|
state.Put("config", b.config)
|
||||||
|
state.Put("hook", hook)
|
||||||
|
state.Put("ui", ui)
|
||||||
|
|
||||||
|
// Run!
|
||||||
|
if b.config.PackerDebug {
|
||||||
|
b.runner = &multistep.DebugRunner{
|
||||||
|
Steps: steps,
|
||||||
|
PauseFn: common.MultistepDebugFn(ui),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.runner = &multistep.BasicRunner{Steps: steps}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.runner.Run(state)
|
||||||
|
|
||||||
|
// If there was an error, return that
|
||||||
|
if rawErr, ok := state.GetOk("error"); ok {
|
||||||
|
return nil, rawErr.(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No errors, must've worked
|
||||||
|
artifact := &NullArtifact{}
|
||||||
|
return artifact, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) Cancel() {
|
||||||
|
if b.runner != nil {
|
||||||
|
log.Println("Cancelling the step runner...")
|
||||||
|
b.runner.Cancel()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuilder_implBuilder(t *testing.T) {
|
||||||
|
var _ packer.Builder = new(Builder)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/packer/common"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
common.PackerConfig `mapstructure:",squash"`
|
||||||
|
|
||||||
|
Host string `mapstructure:"host"`
|
||||||
|
Port int `mapstructure:"port"`
|
||||||
|
SSHUsername string `mapstructure:"ssh_username"`
|
||||||
|
SSHPassword string `mapstructure:"ssh_password"`
|
||||||
|
SSHPrivateKeyFile string `mapstructure:"ssh_private_key_file"`
|
||||||
|
|
||||||
|
tpl *packer.ConfigTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig(raws ...interface{}) (*Config, []string, error) {
|
||||||
|
c := new(Config)
|
||||||
|
md, err := common.DecodeConfig(c, raws...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.tpl, err = packer.NewConfigTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.tpl.UserVars = c.PackerUserVars
|
||||||
|
|
||||||
|
// Defaults
|
||||||
|
if c.Port == 0 {
|
||||||
|
c.Port = 22
|
||||||
|
}
|
||||||
|
// (none so far)
|
||||||
|
|
||||||
|
errs := common.CheckUnusedConfig(md)
|
||||||
|
|
||||||
|
if c.Host == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("host must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHUsername == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("ssh_username must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPassword == "" && c.SSHPrivateKeyFile == "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("one of ssh_password and ssh_private_key_file must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.SSHPassword != "" && c.SSHPrivateKeyFile != "" {
|
||||||
|
errs = packer.MultiErrorAppend(errs,
|
||||||
|
fmt.Errorf("only one of ssh_password and ssh_private_key_file must be specified"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if errs != nil && len(errs.Errors) > 0 {
|
||||||
|
return nil, nil, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
return c, nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testConfig() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"host": "foo",
|
||||||
|
"ssh_username": "bar",
|
||||||
|
"ssh_password": "baz",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigStruct(t *testing.T) *Config {
|
||||||
|
c, warns, errs := NewConfig(testConfig())
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", len(warns))
|
||||||
|
}
|
||||||
|
if errs != nil {
|
||||||
|
t.Fatalf("bad: %#v", errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigErr(t *testing.T, warns []string, err error) {
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigOk(t *testing.T, warns []string, err error) {
|
||||||
|
if len(warns) > 0 {
|
||||||
|
t.Fatalf("bad: %#v", warns)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigPrepare_port(t *testing.T) {
|
||||||
|
raw := testConfig()
|
||||||
|
|
||||||
|
// default port should be 22
|
||||||
|
delete(raw, "port")
|
||||||
|
c, warns, errs := NewConfig(raw)
|
||||||
|
if c.Port != 22 {
|
||||||
|
t.Fatalf("bad: port should default to 22, not %d", c.Port)
|
||||||
|
}
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigPrepare_host(t *testing.T) {
|
||||||
|
raw := testConfig()
|
||||||
|
|
||||||
|
// No host
|
||||||
|
delete(raw, "host")
|
||||||
|
_, warns, errs := NewConfig(raw)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// Good host
|
||||||
|
raw["host"] = "good"
|
||||||
|
_, warns, errs = NewConfig(raw)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigPrepare_sshUsername(t *testing.T) {
|
||||||
|
raw := testConfig()
|
||||||
|
|
||||||
|
// No ssh_username
|
||||||
|
delete(raw, "ssh_username")
|
||||||
|
_, warns, errs := NewConfig(raw)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// Good ssh_username
|
||||||
|
raw["ssh_username"] = "good"
|
||||||
|
_, warns, errs = NewConfig(raw)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigPrepare_sshCredential(t *testing.T) {
|
||||||
|
raw := testConfig()
|
||||||
|
|
||||||
|
// no ssh_password and no ssh_private_key_file
|
||||||
|
delete(raw, "ssh_password")
|
||||||
|
delete(raw, "ssh_private_key_file")
|
||||||
|
_, warns, errs := NewConfig(raw)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
|
||||||
|
// only ssh_password
|
||||||
|
raw["ssh_password"] = "good"
|
||||||
|
_, warns, errs = NewConfig(raw)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
|
||||||
|
// only ssh_private_key_file
|
||||||
|
raw["ssh_private_key_file"] = "good"
|
||||||
|
delete(raw, "ssh_password")
|
||||||
|
_, warns, errs = NewConfig(raw)
|
||||||
|
testConfigOk(t, warns, errs)
|
||||||
|
|
||||||
|
// both ssh_password and ssh_private_key_file set
|
||||||
|
raw["ssh_password"] = "bad"
|
||||||
|
_, warns, errs = NewConfig(raw)
|
||||||
|
testConfigErr(t, warns, errs)
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package null
|
||||||
|
|
||||||
|
import (
|
||||||
|
gossh "code.google.com/p/go.crypto/ssh"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/communicator/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SSHAddress returns a function that can be given to the SSH communicator
|
||||||
|
// for determining the SSH address
|
||||||
|
func SSHAddress(host string, port int) func(multistep.StateBag) (string, error) {
|
||||||
|
return func(state multistep.StateBag) (string, error) {
|
||||||
|
return fmt.Sprintf("%s:%d", host, port), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSHConfig returns a function that can be used for the SSH communicator
|
||||||
|
// config for connecting to the specified host via SSH
|
||||||
|
// private_key_file has precedence over password!
|
||||||
|
func SSHConfig(username string, password string, privateKeyFile string) func(multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||||
|
return func(state multistep.StateBag) (*gossh.ClientConfig, error) {
|
||||||
|
|
||||||
|
if privateKeyFile != "" {
|
||||||
|
// key based auth
|
||||||
|
|
||||||
|
bytes, err := ioutil.ReadFile(privateKeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
privateKey := string(bytes)
|
||||||
|
|
||||||
|
keyring := new(ssh.SimpleKeychain)
|
||||||
|
if err := keyring.AddPEMKey(privateKey); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error setting up SSH config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gossh.ClientConfig{
|
||||||
|
User: username,
|
||||||
|
Auth: []gossh.ClientAuth{
|
||||||
|
gossh.ClientAuthKeyring(keyring),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
// password based auth
|
||||||
|
|
||||||
|
return &gossh.ClientConfig{
|
||||||
|
User: username,
|
||||||
|
Auth: []gossh.ClientAuth{
|
||||||
|
gossh.ClientAuthPassword(ssh.Password(password)),
|
||||||
|
gossh.ClientAuthKeyboardInteractive(ssh.PasswordKeyboardInteractive(password)),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,7 +30,8 @@ const defaultConfig = `
|
||||||
"virtualbox-iso": "packer-builder-virtualbox-iso",
|
"virtualbox-iso": "packer-builder-virtualbox-iso",
|
||||||
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
"virtualbox-ovf": "packer-builder-virtualbox-ovf",
|
||||||
"vmware-iso": "packer-builder-vmware-iso",
|
"vmware-iso": "packer-builder-vmware-iso",
|
||||||
"vmware-vmx": "packer-builder-vmware-vmx"
|
"vmware-vmx": "packer-builder-vmware-vmx",
|
||||||
|
"null": "packer-builder-null"
|
||||||
},
|
},
|
||||||
|
|
||||||
"commands": {
|
"commands": {
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/packer/builder/null"
|
||||||
|
"github.com/mitchellh/packer/packer/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
server, err := plugin.Server()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
server.RegisterBuilder(new(null.Builder))
|
||||||
|
server.Serve()
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Null Builder
|
||||||
|
|
||||||
|
Type: `null`
|
||||||
|
|
||||||
|
The null builder is not really a builder, it just setups a SSH connection
|
||||||
|
and runs the provisioners. It can be used to debug provisioners without
|
||||||
|
incurring high wait times. It does not create any kind of image or artifact.
|
||||||
|
|
||||||
|
## Basic Example
|
||||||
|
|
||||||
|
Below is a fully functioning example. It doesn't do anything useful, since
|
||||||
|
no provisioners are defined, but it will connect to the specified host via ssh.
|
||||||
|
|
||||||
|
<pre class="prettyprint">
|
||||||
|
{
|
||||||
|
"type": "null",
|
||||||
|
"host": "127.0.0.1",
|
||||||
|
"username": "foo",
|
||||||
|
"password": "bar"
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
Configuration options are organized below into two categories: required and
|
||||||
|
optional. Within each category, the available options are alphabetized and
|
||||||
|
described.
|
||||||
|
|
||||||
|
Required:
|
||||||
|
|
||||||
|
* `host` (string) - The hostname or IP address to connect to.
|
||||||
|
|
||||||
|
* `ssh_password` (string) - The password to be used for the ssh connection.
|
||||||
|
Cannot be combined with ssh_private_key_file.
|
||||||
|
|
||||||
|
* `ssh_private_key_file` (string) - The filename of the ssh private key to be
|
||||||
|
used for the ssh connection. E.g. /home/user/.ssh/identity_rsa.
|
||||||
|
|
||||||
|
* `ssh_username` (string) - The username to be used for the ssh connection.
|
||||||
|
|
||||||
|
Optional:
|
||||||
|
|
||||||
|
* `port` (int) - port to connect to, defaults to 22.
|
||||||
|
|
Loading…
Reference in New Issue