diff --git a/builder/triton/access_config.go b/builder/triton/access_config.go index 229f63ec9..4cb8e04cd 100644 --- a/builder/triton/access_config.go +++ b/builder/triton/access_config.go @@ -1,16 +1,15 @@ package triton import ( + "errors" "fmt" "io/ioutil" - "log" "os" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/template/interpolate" - "github.com/joyent/gocommon/client" - "github.com/joyent/gosdc/cloudapi" - "github.com/joyent/gosign/auth" + "github.com/joyent/triton-go" + "github.com/joyent/triton-go/authentication" ) // AccessConfig is for common configuration related to Triton access @@ -19,29 +18,40 @@ type AccessConfig struct { Account string `mapstructure:"triton_account"` KeyID string `mapstructure:"triton_key_id"` KeyMaterial string `mapstructure:"triton_key_material"` + + signer authentication.Signer } -// Prepare performs basic validation on the AccessConfig +// Prepare performs basic validation on the AccessConfig and ensures we can sign +// a request. func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { var errs []error if c.Endpoint == "" { - // Use Joyent public cloud as the default endpoint if none is in environment + // Use Joyent public cloud as the default endpoint if none is specified c.Endpoint = "https://us-east-1.api.joyent.com" } if c.Account == "" { - errs = append(errs, fmt.Errorf("triton_account is required to use the triton builder")) + errs = append(errs, errors.New("triton_account is required to use the triton builder")) } if c.KeyID == "" { - errs = append(errs, fmt.Errorf("triton_key_id is required to use the triton builder")) + errs = append(errs, errors.New("triton_key_id is required to use the triton builder")) } - var err error - c.KeyMaterial, err = processKeyMaterial(c.KeyMaterial) - if c.KeyMaterial == "" || err != nil { - errs = append(errs, fmt.Errorf("valid triton_key_material is required to use the triton builder")) + if c.KeyMaterial == "" { + signer, err := c.createSSHAgentSigner() + if err != nil { + errs = append(errs, err) + } + c.signer = signer + } else { + signer, err := c.createPrivateKeySigner() + if err != nil { + errs = append(errs, err) + } + c.signer = signer } if len(errs) > 0 { @@ -51,49 +61,55 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { return nil } -// CreateTritonClient returns an SDC client configured with the appropriate client credentials -// or an error if creating the client fails. -func (c *AccessConfig) CreateTritonClient() (*cloudapi.Client, error) { - keyData, err := processKeyMaterial(c.KeyMaterial) +func (c *AccessConfig) createSSHAgentSigner() (authentication.Signer, error) { + signer, err := authentication.NewSSHAgentSigner(c.KeyID, c.Account) if err != nil { - return nil, err + return nil, fmt.Errorf("Error creating Triton request signer: %s", err) } - userauth, err := auth.NewAuth(c.Account, keyData, "rsa-sha256") + // Ensure we can sign a request + _, err = signer.Sign("Wed, 26 Apr 2017 16:01:11 UTC") if err != nil { - return nil, err + return nil, fmt.Errorf("Error signing test request: %s", err) } - creds := &auth.Credentials{ - UserAuthentication: userauth, - SdcKeyId: c.KeyID, - SdcEndpoint: auth.Endpoint{URL: c.Endpoint}, - } - - return cloudapi.New(client.NewClient( - c.Endpoint, - cloudapi.DefaultAPIVersion, - creds, - log.New(os.Stdout, "", log.Flags()), - )), nil + return signer, nil } -func (c *AccessConfig) Comm() communicator.Config { - return communicator.Config{} -} +func (c *AccessConfig) createPrivateKeySigner() (authentication.Signer, error) { + var privateKeyMaterial []byte + var err error -func processKeyMaterial(keyMaterial string) (string, error) { // Check for keyMaterial being a file path - if _, err := os.Stat(keyMaterial); err != nil { - // Not a valid file. Assume that keyMaterial is the key data - return keyMaterial, nil + if _, err = os.Stat(c.KeyMaterial); err != nil { + privateKeyMaterial = []byte(c.KeyMaterial) + } else { + privateKeyMaterial, err = ioutil.ReadFile(c.KeyMaterial) + if err != nil { + return nil, fmt.Errorf("Error reading key material from path '%s': %s", + c.KeyMaterial, err) + } } - b, err := ioutil.ReadFile(keyMaterial) + // Create signer + signer, err := authentication.NewPrivateKeySigner(c.KeyID, privateKeyMaterial, c.Account) if err != nil { - return "", fmt.Errorf("Error reading key_material from path '%s': %s", - keyMaterial, err) + return nil, fmt.Errorf("Error creating Triton request signer: %s", err) } - return string(b), nil + // Ensure we can sign a request + _, err = signer.Sign("Wed, 26 Apr 2017 16:01:11 UTC") + if err != nil { + return nil, fmt.Errorf("Error signing test request: %s", err) + } + + return signer, nil +} + +func (c *AccessConfig) CreateTritonClient() (*triton.Client, error) { + return triton.NewClient(c.Endpoint, c.Account, c.signer) +} + +func (c *AccessConfig) Comm() communicator.Config { + return communicator.Config{} } diff --git a/builder/triton/builder.go b/builder/triton/builder.go index dd9ba30b9..3e572e0a4 100644 --- a/builder/triton/builder.go +++ b/builder/triton/builder.go @@ -36,6 +36,12 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = multierror.Append(errs, b.config.Comm.Prepare(&b.config.ctx)...) errs = multierror.Append(errs, b.config.TargetImageConfig.Prepare(&b.config.ctx)...) + // If we are using an SSH agent to sign requests, and no private key has been + // specified for SSH, use the agent for connecting for provisioning. + if b.config.AccessConfig.KeyMaterial == "" && b.config.Comm.SSHPrivateKey == "" { + b.config.Comm.SSHAgentAuth = true + } + return nil, errs.ErrorOrNil() } diff --git a/builder/triton/driver.go b/builder/triton/driver.go index 1cd30b90b..2afa11d3c 100644 --- a/builder/triton/driver.go +++ b/builder/triton/driver.go @@ -9,7 +9,7 @@ type Driver interface { CreateMachine(config Config) (string, error) DeleteImage(imageId string) error DeleteMachine(machineId string) error - GetMachine(machineId string) (string, error) + GetMachineIP(machineId string) (string, error) StopMachine(machineId string) error WaitForImageCreation(imageId string, timeout time.Duration) error WaitForMachineDeletion(machineId string, timeout time.Duration) error diff --git a/builder/triton/driver_mock.go b/builder/triton/driver_mock.go index 33bc618a6..831af8ada 100644 --- a/builder/triton/driver_mock.go +++ b/builder/triton/driver_mock.go @@ -69,7 +69,7 @@ func (d *DriverMock) DeleteMachine(machineId string) error { return nil } -func (d *DriverMock) GetMachine(machineId string) (string, error) { +func (d *DriverMock) GetMachineIP(machineId string) (string, error) { if d.GetMachineErr != nil { return "", d.GetMachineErr } diff --git a/builder/triton/driver_triton.go b/builder/triton/driver_triton.go index cef18bab8..caf39c981 100644 --- a/builder/triton/driver_triton.go +++ b/builder/triton/driver_triton.go @@ -2,15 +2,14 @@ package triton import ( "errors" - "strings" "time" "github.com/hashicorp/packer/packer" - "github.com/joyent/gosdc/cloudapi" + "github.com/joyent/triton-go" ) type driverTriton struct { - client *cloudapi.Client + client *triton.Client ui packer.Ui } @@ -27,30 +26,27 @@ func NewDriverTriton(ui packer.Ui, config Config) (Driver, error) { } func (d *driverTriton) CreateImageFromMachine(machineId string, config Config) (string, error) { - opts := cloudapi.CreateImageFromMachineOpts{ - Machine: machineId, + image, err := d.client.Images().CreateImageFromMachine(&triton.CreateImageFromMachineInput{ + MachineID: machineId, Name: config.ImageName, Version: config.ImageVersion, Description: config.ImageDescription, - Homepage: config.ImageHomepage, + HomePage: config.ImageHomepage, EULA: config.ImageEULA, ACL: config.ImageACL, Tags: config.ImageTags, - } - - image, err := d.client.CreateImageFromMachine(opts) + }) if err != nil { return "", err } - return image.Id, err + return image.ID, err } func (d *driverTriton) CreateMachine(config Config) (string, error) { - opts := cloudapi.CreateMachineOpts{ + input := &triton.CreateMachineInput{ Package: config.MachinePackage, Image: config.MachineImage, - Networks: config.MachineNetworks, Metadata: config.MachineMetadata, Tags: config.MachineTags, FirewallEnabled: config.MachineFirewallEnabled, @@ -59,29 +55,39 @@ func (d *driverTriton) CreateMachine(config Config) (string, error) { if config.MachineName == "" { // If not supplied generate a name for the source VM: "packer-builder-[image_name]". // The version is not used because it can contain characters invalid for a VM name. - opts.Name = "packer-builder-" + config.ImageName + input.Name = "packer-builder-" + config.ImageName } else { - opts.Name = config.MachineName + input.Name = config.MachineName + } + + if len(config.MachineNetworks) > 0 { + input.Networks = config.MachineNetworks } - machine, err := d.client.CreateMachine(opts) + machine, err := d.client.Machines().CreateMachine(input) if err != nil { return "", err } - return machine.Id, nil + return machine.ID, nil } func (d *driverTriton) DeleteImage(imageId string) error { - return d.client.DeleteImage(imageId) + return d.client.Images().DeleteImage(&triton.DeleteImageInput{ + ImageID: imageId, + }) } func (d *driverTriton) DeleteMachine(machineId string) error { - return d.client.DeleteMachine(machineId) + return d.client.Machines().DeleteMachine(&triton.DeleteMachineInput{ + ID: machineId, + }) } -func (d *driverTriton) GetMachine(machineId string) (string, error) { - machine, err := d.client.GetMachine(machineId) +func (d *driverTriton) GetMachineIP(machineId string) (string, error) { + machine, err := d.client.Machines().GetMachine(&triton.GetMachineInput{ + ID: machineId, + }) if err != nil { return "", err } @@ -90,7 +96,9 @@ func (d *driverTriton) GetMachine(machineId string) (string, error) { } func (d *driverTriton) StopMachine(machineId string) error { - return d.client.StopMachine(machineId) + return d.client.Machines().StopMachine(&triton.StopMachineInput{ + MachineID: machineId, + }) } // waitForMachineState uses the supplied client to wait for the state of @@ -101,7 +109,9 @@ func (d *driverTriton) StopMachine(machineId string) error { func (d *driverTriton) WaitForMachineState(machineId string, state string, timeout time.Duration) error { return waitFor( func() (bool, error) { - machine, err := d.client.GetMachine(machineId) + machine, err := d.client.Machines().GetMachine(&triton.GetMachineInput{ + ID: machineId, + }) if machine == nil { return false, err } @@ -118,16 +128,15 @@ func (d *driverTriton) WaitForMachineState(machineId string, state string, timeo func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Duration) error { return waitFor( func() (bool, error) { - machine, err := d.client.GetMachine(machineId) - if err != nil { - //TODO(jen20): is there a better way here than searching strings? - if strings.Contains(err.Error(), "410") || strings.Contains(err.Error(), "404") { - return true, nil - } + machine, err := d.client.Machines().GetMachine(&triton.GetMachineInput{ + ID: machineId, + }) + if err != nil && triton.IsResourceNotFound(err) { + return true, nil } if machine != nil { - return false, nil + return machine.State == "deleted", nil } return false, err @@ -140,7 +149,9 @@ func (d *driverTriton) WaitForMachineDeletion(machineId string, timeout time.Dur func (d *driverTriton) WaitForImageCreation(imageId string, timeout time.Duration) error { return waitFor( func() (bool, error) { - image, err := d.client.GetImage(imageId) + image, err := d.client.Images().GetImage(&triton.GetImageInput{ + ImageID: imageId, + }) if image == nil { return false, err } diff --git a/builder/triton/ssh.go b/builder/triton/ssh.go index 496379318..9b5f20732 100644 --- a/builder/triton/ssh.go +++ b/builder/triton/ssh.go @@ -17,7 +17,7 @@ func commHost(state multistep.StateBag) (string, error) { driver := state.Get("driver").(Driver) machineID := state.Get("machine").(string) - machine, err := driver.GetMachine(machineID) + machine, err := driver.GetMachineIP(machineID) if err != nil { return "", err }