diff --git a/builder/digitalocean/api.go b/builder/digitalocean/api.go index ea4e3b03b..85c3c63a9 100644 --- a/builder/digitalocean/api.go +++ b/builder/digitalocean/api.go @@ -9,7 +9,9 @@ import ( "errors" "fmt" "io/ioutil" + "log" "net/http" + "net/url" ) const DIGITALOCEAN_API_URL = "https://api.digitalocean.com" @@ -39,7 +41,10 @@ func (d DigitalOceanClient) New(client string, key string) *DigitalOceanClient { // Creates an SSH Key and returns it's id func (d DigitalOceanClient) CreateKey(name string, pub string) (uint, error) { - params := fmt.Sprintf("?name=%s&ssh_pub_key=%s", name, pub) + // Escape the public key + pub = url.QueryEscape(pub) + + params := fmt.Sprintf("name=%v&ssh_pub_key=%v", name, pub) body, err := NewRequest(d, "ssh_keys/new", params) if err != nil { @@ -54,7 +59,7 @@ func (d DigitalOceanClient) CreateKey(name string, pub string) (uint, error) { // Destroys an SSH key func (d DigitalOceanClient) DestroyKey(id uint) error { - path := fmt.Sprintf("ssh_keys/%s/destroy", id) + path := fmt.Sprintf("ssh_keys/%v/destroy", id) _, err := NewRequest(d, path, "") return err } @@ -62,8 +67,8 @@ func (d DigitalOceanClient) DestroyKey(id uint) error { // Creates a droplet and returns it's id func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, region uint, keyId uint) (uint, error) { params := fmt.Sprintf( - "name=%s&size_id=%s&image_id=%s&size_id=%s&image_id=%s®ion_id=%s&ssh_key_ids=%s", - name, size, image, size, region, keyId) + "name=%v&image_id=%v&size_id=%v®ion_id=%v&ssh_key_ids=%v", + name, image, size, region, keyId) body, err := NewRequest(d, "droplets/new", params) if err != nil { @@ -78,14 +83,14 @@ func (d DigitalOceanClient) CreateDroplet(name string, size uint, image uint, re // Destroys a droplet func (d DigitalOceanClient) DestroyDroplet(id uint) error { - path := fmt.Sprintf("droplets/%s/destroy", id) + path := fmt.Sprintf("droplets/%v/destroy", id) _, err := NewRequest(d, path, "") return err } // Powers off a droplet func (d DigitalOceanClient) PowerOffDroplet(id uint) error { - path := fmt.Sprintf("droplets/%s/power_off", id) + path := fmt.Sprintf("droplets/%v/power_off", id) _, err := NewRequest(d, path, "") @@ -94,8 +99,8 @@ func (d DigitalOceanClient) PowerOffDroplet(id uint) error { // Creates a snaphot of a droplet by it's ID func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error { - path := fmt.Sprintf("droplets/%s/snapshot", id) - params := fmt.Sprintf("name=%s", name) + path := fmt.Sprintf("droplets/%v/snapshot", id) + params := fmt.Sprintf("name=%v", name) _, err := NewRequest(d, path, params) @@ -104,17 +109,22 @@ func (d DigitalOceanClient) CreateSnapshot(id uint, name string) error { // Returns DO's string representation of status "off" "new" "active" etc. func (d DigitalOceanClient) DropletStatus(id uint) (string, string, error) { - path := fmt.Sprintf("droplets/%s", id) + path := fmt.Sprintf("droplets/%v", id) body, err := NewRequest(d, path, "") if err != nil { return "", "", err } + var ip string + // Read the droplet's "status" droplet := body["droplet"].(map[string]interface{}) status := droplet["status"].(string) - ip := droplet["ip_address"].(string) + + if droplet["ip_address"] != nil { + ip = droplet["ip_address"].(string) + } return ip, status, err } @@ -123,11 +133,13 @@ func (d DigitalOceanClient) DropletStatus(id uint) (string, string, error) { // the response. func NewRequest(d DigitalOceanClient, path string, params string) (map[string]interface{}, error) { client := d.client - url := fmt.Sprintf("%s/%s?%s&client_id=%s&api_key=%s", + url := fmt.Sprintf("%v/%v?%v&client_id=%v&api_key=%v", DIGITALOCEAN_API_URL, path, params, d.ClientID, d.APIKey) var decodedResponse map[string]interface{} + log.Printf("sending new request to digitalocean: %v", url) + resp, err := client.Get(url) if err != nil { return decodedResponse, err @@ -140,13 +152,16 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in return decodedResponse, err } + err = json.Unmarshal(body, &decodedResponse) + // Catch all non-200 status and return an error if resp.StatusCode != 200 { err = errors.New(fmt.Sprintf("recieved non-200 status from digitalocean: %d", resp.StatusCode)) + log.Printf("response from digital ocean: %v", decodedResponse) return decodedResponse, err } - err = json.Unmarshal(body, &decodedResponse) + log.Printf("response from digital ocean: %v", decodedResponse) if err != nil { return decodedResponse, err @@ -156,6 +171,7 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in status := decodedResponse["status"] if status != "OK" { err = errors.New(fmt.Sprintf("recieved non-OK status from digitalocean: %d", status)) + log.Printf("response from digital ocean: %v", decodedResponse) return decodedResponse, err } diff --git a/builder/digitalocean/builder.go b/builder/digitalocean/builder.go index 55c55c7e0..49dfe2422 100644 --- a/builder/digitalocean/builder.go +++ b/builder/digitalocean/builder.go @@ -60,8 +60,8 @@ func (b *Builder) Prepare(raw interface{}) error { } if b.config.ImageID == 0 { - // Default to base image "Ubuntu 12.04 x64 Server" - b.config.ImageID = 2676 + // Default to base image "Ubuntu 12.04 x64 Server (id: 284203)" + b.config.ImageID = 284203 } if b.config.SSHUsername == "" { @@ -131,8 +131,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe new(stepProvision), new(stepPowerOff), new(stepSnapshot), - new(stepDestroyDroplet), - new(stepDestroySSHKey), } // Run the steps diff --git a/builder/digitalocean/step_create_droplet.go b/builder/digitalocean/step_create_droplet.go index feef83566..36a745de5 100644 --- a/builder/digitalocean/step_create_droplet.go +++ b/builder/digitalocean/step_create_droplet.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "time" ) type stepCreateDroplet struct { @@ -51,10 +52,16 @@ func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) { // Destroy the droplet we just created ui.Say("Destroying droplet...") + + // Sleep arbitrarily before sending destroy request + // Otherwise we get "pending event" errors, even though there isn't + // one. + time.Sleep(5 * time.Second) + err := client.DestroyDroplet(s.dropletId) if err != nil { ui.Error(fmt.Sprintf( - "Error destroying droplet. Please destroy it manually: %s", s.dropletId)) + "Error destroying droplet. Please destroy it manually: %v", s.dropletId)) } } diff --git a/builder/digitalocean/step_create_ssh_key.go b/builder/digitalocean/step_create_ssh_key.go index 7ff810538..1be83d706 100644 --- a/builder/digitalocean/step_create_ssh_key.go +++ b/builder/digitalocean/step_create_ssh_key.go @@ -2,11 +2,7 @@ package digitalocean import ( "cgl.tideland.biz/identifier" - "crypto/rand" - "crypto/rsa" - "crypto/x509" "encoding/hex" - "encoding/pem" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -22,37 +18,80 @@ func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepActio ui := state["ui"].(packer.Ui) ui.Say("Creating temporary ssh key for droplet...") - priv, err := rsa.GenerateKey(rand.Reader, 2014) - if err != nil { - ui.Error(err.Error()) - return multistep.ActionHalt - } + // priv, err := rsa.GenerateKey(rand.Reader, 2014) + // if err != nil { + // ui.Error(err.Error()) + // return multistep.ActionHalt + // } + + // priv_der := x509.MarshalPKCS1PrivateKey(priv) + // priv_blk := pem.Block{ + // Type: "RSA PRIVATE KEY", + // Headers: nil, + // Bytes: priv_der, + // } // Set the pem formatted private key on the state for later - priv_der := x509.MarshalPKCS1PrivateKey(priv) - priv_blk := pem.Block{ - Type: "RSA PRIVATE KEY", - Headers: nil, - Bytes: priv_der, - } + // state["privateKey"] = string(pem.EncodeToMemory(&priv_blk)) + // log.Printf("PRIVATE KEY:\n\n%v\n\n", state["privateKey"]) // Create the public key for uploading to DO - pub := priv.PublicKey - pub_der, err := x509.MarshalPKIXPublicKey(&pub) - if err != nil { - ui.Error(err.Error()) - return multistep.ActionHalt - } - pub_blk := pem.Block{ - Type: "PUBLIC KEY", - Headers: nil, - Bytes: pub_der, - } - pub_pem := string(pem.EncodeToMemory(&pub_blk)) + // pub := priv.PublicKey - name := fmt.Sprintf("packer %s", hex.EncodeToString(identifier.NewUUID().Raw())) + // pub_bytes, err := x509.MarshalPKIXPublicKey(&pub) - keyId, err := client.CreateKey(name, pub_pem) + // pub_blk := pem.Block{ + // Type: "RSA PUBLIC KEY", + // Headers: nil, + // Bytes: pub_bytes, + // } + + // if err != nil { + // ui.Error(err.Error()) + // return multistep.ActionHalt + // } + + // // Encode the public key to base64 + // pub_str := base64.StdEncoding.EncodeToString(pub_bytes) + // pub_str = "ssh-rsa " + pub_str + + // log.Printf("PUBLIC KEY:\n\n%v\n\n", string(pem.EncodeToMemory(&pub_blk))) + // log.Printf("PUBLIC KEY BASE64:\n\n%v\n\n", pub_str) + + pub_str := `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD29LZNMe0f7nOmdOIXDrF6eAmLZEk1yrnnsPI+xjLsnKxggMjdD3HvkBPXMdhakOj3pEF6DNtXbK43A7Pilezvu7y2awz+dxCavgUNtwaJkiTJw3C2qleNDDgrq7ZYLJ/wKmfhgPO4jZBej/8ONA0VjxemCNBPTTBeZ8FaeOpeUqopdhk78KGeGmUJ8Bvl8ACuYNdtJ5Y0BQCZkJT+g1ntTwHvuq/Vy/E2uCwJ2xV3vCDkLlqXVyksuVIcLJxTPtd5LdasD4WMQwoOPNdNMBLBG6ZBhXC/6kCVbMgzy5poSZ7r6BK0EA6b2EdAanaojYs3i52j6JeCIIrYtu9Ub173 jack@jose.local` + state["privateKey"] = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA9vS2TTHtH+5zpnTiFw6xengJi2RJNcq557DyPsYy7JysYIDI +3Q9x75AT1zHYWpDo96RBegzbV2yuNwOz4pXs77u8tmsM/ncQmr4FDbcGiZIkycNw +tqpXjQw4K6u2WCyf8Cpn4YDzuI2QXo//DjQNFY8XpgjQT00wXmfBWnjqXlKqKXYZ +O/ChnhplCfAb5fAArmDXbSeWNAUAmZCU/oNZ7U8B77qv1cvxNrgsCdsVd7wg5C5a +l1cpLLlSHCycUz7XeS3WrA+FjEMKDjzXTTASwRumQYVwv+pAlWzIM8uaaEme6+gS +tBAOm9hHQGp2qI2LN4udo+iXgiCK2LbvVG9e9wIDAQABAoIBABuBB6izTciHoyO/ +0spknYmZQt7ebXTrPic6wtAQ/OzzShN5ZGWSacsXjc4ixAjaKMgj6BLyyZ8EAKcp +52ft8LSGgS8D3y+cDSJe1WtAnh7GQwihlrURZazU1pCukCFj3vA9mNI5rWs5gQG3 +Id3wGCD1jdm1E5Yxb5ikD5nG67tTW5Pn4+tidsavTNsDLsks/pW/0EcPcKAS+TJ8 +Zy15MsGGfHVVkxf+ldULIxxidAeplQhWuED6wkbuD3LQi6Kt4yElHS+UCATca8Fe +CvXNcQWrEHiYUvpyrvU3ybw7WEUUWFa/dctSZwmHvkvRD/bwJPf5M8sIIl8zlyuy +3YCIlSkCgYEA/ZqGOnYIK/bA/QVuyFkFkP3aJjOKJtH0RV9V5XVKSBlU1/Lm3DUZ +XVmp7JuWZHVhPxZa8tswj4x15dX+TwTvGdoUuqPC7K/UMOt6Qzk11o0+o2VRYU97 +GzYyEDxGEnRqoZsc1922I6nBv8YqsW4WkMRhkFN4JNzLJBVXMTXcDCMCgYEA+Uob +VQfVF+7BfCOCNdSu9dqZoYRCyBm5JNEp5bqF1kiEbGw4FhJYp95Ix5ogD3Ug4aqe +8ylwUK86U2BhfkKmGQ5yf+6VNoTx3EPFaGrODIi82BUraYPyYEN10ZrR8Czy5X9g +1WC+WuboRgvTZs+grwnDVJwqQIOqIB2L0p+SdR0CgYEAokHavc7E/bP72CdAsSjb ++d+hUq3JJ3tPiY8suwnnQ+gJM72y3ZOPrf1vTfZiK9Y6KQ4ZlKaPFFkvGaVn95DV +ljnE54FddugsoDwZVqdk/egS+qIZhmQ/BLMRJvgZcTdQ/iLrOmYdYgX788JLkIg6 +Ide0AI6XISavRl/tEIxARPcCgYEAlgh+6K8dFhlRA7iPPnyxjDAzdF0YoDuzDTCB +icy3jh747BQ5sTb7epSyssbU8tiooIjCv1A6U6UScmm4Y3gTZVMnoE1kKnra4Zk8 +LzrQpgSJu3cKOKf78OnI+Ay4u1ciHPOLwQBHsIf2VWn6oo7lg1NZ5wtR9qAHfOqr +Y2k8iRUCgYBKQCtY4SNDuFb6+r5YSEFVfelCn6DJzNgTxO2mkUzzM7RcgejHbd+i +oqgnYXsFLJgm+NpN1eFpbs2RgAe8Zd4pKQNwJFJf0EbEP57sW3kujgFFEsPYJPOp +n8wFU32yrKgrVCftmCk1iI+WPfr1r9LKgKhb0sRX1+DsdWqfN6J7Sw== +-----END RSA PRIVATE KEY-----` + + // The name of the public key on DO + name := fmt.Sprintf("packer-%s", hex.EncodeToString(identifier.NewUUID().Raw())) + + // Create the key! + keyId, err := client.CreateKey(name, pub_str) if err != nil { ui.Error(err.Error()) @@ -66,7 +105,6 @@ func (s *stepCreateSSHKey) Run(state map[string]interface{}) multistep.StepActio // Remember some state for the future state["ssh_key_id"] = keyId - state["privateKey"] = string(pem.EncodeToMemory(&priv_blk)) return multistep.ActionContinue } @@ -83,7 +121,8 @@ func (s *stepCreateSSHKey) Cleanup(state map[string]interface{}) { ui.Say("Deleting temporary ssh key...") err := client.DestroyKey(s.keyId) if err != nil { + log.Printf("Error cleaning up ssh key: %v", err.Error()) ui.Error(fmt.Sprintf( - "Error cleaning up ssh key. Please delete the key manually: %s", s.keyId)) + "Error cleaning up ssh key. Please delete the key manually: %v", s.keyId)) } } diff --git a/builder/digitalocean/step_destroy_droplet.go b/builder/digitalocean/step_destroy_droplet.go deleted file mode 100644 index 65af363c0..000000000 --- a/builder/digitalocean/step_destroy_droplet.go +++ /dev/null @@ -1,29 +0,0 @@ -package digitalocean - -import ( - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -type stepDestroyDroplet struct{} - -func (s *stepDestroyDroplet) Run(state map[string]interface{}) multistep.StepAction { - client := state["client"].(*DigitalOceanClient) - ui := state["ui"].(packer.Ui) - dropletId := state["droplet_id"].(uint) - - ui.Say("Destroying droplet...") - - err := client.DestroyDroplet(dropletId) - - if err != nil { - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -func (s *stepDestroyDroplet) Cleanup(state map[string]interface{}) { - // no cleanup -} diff --git a/builder/digitalocean/step_destroy_ssh_key.go b/builder/digitalocean/step_destroy_ssh_key.go deleted file mode 100644 index c6fe675e1..000000000 --- a/builder/digitalocean/step_destroy_ssh_key.go +++ /dev/null @@ -1,29 +0,0 @@ -package digitalocean - -import ( - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" -) - -type stepDestroySSHKey struct{} - -func (s *stepDestroySSHKey) Run(state map[string]interface{}) multistep.StepAction { - client := state["client"].(*DigitalOceanClient) - ui := state["ui"].(packer.Ui) - sshKeyId := state["ssh_key_id"].(uint) - - ui.Say("Destroying temporary ssh key...") - - err := client.DestroyKey(sshKeyId) - - if err != nil { - ui.Error(err.Error()) - return multistep.ActionHalt - } - - return multistep.ActionContinue -} - -func (s *stepDestroySSHKey) Cleanup(state map[string]interface{}) { - // no cleanup -} diff --git a/builder/digitalocean/step_power_off.go b/builder/digitalocean/step_power_off.go index 3cd1220fc..b487f7779 100644 --- a/builder/digitalocean/step_power_off.go +++ b/builder/digitalocean/step_power_off.go @@ -3,6 +3,7 @@ package digitalocean import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "time" ) type stepPowerOff struct{} @@ -12,6 +13,11 @@ func (s *stepPowerOff) Run(state map[string]interface{}) multistep.StepAction { ui := state["ui"].(packer.Ui) dropletId := state["droplet_id"].(uint) + // Sleep arbitrarily before sending power off request + // Otherwise we get "pending event" errors, even though there isn't + // one. + time.Sleep(3 * time.Second) + // Poweroff the droplet so it can be snapshot err := client.PowerOffDroplet(dropletId) diff --git a/builder/digitalocean/wait.go b/builder/digitalocean/wait.go index b0794d2c2..c712c16ad 100644 --- a/builder/digitalocean/wait.go +++ b/builder/digitalocean/wait.go @@ -14,10 +14,6 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce go func() { attempts := 0 for { - select { - default: - } - attempts += 1 log.Printf("Checking droplet status... (attempt: %d)", attempts) @@ -33,8 +29,8 @@ func waitForDropletState(desiredState string, dropletId uint, client *DigitalOce break } - // Wait a second in between - time.Sleep(1 * time.Second) + // Wait 3 seconds in between + time.Sleep(3 * time.Second) } active <- true @@ -53,9 +49,6 @@ ActiveWaitLoop: case <-timeout: err := errors.New("Timeout while waiting to for droplet to become active") return err - case <-time.After(1 * time.Second): - err := errors.New("Interrupt detected, quitting waiting for droplet") - return err } }