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
5ecd30a7
Commit
5ecd30a7
authored
Jun 17, 2013
by
Mitchell Hashimoto
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #17 from mitchellh/b-digital-ocean-fixup
DigitalOcean Completion
parents
dd89716e
6eb0568f
Changes
9
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
196 additions
and
89 deletions
+196
-89
builder/digitalocean/api.go
builder/digitalocean/api.go
+8
-6
builder/digitalocean/artifact.go
builder/digitalocean/artifact.go
+27
-0
builder/digitalocean/artifact_test.go
builder/digitalocean/artifact_test.go
+23
-0
builder/digitalocean/builder.go
builder/digitalocean/builder.go
+37
-5
builder/digitalocean/builder_test.go
builder/digitalocean/builder_test.go
+58
-4
builder/digitalocean/step_create_droplet.go
builder/digitalocean/step_create_droplet.go
+8
-2
builder/digitalocean/step_create_ssh_key.go
builder/digitalocean/step_create_ssh_key.go
+29
-70
builder/digitalocean/step_power_off.go
builder/digitalocean/step_power_off.go
+4
-1
builder/digitalocean/step_snapshot.go
builder/digitalocean/step_snapshot.go
+2
-1
No files found.
builder/digitalocean/api.go
View file @
5ecd30a7
...
...
@@ -154,15 +154,14 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in
err
=
json
.
Unmarshal
(
body
,
&
decodedResponse
)
log
.
Printf
(
"response from digitalocean: %v"
,
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
)
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Received non-200 HTTP status from DigitalOcean: %v"
,
resp
.
StatusCode
))
return
decodedResponse
,
err
}
log
.
Printf
(
"response from digital ocean: %v"
,
decodedResponse
)
if
err
!=
nil
{
return
decodedResponse
,
err
}
...
...
@@ -170,8 +169,11 @@ func NewRequest(d DigitalOceanClient, path string, params string) (map[string]in
// Catch all non-OK statuses from DO and return an error
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
)
// Get the actual error message if there is one
if
status
==
"ERROR"
{
status
=
decodedResponse
[
"error_message"
]
}
err
=
errors
.
New
(
fmt
.
Sprintf
(
"Received bad status from DigitalOcean: %v"
,
status
))
return
decodedResponse
,
err
}
...
...
builder/digitalocean/artifact.go
0 → 100644
View file @
5ecd30a7
package
digitalocean
import
(
"fmt"
)
type
Artifact
struct
{
// The name of the snapshot
snapshotName
string
}
func
(
*
Artifact
)
BuilderId
()
string
{
return
BuilderId
}
func
(
*
Artifact
)
Files
()
[]
string
{
// No files with DigitalOcean
return
nil
}
func
(
a
*
Artifact
)
Id
()
string
{
return
a
.
snapshotName
}
func
(
a
*
Artifact
)
String
()
string
{
return
fmt
.
Sprintf
(
"A snapshot was created: %v"
,
a
.
snapshotName
)
}
builder/digitalocean/artifact_test.go
0 → 100644
View file @
5ecd30a7
package
digitalocean
import
(
"github.com/mitchellh/packer/packer"
"testing"
)
func
TestArtifact_Impl
(
t
*
testing
.
T
)
{
var
raw
interface
{}
raw
=
&
Artifact
{}
if
_
,
ok
:=
raw
.
(
packer
.
Artifact
);
!
ok
{
t
.
Fatalf
(
"Artifact should be artifact"
)
}
}
func
TestArtifactString
(
t
*
testing
.
T
)
{
a
:=
&
Artifact
{
"packer-foobar"
}
expected
:=
"A snapshot was created: packer-foobar"
if
a
.
String
()
!=
expected
{
t
.
Fatalf
(
"artifact string should match: %v"
,
expected
)
}
}
builder/digitalocean/builder.go
View file @
5ecd30a7
...
...
@@ -4,6 +4,7 @@
package
digitalocean
import
(
"bytes"
"errors"
"fmt"
"github.com/mitchellh/mapstructure"
...
...
@@ -11,12 +12,18 @@ import (
"github.com/mitchellh/packer/builder/common"
"github.com/mitchellh/packer/packer"
"log"
"strconv"
"text/template"
"time"
)
// The unique id for the builder
const
BuilderId
=
"pearkes.digitalocean"
type
snapshotNameData
struct
{
CreateTime
string
}
// Configuration tells the builder the credentials
// to use while communicating with DO and describes the image
// you are creating
...
...
@@ -27,14 +34,17 @@ type config struct {
SizeID
uint
`mapstructure:"size_id"`
ImageID
uint
`mapstructure:"image_id"`
SnapshotName
string
`mapstructure:"snapshot_name"`
SnapshotName
string
SSHUsername
string
`mapstructure:"ssh_username"`
SSHPort
uint
`mapstructure:"ssh_port"`
SSHTimeout
time
.
Duration
EventDelay
time
.
Duration
PackerDebug
bool
`mapstructure:"packer_debug"`
RawSnapshotName
string
`mapstructure:"snapshot_name"`
RawSSHTimeout
string
`mapstructure:"ssh_timeout"`
RawEventDelay
string
`mapstructure:"event_delay"`
}
type
Builder
struct
{
...
...
@@ -78,9 +88,9 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b
.
config
.
SSHPort
=
22
}
if
b
.
config
.
SnapshotName
==
""
{
if
b
.
config
.
Raw
SnapshotName
==
""
{
// Default to packer-{{ unix timestamp (utc) }}
b
.
config
.
SnapshotName
=
"packer-{{.CreateTime}}"
b
.
config
.
Raw
SnapshotName
=
"packer-{{.CreateTime}}"
}
if
b
.
config
.
RawSSHTimeout
==
""
{
...
...
@@ -88,6 +98,12 @@ func (b *Builder) Prepare(raws ...interface{}) error {
b
.
config
.
RawSSHTimeout
=
"1m"
}
if
b
.
config
.
RawEventDelay
==
""
{
// Default to 5 second delays after creating events
// to allow DO to process
b
.
config
.
RawEventDelay
=
"5s"
}
// A list of errors on the configuration
errs
:=
make
([]
error
,
0
)
...
...
@@ -100,12 +116,28 @@ func (b *Builder) Prepare(raws ...interface{}) error {
if
b
.
config
.
APIKey
==
""
{
errs
=
append
(
errs
,
errors
.
New
(
"an api_key must be specified"
))
}
timeout
,
err
:=
time
.
ParseDuration
(
b
.
config
.
RawSSHTimeout
)
if
err
!=
nil
{
errs
=
append
(
errs
,
fmt
.
Errorf
(
"Failed parsing ssh_timeout: %s"
,
err
))
}
b
.
config
.
SSHTimeout
=
timeout
delay
,
err
:=
time
.
ParseDuration
(
b
.
config
.
RawEventDelay
)
if
err
!=
nil
{
errs
=
append
(
errs
,
fmt
.
Errorf
(
"Failed parsing event_delay: %s"
,
err
))
}
b
.
config
.
EventDelay
=
delay
// Parse the name of the snapshot
snapNameBuf
:=
new
(
bytes
.
Buffer
)
tData
:=
snapshotNameData
{
strconv
.
FormatInt
(
time
.
Now
()
.
UTC
()
.
Unix
(),
10
),
}
t
:=
template
.
Must
(
template
.
New
(
"snapshot"
)
.
Parse
(
b
.
config
.
RawSnapshotName
))
t
.
Execute
(
snapNameBuf
,
tData
)
b
.
config
.
SnapshotName
=
snapNameBuf
.
String
()
if
len
(
errs
)
>
0
{
return
&
packer
.
MultiError
{
errs
}
}
...
...
@@ -148,7 +180,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
b
.
runner
.
Run
(
state
)
return
nil
,
nil
return
&
Artifact
{
b
.
config
.
SnapshotName
}
,
nil
}
func
(
b
*
Builder
)
Cancel
()
{
...
...
builder/digitalocean/builder_test.go
View file @
5ecd30a7
...
...
@@ -2,6 +2,7 @@ package digitalocean
import
(
"github.com/mitchellh/packer/packer"
"strconv"
"testing"
)
...
...
@@ -220,18 +221,71 @@ func TestBuilderPrepare_SSHTimeout(t *testing.T) {
}
func
TestBuilderPrepare_
SnapshotName
(
t
*
testing
.
T
)
{
func
TestBuilderPrepare_
EventDelay
(
t
*
testing
.
T
)
{
var
b
Builder
config
:=
testConfig
()
// Test default
err
:=
b
.
Prepare
(
config
)
if
err
!=
nil
{
t
.
Fatalf
(
"should not have error: %s"
,
err
)
}
if
b
.
config
.
RawEventDelay
!=
"5s"
{
t
.
Errorf
(
"invalid: %d"
,
b
.
config
.
RawEventDelay
)
}
// Test set
config
[
"snapshot_name"
]
=
"foo"
config
[
"event_delay"
]
=
"10s"
b
=
Builder
{}
err
=
b
.
Prepare
(
config
)
if
err
!=
nil
{
t
.
Fatalf
(
"should not have error: %s"
,
err
)
}
// Test bad
config
[
"event_delay"
]
=
"tubes"
b
=
Builder
{}
err
=
b
.
Prepare
(
config
)
if
err
==
nil
{
t
.
Fatal
(
"should have error"
)
}
}
func
TestBuilderPrepare_SnapshotName
(
t
*
testing
.
T
)
{
var
b
Builder
config
:=
testConfig
()
// Test default
err
:=
b
.
Prepare
(
config
)
if
err
!=
nil
{
t
.
Fatalf
(
"should not have error: %s"
,
err
)
}
if
b
.
config
.
SnapshotName
!=
"foo
"
{
t
.
Errorf
(
"invalid: %
s"
,
b
.
config
.
SnapshotName
)
if
b
.
config
.
RawSnapshotName
!=
"packer-{{.CreateTime}}
"
{
t
.
Errorf
(
"invalid: %
d"
,
b
.
config
.
Raw
SnapshotName
)
}
// Test set
config
[
"snapshot_name"
]
=
"foobarbaz"
b
=
Builder
{}
err
=
b
.
Prepare
(
config
)
if
err
!=
nil
{
t
.
Fatalf
(
"should not have error: %s"
,
err
)
}
// Test set with template
config
[
"snapshot_name"
]
=
"{{.CreateTime}}"
b
=
Builder
{}
err
=
b
.
Prepare
(
config
)
if
err
!=
nil
{
t
.
Fatalf
(
"should not have error: %s"
,
err
)
}
_
,
err
=
strconv
.
ParseInt
(
b
.
config
.
SnapshotName
,
0
,
0
)
if
err
!=
nil
{
t
.
Fatalf
(
"failed to parse int in template: %s"
,
err
)
}
}
builder/digitalocean/step_create_droplet.go
View file @
5ecd30a7
...
...
@@ -6,6 +6,7 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
...
...
@@ -49,6 +50,7 @@ func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
client
:=
state
[
"client"
]
.
(
*
DigitalOceanClient
)
ui
:=
state
[
"ui"
]
.
(
packer
.
Ui
)
c
:=
state
[
"config"
]
.
(
config
)
// Destroy the droplet we just created
ui
.
Say
(
"Destroying droplet..."
)
...
...
@@ -56,12 +58,16 @@ func (s *stepCreateDroplet) Cleanup(state map[string]interface{}) {
// Sleep arbitrarily before sending destroy request
// Otherwise we get "pending event" errors, even though there isn't
// one.
time
.
Sleep
(
5
*
time
.
Second
)
log
.
Printf
(
"Sleeping for %v, event_delay"
,
c
.
RawEventDelay
)
time
.
Sleep
(
c
.
EventDelay
)
err
:=
client
.
DestroyDroplet
(
s
.
dropletId
)
curlstr
:=
fmt
.
Sprintf
(
"curl '%v/droplets/%v/destroy?client_id=%v&api_key=%v'"
,
DIGITALOCEAN_API_URL
,
s
.
dropletId
,
c
.
ClientID
,
c
.
APIKey
)
if
err
!=
nil
{
ui
.
Error
(
fmt
.
Sprintf
(
"Error destroying droplet. Please destroy it manually: %v"
,
s
.
dropletId
))
"Error destroying droplet. Please destroy it manually: %v"
,
curlstr
))
}
}
builder/digitalocean/step_create_ssh_key.go
View file @
5ecd30a7
...
...
@@ -2,7 +2,12 @@ package digitalocean
import
(
"cgl.tideland.biz/identifier"
"code.google.com/p/go.crypto/ssh"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
...
...
@@ -18,80 +23,29 @@ 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_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
// 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_bytes, err := x509.MarshalPKIXPublicKey(&pub)
// 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-----`
priv
,
err
:=
rsa
.
GenerateKey
(
rand
.
Reader
,
2014
)
// ASN.1 DER encoded form
priv_der
:=
x509
.
MarshalPKCS1PrivateKey
(
priv
)
priv_blk
:=
pem
.
Block
{
Type
:
"RSA PRIVATE KEY"
,
Headers
:
nil
,
Bytes
:
priv_der
,
}
// Set the private key in the statebag for later
state
[
"privateKey"
]
=
string
(
pem
.
EncodeToMemory
(
&
priv_blk
))
// Marshal the public key into SSH compatible format
pub
:=
priv
.
PublicKey
pub_sshformat
:=
string
(
ssh
.
MarshalAuthorizedKey
(
&
pub
))
// 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_s
tr
)
keyId
,
err
:=
client
.
CreateKey
(
name
,
pub_s
shformat
)
if
err
!=
nil
{
ui
.
Error
(
err
.
Error
())
...
...
@@ -117,12 +71,17 @@ func (s *stepCreateSSHKey) Cleanup(state map[string]interface{}) {
client
:=
state
[
"client"
]
.
(
*
DigitalOceanClient
)
ui
:=
state
[
"ui"
]
.
(
packer
.
Ui
)
c
:=
state
[
"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'"
,
DIGITALOCEAN_API_URL
,
s
.
keyId
,
c
.
ClientID
,
c
.
APIKey
)
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: %v"
,
s
.
keyId
))
"Error cleaning up ssh key. Please delete the key manually: %v"
,
curlstr
))
}
}
builder/digitalocean/step_power_off.go
View file @
5ecd30a7
...
...
@@ -3,6 +3,7 @@ package digitalocean
import
(
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"time"
)
...
...
@@ -10,13 +11,15 @@ type stepPowerOff struct{}
func
(
s
*
stepPowerOff
)
Run
(
state
map
[
string
]
interface
{})
multistep
.
StepAction
{
client
:=
state
[
"client"
]
.
(
*
DigitalOceanClient
)
c
:=
state
[
"config"
]
.
(
config
)
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
)
log
.
Printf
(
"Sleeping for %v, event_delay"
,
c
.
RawEventDelay
)
time
.
Sleep
(
c
.
EventDelay
)
// Poweroff the droplet so it can be snapshot
err
:=
client
.
PowerOffDroplet
(
dropletId
)
...
...
builder/digitalocean/step_snapshot.go
View file @
5ecd30a7
package
digitalocean
import
(
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
)
...
...
@@ -13,7 +14,7 @@ func (s *stepSnapshot) Run(state map[string]interface{}) multistep.StepAction {
c
:=
state
[
"config"
]
.
(
config
)
dropletId
:=
state
[
"droplet_id"
]
.
(
uint
)
ui
.
Say
(
"Creating snapshot..."
)
ui
.
Say
(
fmt
.
Sprintf
(
"Creating snapshot: %v"
,
c
.
SnapshotName
)
)
err
:=
client
.
CreateSnapshot
(
dropletId
,
c
.
SnapshotName
)
...
...
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