mirror of https://github.com/hashicorp/packer
parent
4e6993909c
commit
dd6e4e4933
@ -0,0 +1,118 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
gossh "code.google.com/p/go.crypto/ssh"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/communicator/ssh"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepConnectSSH struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepConnectSSH) Run(state map[string]interface{}) multistep.StepAction {
|
||||||
|
config := state["config"].(config)
|
||||||
|
client := state["client"].(*DigitalOceanClient)
|
||||||
|
privateKey := state["privateKey"].(string)
|
||||||
|
ui := state["ui"].(packer.Ui)
|
||||||
|
ipAddress := state["droplet_ip"]
|
||||||
|
|
||||||
|
// Build the keyring for authentication. This stores the private key
|
||||||
|
// we'll use to authenticate.
|
||||||
|
keyring := &ssh.SimpleKeychain{}
|
||||||
|
err := keyring.AddPEMKey(privateKey)
|
||||||
|
if err != nil {
|
||||||
|
ui.Say(fmt.Sprintf("Error setting up SSH config: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the actual SSH client configuration
|
||||||
|
sshConfig := &gossh.ClientConfig{
|
||||||
|
User: config.SSHUsername,
|
||||||
|
Auth: []gossh.ClientAuth{
|
||||||
|
gossh.ClientAuthKeyring(keyring),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start trying to connect to SSH
|
||||||
|
connected := make(chan bool, 1)
|
||||||
|
connectQuit := make(chan bool, 1)
|
||||||
|
defer func() {
|
||||||
|
connectQuit <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ui.Say("Connecting to the droplet via SSH...")
|
||||||
|
attempts := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-connectQuit:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
log.Printf(
|
||||||
|
"Opening TCP conn for SSH to %s:%d (attempt %d)",
|
||||||
|
ipAddress, config.SSHPort, attempts)
|
||||||
|
s.conn, err = net.Dial("tcp", fmt.Sprintf("%s:%d", ipAddress, config.SSHPort))
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// A brief sleep so we're not being overly zealous attempting
|
||||||
|
// to connect to the instance.
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
connected <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Printf("Waiting up to %s for SSH connection", config.SSHTimeout)
|
||||||
|
timeout := time.After(config.SSHTimeout)
|
||||||
|
|
||||||
|
ConnectWaitLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-connected:
|
||||||
|
// We connected. Just break the loop.
|
||||||
|
break ConnectWaitLoop
|
||||||
|
case <-timeout:
|
||||||
|
ui.Error("Timeout while waiting to connect to SSH.")
|
||||||
|
return multistep.ActionHalt
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
if _, ok := state[multistep.StateCancelled]; ok {
|
||||||
|
log.Println("Interrupt detected, quitting waiting for SSH.")
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var comm packer.Communicator
|
||||||
|
if err == nil {
|
||||||
|
comm, err = ssh.New(s.conn, sshConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf("Error connecting to SSH: %s", err))
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the communicator on the state bag so it can be used later
|
||||||
|
state["communicator"] = comm
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepConnectSSH) Cleanup(map[string]interface{}) {
|
||||||
|
if s.conn != nil {
|
||||||
|
s.conn.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cgl.tideland.biz/identifier"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepCreateDroplet struct {
|
||||||
|
dropletId uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateDroplet) Run(state map[string]interface{}) multistep.StepAction {
|
||||||
|
client := state["client"].(*DigitalOceanClient)
|
||||||
|
ui := state["ui"].(packer.Ui)
|
||||||
|
c := state["config"].(config)
|
||||||
|
sshKeyId := state["ssh_key_id"].(uint)
|
||||||
|
|
||||||
|
ui.Say("Creating droplet...")
|
||||||
|
|
||||||
|
// Some random droplet name as it's temporary
|
||||||
|
name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw()))
|
||||||
|
|
||||||
|
// Create the droplet based on configuration
|
||||||
|
dropletId, err := client.CreateDroplet(name, c.SizeID, c.ImageID, c.RegionID, sshKeyId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use this in cleanup
|
||||||
|
s.dropletId = dropletId
|
||||||
|
|
||||||
|
// Store the droplet id for later
|
||||||
|
state["droplet_id"] = dropletId
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
|
||||||
|
// If the dropletid isn't there, we probably never created it
|
||||||
|
if s.dropletId == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
client := state["client"].(*DigitalOceanClient)
|
||||||
|
ui := state["ui"].(packer.Ui)
|
||||||
|
|
||||||
|
// Destroy the droplet we just created
|
||||||
|
ui.Say("Destroying droplet...")
|
||||||
|
err := client.DestroyDroplet(s.dropletId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(fmt.Sprintf(
|
||||||
|
"Error destroying droplet. Please destroy it manually: %s", s.dropletId))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,85 @@
|
|||||||
|
package digitalocean
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mitchellh/multistep"
|
||||||
|
"github.com/mitchellh/packer/packer"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stepDropletInfo struct{}
|
||||||
|
|
||||||
|
func (s *stepDropletInfo) Run(state map[string]interface{}) multistep.StepAction {
|
||||||
|
client := state["client"].(*DigitalOceanClient)
|
||||||
|
ui := state["ui"].(packer.Ui)
|
||||||
|
c := state["config"].(config)
|
||||||
|
dropletId := state["droplet_id"].(uint)
|
||||||
|
|
||||||
|
ui.Say("Waiting for droplet to become active...")
|
||||||
|
|
||||||
|
// Wait for the droplet to become active
|
||||||
|
active := make(chan bool, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
attempts := 0
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
|
||||||
|
log.Printf("Checking droplet status... (attempt: %d)", attempts)
|
||||||
|
|
||||||
|
ip, status, err := client.DropletStatus(dropletId)
|
||||||
|
|
||||||
|
if status == "active" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a second in between
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
active <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
log.Printf("Waiting for up to 3 minutes for droplet to become active")
|
||||||
|
duration, _ := time.ParseDuration("3m")
|
||||||
|
timeout := time.After(duration)
|
||||||
|
|
||||||
|
ActiveWaitLoop:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-active:
|
||||||
|
// We connected. Just break the loop.
|
||||||
|
break ActiveWaitLoop
|
||||||
|
case <-timeout:
|
||||||
|
ui.Error("Timeout while waiting to for droplet to become active")
|
||||||
|
return multistep.ActionHalt
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
if _, ok := state[multistep.StateCancelled]; ok {
|
||||||
|
log.Println("Interrupt detected, quitting waiting droplet to become active")
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the IP on the state for later
|
||||||
|
ip, _, err := client.DropletStatus(dropletId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ui.Error(err.Error())
|
||||||
|
return multistep.ActionHalt
|
||||||
|
}
|
||||||
|
|
||||||
|
state["droplet_ip"] = ip
|
||||||
|
|
||||||
|
return multistep.ActionContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stepDropletInfo) Cleanup(state map[string]interface{}) {
|
||||||
|
// no cleanup
|
||||||
|
}
|
||||||
Loading…
Reference in new issue