Merge pull request #970 from fnoeding/nullbuilder
implemented null buider
This commit is contained in:
commit
a33aee13d5
|
@ -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-ovf": "packer-builder-virtualbox-ovf",
|
||||
"vmware-iso": "packer-builder-vmware-iso",
|
||||
"vmware-vmx": "packer-builder-vmware-vmx"
|
||||
"vmware-vmx": "packer-builder-vmware-vmx",
|
||||
"null": "packer-builder-null"
|
||||
},
|
||||
|
||||
"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