Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
P
packer
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kristopher Ruzic
packer
Commits
7865026e
Commit
7865026e
authored
Oct 28, 2014
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1463 from vtolstov/digitalocean
add digitalocean v2 api support
parents
2c052fa8
10612b5d
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
926 additions
and
420 deletions
+926
-420
builder/digitalocean/api.go
builder/digitalocean/api.go
+43
-393
builder/digitalocean/api_v1.go
builder/digitalocean/api_v1.go
+382
-0
builder/digitalocean/api_v2.go
builder/digitalocean/api_v2.go
+448
-0
builder/digitalocean/artifact.go
builder/digitalocean/artifact.go
+1
-1
builder/digitalocean/builder.go
builder/digitalocean/builder.go
+24
-10
builder/digitalocean/step_create_droplet.go
builder/digitalocean/step_create_droplet.go
+2
-2
builder/digitalocean/step_create_ssh_key.go
builder/digitalocean/step_create_ssh_key.go
+3
-4
builder/digitalocean/step_droplet_info.go
builder/digitalocean/step_droplet_info.go
+2
-1
builder/digitalocean/step_power_off.go
builder/digitalocean/step_power_off.go
+3
-2
builder/digitalocean/step_shutdown.go
builder/digitalocean/step_shutdown.go
+4
-3
builder/digitalocean/step_snapshot.go
builder/digitalocean/step_snapshot.go
+3
-2
builder/digitalocean/wait.go
builder/digitalocean/wait.go
+1
-1
website/source/docs/builders/digitalocean.html.markdown
website/source/docs/builders/digitalocean.html.markdown
+10
-1
No files found.
builder/digitalocean/api.go
View file @
7865026e
...
...
@@ -4,36 +4,13 @@
package
digitalocean
import
(
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/mitchellh/mapstructure"
)
type
Image
struct
{
Id
uint
Name
string
Slug
string
Distribution
string
}
type
ImagesResp
struct
{
Images
[]
Image
}
type
Region
struct
{
Id
uint
Name
string
Slug
string
Id
uint
`json:"id,omitempty"`
//only in v1 api
Slug
string
`json:"slug"`
//presen in both api
Name
string
`json:"name"`
//presen in both api
Sizes
[]
string
`json:"sizes,omitempty"`
//only in v2 api
Available
bool
`json:"available,omitempty"`
//only in v2 api
Features
[]
string
`json:"features,omitempty"`
//only in v2 api
}
type
RegionsResp
struct
{
...
...
@@ -41,378 +18,51 @@ type RegionsResp struct {
}
type
Size
struct
{
Id
uint
Name
string
Slug
string
Id
uint
`json:"id,omitempty"`
//only in v1 api
Name
string
`json:"name,omitempty"`
//only in v1 api
Slug
string
`json:"slug"`
//presen in both api
Memory
uint
`json:"memory,omitempty"`
//only in v2 api
VCPUS
uint
`json:"vcpus,omitempty"`
//only in v2 api
Disk
uint
`json:"disk,omitempty"`
//only in v2 api
Transfer
uint
`json:"transfer,omitempty"`
//only in v2 api
PriceMonthly
float64
`json:"price_monthly,omitempty"`
//only in v2 api
PriceHourly
float64
`json:"price_hourly,omitempty"`
//only in v2 api
Regions
[]
string
`json:"regions,omitempty"`
//only in v2 api
}
type
SizesResp
struct
{
Sizes
[]
Size
}
type
DigitalOceanClient
struct
{
// The http client for communicating
client
*
http
.
Client
// Credentials
ClientID
string
APIKey
string
// The base URL of the API
APIURL
string
}
// Creates a new client for communicating with DO
func
(
d
DigitalOceanClient
)
New
(
client
string
,
key
string
,
url
string
)
*
DigitalOceanClient
{
c
:=
&
DigitalOceanClient
{
client
:
&
http
.
Client
{
Transport
:
&
http
.
Transport
{
Proxy
:
http
.
ProxyFromEnvironment
,
},
},
APIURL
:
url
,
ClientID
:
client
,
APIKey
:
key
,
}
return
c
}
// Creates an SSH Key and returns it's id
func
(
d
DigitalOceanClient
)
CreateKey
(
name
string
,
pub
string
)
(
uint
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
params
.
Set
(
"ssh_pub_key"
,
pub
)
body
,
err
:=
NewRequest
(
d
,
"ssh_keys/new"
,
params
)
if
err
!=
nil
{
return
0
,
err
}
// Read the SSH key's ID we just created
key
:=
body
[
"ssh_key"
]
.
(
map
[
string
]
interface
{})
keyId
:=
key
[
"id"
]
.
(
float64
)
return
uint
(
keyId
),
nil
}
// Destroys an SSH key
func
(
d
DigitalOceanClient
)
DestroyKey
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"ssh_keys/%v/destroy"
,
id
)
_
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
return
err
}
// Creates a droplet and returns it's id
func
(
d
DigitalOceanClient
)
CreateDroplet
(
name
string
,
size
string
,
image
string
,
region
string
,
keyId
uint
,
privateNetworking
bool
)
(
uint
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
found_size
,
err
:=
d
.
Size
(
size
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid size or lookup failure: '%s': %s"
,
size
,
err
)
}
found_image
,
err
:=
d
.
Image
(
image
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid image or lookup failure: '%s': %s"
,
image
,
err
)
}
found_region
,
err
:=
d
.
Region
(
region
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid region or lookup failure: '%s': %s"
,
region
,
err
)
}
params
.
Set
(
"size_slug"
,
found_size
.
Slug
)
params
.
Set
(
"image_slug"
,
found_image
.
Slug
)
params
.
Set
(
"region_slug"
,
found_region
.
Slug
)
params
.
Set
(
"ssh_key_ids"
,
fmt
.
Sprintf
(
"%v"
,
keyId
))
params
.
Set
(
"private_networking"
,
fmt
.
Sprintf
(
"%v"
,
privateNetworking
))
body
,
err
:=
NewRequest
(
d
,
"droplets/new"
,
params
)
if
err
!=
nil
{
return
0
,
err
}
// Read the Droplets ID
droplet
:=
body
[
"droplet"
]
.
(
map
[
string
]
interface
{})
dropletId
:=
droplet
[
"id"
]
.
(
float64
)
return
uint
(
dropletId
),
err
}
// Destroys a droplet
func
(
d
DigitalOceanClient
)
DestroyDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/destroy"
,
id
)
_
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
return
err
}
// Powers off a droplet
func
(
d
DigitalOceanClient
)
PowerOffDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/power_off"
,
id
)
_
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
return
err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func
(
d
DigitalOceanClient
)
ShutdownDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/shutdown"
,
id
)
_
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
return
err
}
// Creates a snaphot of a droplet by it's ID
func
(
d
DigitalOceanClient
)
CreateSnapshot
(
id
uint
,
name
string
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/snapshot"
,
id
)
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
_
,
err
:=
NewRequest
(
d
,
path
,
params
)
return
err
}
// Returns all available images.
func
(
d
DigitalOceanClient
)
Images
()
([]
Image
,
error
)
{
resp
,
err
:=
NewRequest
(
d
,
"images"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
ImagesResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Images
,
nil
}
// Destroys an image by its ID.
func
(
d
DigitalOceanClient
)
DestroyImage
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"images/%d/destroy"
,
id
)
_
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
return
err
}
// Returns DO's string representation of status "off" "new" "active" etc.
func
(
d
DigitalOceanClient
)
DropletStatus
(
id
uint
)
(
string
,
string
,
error
)
{
path
:=
fmt
.
Sprintf
(
"droplets/%v"
,
id
)
body
,
err
:=
NewRequest
(
d
,
path
,
url
.
Values
{})
if
err
!=
nil
{
return
""
,
""
,
err
}
var
ip
string
// Read the droplet's "status"
droplet
:=
body
[
"droplet"
]
.
(
map
[
string
]
interface
{})
status
:=
droplet
[
"status"
]
.
(
string
)
if
droplet
[
"ip_address"
]
!=
nil
{
ip
=
droplet
[
"ip_address"
]
.
(
string
)
}
return
ip
,
status
,
err
}
// Sends an api request and returns a generic map[string]interface of
// the response.
func
NewRequest
(
d
DigitalOceanClient
,
path
string
,
params
url
.
Values
)
(
map
[
string
]
interface
{},
error
)
{
client
:=
d
.
client
// Add the authentication parameters
params
.
Set
(
"client_id"
,
d
.
ClientID
)
params
.
Set
(
"api_key"
,
d
.
APIKey
)
url
:=
fmt
.
Sprintf
(
"%s/%s?%s"
,
d
.
APIURL
,
path
,
params
.
Encode
())
// Do some basic scrubbing so sensitive information doesn't appear in logs
scrubbedUrl
:=
strings
.
Replace
(
url
,
d
.
ClientID
,
"CLIENT_ID"
,
-
1
)
scrubbedUrl
=
strings
.
Replace
(
scrubbedUrl
,
d
.
APIKey
,
"API_KEY"
,
-
1
)
log
.
Printf
(
"sending new request to digitalocean: %s"
,
scrubbedUrl
)
var
lastErr
error
for
attempts
:=
1
;
attempts
<
10
;
attempts
++
{
resp
,
err
:=
client
.
Get
(
url
)
if
err
!=
nil
{
return
nil
,
err
}
body
,
err
:=
ioutil
.
ReadAll
(
resp
.
Body
)
resp
.
Body
.
Close
()
if
err
!=
nil
{
return
nil
,
err
}
log
.
Printf
(
"response from digitalocean: %s"
,
body
)
var
decodedResponse
map
[
string
]
interface
{}
err
=
json
.
Unmarshal
(
body
,
&
decodedResponse
)
if
err
!=
nil
{
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Failed to decode JSON response (HTTP %v) from DigitalOcean: %s"
,
resp
.
StatusCode
,
body
))
return
decodedResponse
,
err
}
// Check for errors sent by digitalocean
status
:=
decodedResponse
[
"status"
]
.
(
string
)
if
status
==
"OK"
{
return
decodedResponse
,
nil
}
if
status
==
"ERROR"
{
statusRaw
,
ok
:=
decodedResponse
[
"error_message"
]
if
ok
{
status
=
statusRaw
.
(
string
)
}
else
{
status
=
fmt
.
Sprintf
(
"Unknown error. Full response body: %s"
,
body
)
}
}
lastErr
=
errors
.
New
(
fmt
.
Sprintf
(
"Received error from DigitalOcean (%d): %s"
,
resp
.
StatusCode
,
status
))
log
.
Println
(
lastErr
)
if
strings
.
Contains
(
status
,
"a pending event"
)
{
// Retry, DigitalOcean sends these dumb "pending event"
// errors all the time.
time
.
Sleep
(
5
*
time
.
Second
)
continue
}
// Some other kind of error. Just return.
return
decodedResponse
,
lastErr
}
return
nil
,
lastErr
}
func
(
d
DigitalOceanClient
)
Image
(
slug_or_name_or_id
string
)
(
Image
,
error
)
{
images
,
err
:=
d
.
Images
()
if
err
!=
nil
{
return
Image
{},
err
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Slug
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Name
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
image
.
Id
==
uint
(
id
)
{
return
image
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown image '%v'"
,
slug_or_name_or_id
))
return
Image
{},
err
}
// Returns all available regions.
func
(
d
DigitalOceanClient
)
Regions
()
([]
Region
,
error
)
{
resp
,
err
:=
NewRequest
(
d
,
"regions"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
RegionsResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Regions
,
nil
}
func
(
d
DigitalOceanClient
)
Region
(
slug_or_name_or_id
string
)
(
Region
,
error
)
{
regions
,
err
:=
d
.
Regions
()
if
err
!=
nil
{
return
Region
{},
err
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Slug
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Name
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
region
.
Id
==
uint
(
id
)
{
return
region
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown region '%v'"
,
slug_or_name_or_id
))
return
Region
{},
err
type
Image
struct
{
Id
uint
`json:"id"`
//presen in both api
Name
string
`json:"name"`
//presen in both api
Slug
string
`json:"slug"`
//presen in both api
Distribution
string
`json:"distribution"`
//presen in both api
Public
bool
`json:"public,omitempty"`
//only in v2 api
Regions
[]
string
`json:"regions,omitempty"`
//only in v2 api
ActionIds
[]
string
`json:"action_ids,omitempty"`
//only in v2 api
CreatedAt
string
`json:"created_at,omitempty"`
//only in v2 api
}
// Returns all available sizes.
func
(
d
DigitalOceanClient
)
Sizes
()
([]
Size
,
error
)
{
resp
,
err
:=
NewRequest
(
d
,
"sizes"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
SizesResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Sizes
,
nil
type
ImagesResp
struct
{
Images
[]
Image
}
func
(
d
DigitalOceanClient
)
Size
(
slug_or_name_or_id
string
)
(
Size
,
error
)
{
sizes
,
err
:=
d
.
Sizes
()
if
err
!=
nil
{
return
Size
{},
err
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Slug
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Name
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
size
.
Id
==
uint
(
id
)
{
return
size
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown size '%v'"
,
slug_or_name_or_id
))
return
Size
{},
err
type
DigitalOceanClient
interface
{
CreateKey
(
string
,
string
)
(
uint
,
error
)
DestroyKey
(
uint
)
error
CreateDroplet
(
string
,
string
,
string
,
string
,
uint
,
bool
)
(
uint
,
error
)
DestroyDroplet
(
uint
)
error
PowerOffDroplet
(
uint
)
error
ShutdownDroplet
(
uint
)
error
CreateSnapshot
(
uint
,
string
)
error
Images
()
([]
Image
,
error
)
DestroyImage
(
uint
)
error
DropletStatus
(
uint
)
(
string
,
string
,
error
)
Image
(
string
)
(
Image
,
error
)
Regions
()
([]
Region
,
error
)
Region
(
string
)
(
Region
,
error
)
Sizes
()
([]
Size
,
error
)
Size
(
string
)
(
Size
,
error
)
}
builder/digitalocean/api_v1.go
0 → 100644
View file @
7865026e
// All of the methods used to communicate with the digital_ocean API
// are here. Their API is on a path to V2, so just plain JSON is used
// in place of a proper client library for now.
package
digitalocean
import
(
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/mitchellh/mapstructure"
)
type
DigitalOceanClientV1
struct
{
// The http client for communicating
client
*
http
.
Client
// Credentials
ClientID
string
APIKey
string
// The base URL of the API
APIURL
string
}
// Creates a new client for communicating with DO
func
DigitalOceanClientNewV1
(
client
string
,
key
string
,
url
string
)
*
DigitalOceanClientV1
{
c
:=
&
DigitalOceanClientV1
{
client
:
&
http
.
Client
{
Transport
:
&
http
.
Transport
{
Proxy
:
http
.
ProxyFromEnvironment
,
},
},
APIURL
:
url
,
ClientID
:
client
,
APIKey
:
key
,
}
return
c
}
// Creates an SSH Key and returns it's id
func
(
d
DigitalOceanClientV1
)
CreateKey
(
name
string
,
pub
string
)
(
uint
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
params
.
Set
(
"ssh_pub_key"
,
pub
)
body
,
err
:=
NewRequestV1
(
d
,
"ssh_keys/new"
,
params
)
if
err
!=
nil
{
return
0
,
err
}
// Read the SSH key's ID we just created
key
:=
body
[
"ssh_key"
]
.
(
map
[
string
]
interface
{})
keyId
:=
key
[
"id"
]
.
(
float64
)
return
uint
(
keyId
),
nil
}
// Destroys an SSH key
func
(
d
DigitalOceanClientV1
)
DestroyKey
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"ssh_keys/%v/destroy"
,
id
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
return
err
}
// Creates a droplet and returns it's id
func
(
d
DigitalOceanClientV1
)
CreateDroplet
(
name
string
,
size
string
,
image
string
,
region
string
,
keyId
uint
,
privateNetworking
bool
)
(
uint
,
error
)
{
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
found_size
,
err
:=
d
.
Size
(
size
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid size or lookup failure: '%s': %s"
,
size
,
err
)
}
found_image
,
err
:=
d
.
Image
(
image
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid image or lookup failure: '%s': %s"
,
image
,
err
)
}
found_region
,
err
:=
d
.
Region
(
region
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid region or lookup failure: '%s': %s"
,
region
,
err
)
}
params
.
Set
(
"size_slug"
,
found_size
.
Slug
)
params
.
Set
(
"image_slug"
,
found_image
.
Slug
)
params
.
Set
(
"region_slug"
,
found_region
.
Slug
)
params
.
Set
(
"ssh_key_ids"
,
fmt
.
Sprintf
(
"%v"
,
keyId
))
params
.
Set
(
"private_networking"
,
fmt
.
Sprintf
(
"%v"
,
privateNetworking
))
body
,
err
:=
NewRequestV1
(
d
,
"droplets/new"
,
params
)
if
err
!=
nil
{
return
0
,
err
}
// Read the Droplets ID
droplet
:=
body
[
"droplet"
]
.
(
map
[
string
]
interface
{})
dropletId
:=
droplet
[
"id"
]
.
(
float64
)
return
uint
(
dropletId
),
err
}
// Destroys a droplet
func
(
d
DigitalOceanClientV1
)
DestroyDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/destroy"
,
id
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
return
err
}
// Powers off a droplet
func
(
d
DigitalOceanClientV1
)
PowerOffDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/power_off"
,
id
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
return
err
}
// Shutsdown a droplet. This is a "soft" shutdown.
func
(
d
DigitalOceanClientV1
)
ShutdownDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/shutdown"
,
id
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
return
err
}
// Creates a snaphot of a droplet by it's ID
func
(
d
DigitalOceanClientV1
)
CreateSnapshot
(
id
uint
,
name
string
)
error
{
path
:=
fmt
.
Sprintf
(
"droplets/%v/snapshot"
,
id
)
params
:=
url
.
Values
{}
params
.
Set
(
"name"
,
name
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
params
)
return
err
}
// Returns all available images.
func
(
d
DigitalOceanClientV1
)
Images
()
([]
Image
,
error
)
{
resp
,
err
:=
NewRequestV1
(
d
,
"images"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
ImagesResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Images
,
nil
}
// Destroys an image by its ID.
func
(
d
DigitalOceanClientV1
)
DestroyImage
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"images/%d/destroy"
,
id
)
_
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
return
err
}
// Returns DO's string representation of status "off" "new" "active" etc.
func
(
d
DigitalOceanClientV1
)
DropletStatus
(
id
uint
)
(
string
,
string
,
error
)
{
path
:=
fmt
.
Sprintf
(
"droplets/%v"
,
id
)
body
,
err
:=
NewRequestV1
(
d
,
path
,
url
.
Values
{})
if
err
!=
nil
{
return
""
,
""
,
err
}
var
ip
string
// Read the droplet's "status"
droplet
:=
body
[
"droplet"
]
.
(
map
[
string
]
interface
{})
status
:=
droplet
[
"status"
]
.
(
string
)
if
droplet
[
"ip_address"
]
!=
nil
{
ip
=
droplet
[
"ip_address"
]
.
(
string
)
}
return
ip
,
status
,
err
}
// Sends an api request and returns a generic map[string]interface of
// the response.
func
NewRequestV1
(
d
DigitalOceanClientV1
,
path
string
,
params
url
.
Values
)
(
map
[
string
]
interface
{},
error
)
{
client
:=
d
.
client
// Add the authentication parameters
params
.
Set
(
"client_id"
,
d
.
ClientID
)
params
.
Set
(
"api_key"
,
d
.
APIKey
)
url
:=
fmt
.
Sprintf
(
"%s/%s?%s"
,
d
.
APIURL
,
path
,
params
.
Encode
())
// Do some basic scrubbing so sensitive information doesn't appear in logs
scrubbedUrl
:=
strings
.
Replace
(
url
,
d
.
ClientID
,
"CLIENT_ID"
,
-
1
)
scrubbedUrl
=
strings
.
Replace
(
scrubbedUrl
,
d
.
APIKey
,
"API_KEY"
,
-
1
)
log
.
Printf
(
"sending new request to digitalocean: %s"
,
scrubbedUrl
)
var
lastErr
error
for
attempts
:=
1
;
attempts
<
10
;
attempts
++
{
resp
,
err
:=
client
.
Get
(
url
)
if
err
!=
nil
{
return
nil
,
err
}
body
,
err
:=
ioutil
.
ReadAll
(
resp
.
Body
)
resp
.
Body
.
Close
()
if
err
!=
nil
{
return
nil
,
err
}
log
.
Printf
(
"response from digitalocean: %s"
,
body
)
var
decodedResponse
map
[
string
]
interface
{}
err
=
json
.
Unmarshal
(
body
,
&
decodedResponse
)
if
err
!=
nil
{
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Failed to decode JSON response (HTTP %v) from DigitalOcean: %s"
,
resp
.
StatusCode
,
body
))
return
decodedResponse
,
err
}
// Check for errors sent by digitalocean
status
:=
decodedResponse
[
"status"
]
.
(
string
)
if
status
==
"OK"
{
return
decodedResponse
,
nil
}
if
status
==
"ERROR"
{
statusRaw
,
ok
:=
decodedResponse
[
"error_message"
]
if
ok
{
status
=
statusRaw
.
(
string
)
}
else
{
status
=
fmt
.
Sprintf
(
"Unknown error. Full response body: %s"
,
body
)
}
}
lastErr
=
errors
.
New
(
fmt
.
Sprintf
(
"Received error from DigitalOcean (%d): %s"
,
resp
.
StatusCode
,
status
))
log
.
Println
(
lastErr
)
if
strings
.
Contains
(
status
,
"a pending event"
)
{
// Retry, DigitalOcean sends these dumb "pending event"
// errors all the time.
time
.
Sleep
(
5
*
time
.
Second
)
continue
}
// Some other kind of error. Just return.
return
decodedResponse
,
lastErr
}
return
nil
,
lastErr
}
func
(
d
DigitalOceanClientV1
)
Image
(
slug_or_name_or_id
string
)
(
Image
,
error
)
{
images
,
err
:=
d
.
Images
()
if
err
!=
nil
{
return
Image
{},
err
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Slug
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Name
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
image
.
Id
==
uint
(
id
)
{
return
image
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown image '%v'"
,
slug_or_name_or_id
))
return
Image
{},
err
}
// Returns all available regions.
func
(
d
DigitalOceanClientV1
)
Regions
()
([]
Region
,
error
)
{
resp
,
err
:=
NewRequestV1
(
d
,
"regions"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
RegionsResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Regions
,
nil
}
func
(
d
DigitalOceanClientV1
)
Region
(
slug_or_name_or_id
string
)
(
Region
,
error
)
{
regions
,
err
:=
d
.
Regions
()
if
err
!=
nil
{
return
Region
{},
err
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Slug
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Name
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
region
.
Id
==
uint
(
id
)
{
return
region
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown region '%v'"
,
slug_or_name_or_id
))
return
Region
{},
err
}
// Returns all available sizes.
func
(
d
DigitalOceanClientV1
)
Sizes
()
([]
Size
,
error
)
{
resp
,
err
:=
NewRequestV1
(
d
,
"sizes"
,
url
.
Values
{})
if
err
!=
nil
{
return
nil
,
err
}
var
result
SizesResp
if
err
:=
mapstructure
.
Decode
(
resp
,
&
result
);
err
!=
nil
{
return
nil
,
err
}
return
result
.
Sizes
,
nil
}
func
(
d
DigitalOceanClientV1
)
Size
(
slug_or_name_or_id
string
)
(
Size
,
error
)
{
sizes
,
err
:=
d
.
Sizes
()
if
err
!=
nil
{
return
Size
{},
err
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Slug
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Name
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
size
.
Id
==
uint
(
id
)
{
return
size
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown size '%v'"
,
slug_or_name_or_id
))
return
Size
{},
err
}
builder/digitalocean/api_v2.go
0 → 100644
View file @
7865026e
// are here. Their API is on a path to V2, so just plain JSON is used
// in place of a proper client library for now.
package
digitalocean
import
(
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"strings"
)
type
DigitalOceanClientV2
struct
{
// The http client for communicating
client
*
http
.
Client
// Credentials
APIToken
string
// The base URL of the API
APIURL
string
}
// Creates a new client for communicating with DO
func
DigitalOceanClientNewV2
(
token
string
,
url
string
)
*
DigitalOceanClientV2
{
c
:=
&
DigitalOceanClientV2
{
client
:
&
http
.
Client
{
Transport
:
&
http
.
Transport
{
Proxy
:
http
.
ProxyFromEnvironment
,
},
},
APIURL
:
url
,
APIToken
:
token
,
}
return
c
}
// Creates an SSH Key and returns it's id
func
(
d
DigitalOceanClientV2
)
CreateKey
(
name
string
,
pub
string
)
(
uint
,
error
)
{
type
KeyReq
struct
{
Name
string
`json:"name"`
PublicKey
string
`json:"public_key"`
}
type
KeyRes
struct
{
SSHKey
struct
{
Id
uint
Name
string
Fingerprint
string
PublicKey
string
`json:"public_key"`
}
`json:"ssh_key"`
}
req
:=
&
KeyReq
{
Name
:
name
,
PublicKey
:
pub
}
res
:=
KeyRes
{}
err
:=
NewRequestV2
(
d
,
"v2/account/keys"
,
"POST"
,
req
,
&
res
)
if
err
!=
nil
{
return
0
,
err
}
return
res
.
SSHKey
.
Id
,
err
}
// Destroys an SSH key
func
(
d
DigitalOceanClientV2
)
DestroyKey
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"v2/account/keys/%v"
,
id
)
return
NewRequestV2
(
d
,
path
,
"DELETE"
,
nil
,
nil
)
}
// Creates a droplet and returns it's id
func
(
d
DigitalOceanClientV2
)
CreateDroplet
(
name
string
,
size
string
,
image
string
,
region
string
,
keyId
uint
,
privateNetworking
bool
)
(
uint
,
error
)
{
type
DropletReq
struct
{
Name
string
`json:"name"`
Region
string
`json:"region"`
Size
string
`json:"size"`
Image
string
`json:"image"`
SSHKeys
[]
string
`json:"ssh_keys,omitempty"`
Backups
bool
`json:"backups,omitempty"`
IPv6
bool
`json:"ipv6,omitempty"`
PrivateNetworking
bool
`json:"private_networking,omitempty"`
}
type
DropletRes
struct
{
Droplet
struct
{
Id
uint
Name
string
Memory
uint
VCPUS
uint
`json:"vcpus"`
Disk
uint
Region
Region
Image
Image
Size
Size
Locked
bool
CreateAt
string
`json:"created_at"`
Status
string
Networks
struct
{
V4
[]
struct
{
IPAddr
string
`json:"ip_address"`
Netmask
string
Gateway
string
Type
string
}
`json:"v4,omitempty"`
V6
[]
struct
{
IPAddr
string
`json:"ip_address"`
CIDR
uint
`json:"cidr"`
Gateway
string
Type
string
}
`json:"v6,omitempty"`
}
Kernel
struct
{
Id
uint
Name
string
Version
string
}
BackupIds
[]
uint
SnapshotIds
[]
uint
ActionIds
[]
uint
Features
[]
string
`json:"features,omitempty"`
}
}
req
:=
&
DropletReq
{
Name
:
name
}
res
:=
DropletRes
{}
found_size
,
err
:=
d
.
Size
(
size
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid size or lookup failure: '%s': %s"
,
size
,
err
)
}
found_image
,
err
:=
d
.
Image
(
image
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid image or lookup failure: '%s': %s"
,
image
,
err
)
}
found_region
,
err
:=
d
.
Region
(
region
)
if
err
!=
nil
{
return
0
,
fmt
.
Errorf
(
"Invalid region or lookup failure: '%s': %s"
,
region
,
err
)
}
req
.
Size
=
found_size
.
Slug
req
.
Image
=
found_image
.
Slug
req
.
Region
=
found_region
.
Slug
req
.
SSHKeys
=
[]
string
{
fmt
.
Sprintf
(
"%v"
,
keyId
)}
req
.
PrivateNetworking
=
privateNetworking
err
=
NewRequestV2
(
d
,
"v2/droplets"
,
"POST"
,
req
,
&
res
)
if
err
!=
nil
{
return
0
,
err
}
return
res
.
Droplet
.
Id
,
err
}
// Destroys a droplet
func
(
d
DigitalOceanClientV2
)
DestroyDroplet
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"v2/droplets/%v"
,
id
)
return
NewRequestV2
(
d
,
path
,
"DELETE"
,
nil
,
nil
)
}
// Powers off a droplet
func
(
d
DigitalOceanClientV2
)
PowerOffDroplet
(
id
uint
)
error
{
type
ActionReq
struct
{
Type
string
`json:"type"`
}
type
ActionRes
struct
{
}
req
:=
&
ActionReq
{
Type
:
"power_off"
}
path
:=
fmt
.
Sprintf
(
"v2/droplets/%v/actions"
,
id
)
return
NewRequestV2
(
d
,
path
,
"POST"
,
req
,
nil
)
}
// Shutsdown a droplet. This is a "soft" shutdown.
func
(
d
DigitalOceanClientV2
)
ShutdownDroplet
(
id
uint
)
error
{
type
ActionReq
struct
{
Type
string
`json:"type"`
}
type
ActionRes
struct
{
}
req
:=
&
ActionReq
{
Type
:
"shutdown"
}
path
:=
fmt
.
Sprintf
(
"v2/droplets/%v/actions"
,
id
)
return
NewRequestV2
(
d
,
path
,
"POST"
,
req
,
nil
)
}
// Creates a snaphot of a droplet by it's ID
func
(
d
DigitalOceanClientV2
)
CreateSnapshot
(
id
uint
,
name
string
)
error
{
type
ActionReq
struct
{
Type
string
`json:"type"`
Name
string
`json:"name"`
}
type
ActionRes
struct
{
}
req
:=
&
ActionReq
{
Type
:
"snapshot"
,
Name
:
name
}
path
:=
fmt
.
Sprintf
(
"v2/droplets/%v/actions"
,
id
)
return
NewRequestV2
(
d
,
path
,
"POST"
,
req
,
nil
)
}
// Returns all available images.
func
(
d
DigitalOceanClientV2
)
Images
()
([]
Image
,
error
)
{
res
:=
ImagesResp
{}
err
:=
NewRequestV2
(
d
,
"v2/images?per_page=200"
,
"GET"
,
nil
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
.
Images
,
nil
}
// Destroys an image by its ID.
func
(
d
DigitalOceanClientV2
)
DestroyImage
(
id
uint
)
error
{
path
:=
fmt
.
Sprintf
(
"v2/images/%d"
,
id
)
return
NewRequestV2
(
d
,
path
,
"DELETE"
,
nil
,
nil
)
}
// Returns DO's string representation of status "off" "new" "active" etc.
func
(
d
DigitalOceanClientV2
)
DropletStatus
(
id
uint
)
(
string
,
string
,
error
)
{
path
:=
fmt
.
Sprintf
(
"v2/droplets/%v"
,
id
)
type
DropletRes
struct
{
Droplet
struct
{
Id
uint
Name
string
Memory
uint
VCPUS
uint
`json:"vcpus"`
Disk
uint
Region
Region
Image
Image
Size
Size
Locked
bool
CreateAt
string
`json:"created_at"`
Status
string
Networks
struct
{
V4
[]
struct
{
IPAddr
string
`json:"ip_address"`
Netmask
string
Gateway
string
Type
string
}
`json:"v4,omitempty"`
V6
[]
struct
{
IPAddr
string
`json:"ip_address"`
CIDR
uint
`json:"cidr"`
Gateway
string
Type
string
}
`json:"v6,omitempty"`
}
Kernel
struct
{
Id
uint
Name
string
Version
string
}
BackupIds
[]
uint
SnapshotIds
[]
uint
ActionIds
[]
uint
Features
[]
string
`json:"features,omitempty"`
}
}
res
:=
DropletRes
{}
err
:=
NewRequestV2
(
d
,
path
,
"GET"
,
nil
,
&
res
)
if
err
!=
nil
{
return
""
,
""
,
err
}
var
ip
string
if
len
(
res
.
Droplet
.
Networks
.
V4
)
>
0
{
ip
=
res
.
Droplet
.
Networks
.
V4
[
0
]
.
IPAddr
}
return
ip
,
res
.
Droplet
.
Status
,
err
}
// Sends an api request and returns a generic map[string]interface of
// the response.
func
NewRequestV2
(
d
DigitalOceanClientV2
,
path
string
,
method
string
,
req
interface
{},
res
interface
{})
error
{
var
err
error
var
request
*
http
.
Request
client
:=
d
.
client
buf
:=
new
(
bytes
.
Buffer
)
// Add the authentication parameters
url
:=
fmt
.
Sprintf
(
"%s/%s"
,
d
.
APIURL
,
path
)
if
req
!=
nil
{
enc
:=
json
.
NewEncoder
(
buf
)
enc
.
Encode
(
req
)
defer
buf
.
Reset
()
request
,
err
=
http
.
NewRequest
(
method
,
url
,
buf
)
}
else
{
request
,
err
=
http
.
NewRequest
(
method
,
url
,
nil
)
}
if
err
!=
nil
{
return
err
}
// Add the authentication parameters
request
.
Header
.
Add
(
"Authorization"
,
"Bearer "
+
d
.
APIToken
)
log
.
Printf
(
"sending new request to digitalocean: %s"
,
url
)
resp
,
err
:=
client
.
Do
(
request
)
if
err
!=
nil
{
return
err
}
if
method
==
"DELETE"
&&
resp
.
StatusCode
==
204
{
if
resp
.
Body
!=
nil
{
resp
.
Body
.
Close
()
}
return
nil
}
if
resp
.
Body
==
nil
{
return
errors
.
New
(
"Request returned empty body"
)
}
body
,
err
:=
ioutil
.
ReadAll
(
resp
.
Body
)
resp
.
Body
.
Close
()
if
err
!=
nil
{
return
err
}
log
.
Printf
(
"response from digitalocean: %s"
,
body
)
err
=
json
.
Unmarshal
(
body
,
&
res
)
if
err
!=
nil
{
return
errors
.
New
(
fmt
.
Sprintf
(
"Failed to decode JSON response %s (HTTP %v) from DigitalOcean: %s"
,
err
.
Error
(),
resp
.
StatusCode
,
body
))
}
return
nil
}
func
(
d
DigitalOceanClientV2
)
Image
(
slug_or_name_or_id
string
)
(
Image
,
error
)
{
images
,
err
:=
d
.
Images
()
if
err
!=
nil
{
return
Image
{},
err
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Slug
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
if
strings
.
EqualFold
(
image
.
Name
,
slug_or_name_or_id
)
{
return
image
,
nil
}
}
for
_
,
image
:=
range
images
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
image
.
Id
==
uint
(
id
)
{
return
image
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown image '%v'"
,
slug_or_name_or_id
))
return
Image
{},
err
}
// Returns all available regions.
func
(
d
DigitalOceanClientV2
)
Regions
()
([]
Region
,
error
)
{
res
:=
RegionsResp
{}
err
:=
NewRequestV2
(
d
,
"v2/regions?per_page=200"
,
"GET"
,
nil
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
.
Regions
,
nil
}
func
(
d
DigitalOceanClientV2
)
Region
(
slug_or_name_or_id
string
)
(
Region
,
error
)
{
regions
,
err
:=
d
.
Regions
()
if
err
!=
nil
{
return
Region
{},
err
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Slug
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
if
strings
.
EqualFold
(
region
.
Name
,
slug_or_name_or_id
)
{
return
region
,
nil
}
}
for
_
,
region
:=
range
regions
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
region
.
Id
==
uint
(
id
)
{
return
region
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown region '%v'"
,
slug_or_name_or_id
))
return
Region
{},
err
}
// Returns all available sizes.
func
(
d
DigitalOceanClientV2
)
Sizes
()
([]
Size
,
error
)
{
res
:=
SizesResp
{}
err
:=
NewRequestV2
(
d
,
"v2/sizes?per_page=200"
,
"GET"
,
nil
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
.
Sizes
,
nil
}
func
(
d
DigitalOceanClientV2
)
Size
(
slug_or_name_or_id
string
)
(
Size
,
error
)
{
sizes
,
err
:=
d
.
Sizes
()
if
err
!=
nil
{
return
Size
{},
err
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Slug
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
if
strings
.
EqualFold
(
size
.
Name
,
slug_or_name_or_id
)
{
return
size
,
nil
}
}
for
_
,
size
:=
range
sizes
{
id
,
err
:=
strconv
.
Atoi
(
slug_or_name_or_id
)
if
err
==
nil
{
if
size
.
Id
==
uint
(
id
)
{
return
size
,
nil
}
}
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Unknown size '%v'"
,
slug_or_name_or_id
))
return
Size
{},
err
}
builder/digitalocean/artifact.go
View file @
7865026e
...
...
@@ -16,7 +16,7 @@ type Artifact struct {
regionName
string
// The client for making API calls
client
*
DigitalOceanClient
client
DigitalOceanClient
}
func
(
*
Artifact
)
BuilderId
()
string
{
...
...
builder/digitalocean/builder.go
View file @
7865026e
...
...
@@ -40,6 +40,7 @@ type config struct {
ClientID
string
`mapstructure:"client_id"`
APIKey
string
`mapstructure:"api_key"`
APIURL
string
`mapstructure:"api_url"`
APIToken
string
`mapstructure:"api_token"`
RegionID
uint
`mapstructure:"region_id"`
SizeID
uint
`mapstructure:"size_id"`
ImageID
uint
`mapstructure:"image_id"`
...
...
@@ -101,6 +102,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
b
.
config
.
APIURL
=
os
.
Getenv
(
"DIGITALOCEAN_API_URL"
)
}
if
b
.
config
.
APIToken
==
""
{
// Default to environment variable for api_token, if it exists
b
.
config
.
APIToken
=
os
.
Getenv
(
"DIGITALOCEAN_API_TOKEN"
)
}
if
b
.
config
.
Region
==
""
{
if
b
.
config
.
RegionID
!=
0
{
b
.
config
.
Region
=
fmt
.
Sprintf
(
"%v"
,
b
.
config
.
RegionID
)
...
...
@@ -164,6 +170,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
"client_id"
:
&
b
.
config
.
ClientID
,
"api_key"
:
&
b
.
config
.
APIKey
,
"api_url"
:
&
b
.
config
.
APIURL
,
"api_token"
:
&
b
.
config
.
APIToken
,
"snapshot_name"
:
&
b
.
config
.
SnapshotName
,
"droplet_name"
:
&
b
.
config
.
DropletName
,
"ssh_username"
:
&
b
.
config
.
SSHUsername
,
...
...
@@ -180,19 +187,21 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
}
if
b
.
config
.
APIToken
==
""
{
// Required configurations that will display errors if not set
if
b
.
config
.
ClientID
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"a client_id must be specified"
))
}
if
b
.
config
.
APIURL
==
""
{
b
.
config
.
APIURL
=
"https://api.digitalocean.com"
errs
,
errors
.
New
(
"a client_id for v1 auth or api_token for v2 auth must be specified"
))
}
if
b
.
config
.
APIKey
==
""
{
errs
=
packer
.
MultiErrorAppend
(
errs
,
errors
.
New
(
"an api_key must be specified"
))
errs
,
errors
.
New
(
"a api_key for v1 auth or api_token for v2 auth must be specified"
))
}
}
if
b
.
config
.
APIURL
==
""
{
b
.
config
.
APIURL
=
"https://api.digitalocean.com"
}
sshTimeout
,
err
:=
time
.
ParseDuration
(
b
.
config
.
RawSSHTimeout
)
...
...
@@ -218,8 +227,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
}
func
(
b
*
Builder
)
Run
(
ui
packer
.
Ui
,
hook
packer
.
Hook
,
cache
packer
.
Cache
)
(
packer
.
Artifact
,
error
)
{
var
client
DigitalOceanClient
// Initialize the DO API client
client
:=
DigitalOceanClient
{}
.
New
(
b
.
config
.
ClientID
,
b
.
config
.
APIKey
,
b
.
config
.
APIURL
)
if
b
.
config
.
APIToken
==
""
{
client
=
DigitalOceanClientNewV1
(
b
.
config
.
ClientID
,
b
.
config
.
APIKey
,
b
.
config
.
APIURL
)
}
else
{
client
=
DigitalOceanClientNewV2
(
b
.
config
.
APIToken
,
b
.
config
.
APIURL
)
}
// Set up the state
state
:=
new
(
multistep
.
BasicStateBag
)
...
...
builder/digitalocean/step_create_droplet.go
View file @
7865026e
...
...
@@ -12,7 +12,7 @@ type stepCreateDroplet struct {
}
func
(
s
*
stepCreateDroplet
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
sshKeyId
:=
state
.
Get
(
"ssh_key_id"
)
.
(
uint
)
...
...
@@ -44,7 +44,7 @@ func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
return
}
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
...
...
builder/digitalocean/step_create_ssh_key.go
View file @
7865026e
...
...
@@ -19,7 +19,7 @@ type stepCreateSSHKey struct {
}
func
(
s
*
stepCreateSSHKey
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
ui
.
Say
(
"Creating temporary ssh key for droplet..."
)
...
...
@@ -71,15 +71,14 @@ func (s *stepCreateSSHKey) Cleanup(state multistep.StateBag) {
return
}
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
ui
.
Say
(
"Deleting temporary ssh key..."
)
err
:=
client
.
DestroyKey
(
s
.
keyId
)
curlstr
:=
fmt
.
Sprintf
(
"curl '%v/ssh_keys/%v/destroy?client_id=%v&api_key=%v'"
,
c
.
APIURL
,
s
.
keyId
,
c
.
ClientID
,
c
.
APIKey
)
curlstr
:=
fmt
.
Sprintf
(
"curl -H 'Authorization: Bearer #TOKEN#' -X DELETE '%v/v2/account/keys/%v'"
,
c
.
APIURL
,
s
.
keyId
)
if
err
!=
nil
{
log
.
Printf
(
"Error cleaning up ssh key: %v"
,
err
.
Error
())
...
...
builder/digitalocean/step_droplet_info.go
View file @
7865026e
...
...
@@ -2,6 +2,7 @@ package digitalocean
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
...
...
@@ -9,7 +10,7 @@ import (
type
stepDropletInfo
struct
{}
func
(
s
*
stepDropletInfo
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
dropletId
:=
state
.
Get
(
"droplet_id"
)
.
(
uint
)
...
...
builder/digitalocean/step_power_off.go
View file @
7865026e
...
...
@@ -2,15 +2,16 @@ package digitalocean
import
(
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type
stepPowerOff
struct
{}
func
(
s
*
stepPowerOff
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
dropletId
:=
state
.
Get
(
"droplet_id"
)
.
(
uint
)
...
...
builder/digitalocean/step_shutdown.go
View file @
7865026e
...
...
@@ -2,16 +2,17 @@ package digitalocean
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
type
stepShutdown
struct
{}
func
(
s
*
stepShutdown
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
dropletId
:=
state
.
Get
(
"droplet_id"
)
.
(
uint
)
...
...
builder/digitalocean/step_snapshot.go
View file @
7865026e
...
...
@@ -3,15 +3,16 @@ package digitalocean
import
(
"errors"
"fmt"
"log"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
)
type
stepSnapshot
struct
{}
func
(
s
*
stepSnapshot
)
Run
(
state
multistep
.
StateBag
)
multistep
.
StepAction
{
client
:=
state
.
Get
(
"client"
)
.
(
*
DigitalOceanClient
)
client
:=
state
.
Get
(
"client"
)
.
(
DigitalOceanClient
)
ui
:=
state
.
Get
(
"ui"
)
.
(
packer
.
Ui
)
c
:=
state
.
Get
(
"config"
)
.
(
config
)
dropletId
:=
state
.
Get
(
"droplet_id"
)
.
(
uint
)
...
...
builder/digitalocean/wait.go
View file @
7865026e
...
...
@@ -8,7 +8,7 @@ import (
// waitForState simply blocks until the droplet is in
// a state we expect, while eventually timing out.
func
waitForDropletState
(
desiredState
string
,
dropletId
uint
,
client
*
DigitalOceanClient
,
timeout
time
.
Duration
)
error
{
func
waitForDropletState
(
desiredState
string
,
dropletId
uint
,
client
DigitalOceanClient
,
timeout
time
.
Duration
)
error
{
done
:=
make
(
chan
struct
{})
defer
close
(
done
)
...
...
website/source/docs/builders/digitalocean.html.markdown
View file @
7865026e
...
...
@@ -24,7 +24,7 @@ There are many configuration options available for the builder. They are
segmented below into two categories: required and optional parameters. Within
each category, the available configuration keys are alphabetized.
### Required:
### Required
v1 api
:
*
`api_key`
(string) - The API key to use to access your account. You can
retrieve this on the "API" page visible after logging into your account
...
...
@@ -38,8 +38,17 @@ each category, the available configuration keys are alphabetized.
If not specified, Packer will use the environment variable
`DIGITALOCEAN_CLIENT_ID`
, if set.
### Required v2 api:
*
`api_token`
(string) - The client TOKEN to use to access your account. If it
specified, then use v2 api (current), if not then used old (v1) deprecated api.
Also it can be specified via environment variable
`DIGITALOCEAN_API_TOKEN`
, if set.
### Optional:
*
`api_url`
(string) - API endpoint, by default use https://api.digitalocean.com
Also it can be specified via environment variable
`DIGITALOCEAN_API_URL`
, if set.
*
`droplet_name`
(string) - The name assigned to the droplet. DigitalOcean
sets the hostname of the machine to this value.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment