github.com/jerryclinesmith/packer@v0.3.7/builder/digitalocean/builder.go (about) 1 // The digitalocean package contains a packer.Builder implementation 2 // that builds DigitalOcean images (snapshots). 3 4 package digitalocean 5 6 import ( 7 "errors" 8 "fmt" 9 "github.com/mitchellh/multistep" 10 "github.com/mitchellh/packer/common" 11 "github.com/mitchellh/packer/packer" 12 "log" 13 "os" 14 "time" 15 ) 16 17 // The unique id for the builder 18 const BuilderId = "pearkes.digitalocean" 19 20 // Configuration tells the builder the credentials 21 // to use while communicating with DO and describes the image 22 // you are creating 23 type config struct { 24 common.PackerConfig `mapstructure:",squash"` 25 26 ClientID string `mapstructure:"client_id"` 27 APIKey string `mapstructure:"api_key"` 28 RegionID uint `mapstructure:"region_id"` 29 SizeID uint `mapstructure:"size_id"` 30 ImageID uint `mapstructure:"image_id"` 31 32 SnapshotName string `mapstructure:"snapshot_name"` 33 SSHUsername string `mapstructure:"ssh_username"` 34 SSHPort uint `mapstructure:"ssh_port"` 35 36 RawSSHTimeout string `mapstructure:"ssh_timeout"` 37 RawStateTimeout string `mapstructure:"state_timeout"` 38 39 // These are unexported since they're set by other fields 40 // being set. 41 sshTimeout time.Duration 42 stateTimeout time.Duration 43 44 tpl *packer.ConfigTemplate 45 } 46 47 type Builder struct { 48 config config 49 runner multistep.Runner 50 } 51 52 func (b *Builder) Prepare(raws ...interface{}) error { 53 md, err := common.DecodeConfig(&b.config, raws...) 54 if err != nil { 55 return err 56 } 57 58 b.config.tpl, err = packer.NewConfigTemplate() 59 if err != nil { 60 return err 61 } 62 b.config.tpl.UserVars = b.config.PackerUserVars 63 64 // Accumulate any errors 65 errs := common.CheckUnusedConfig(md) 66 67 // Optional configuration with defaults 68 if b.config.APIKey == "" { 69 // Default to environment variable for api_key, if it exists 70 b.config.APIKey = os.Getenv("DIGITALOCEAN_API_KEY") 71 } 72 73 if b.config.ClientID == "" { 74 // Default to environment variable for client_id, if it exists 75 b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID") 76 } 77 78 if b.config.RegionID == 0 { 79 // Default to Region "New York" 80 b.config.RegionID = 1 81 } 82 83 if b.config.SizeID == 0 { 84 // Default to 512mb, the smallest droplet size 85 b.config.SizeID = 66 86 } 87 88 if b.config.ImageID == 0 { 89 // Default to base image "Ubuntu 12.04 x64 Server (id: 284203)" 90 b.config.ImageID = 284203 91 } 92 93 if b.config.SnapshotName == "" { 94 // Default to packer-{{ unix timestamp (utc) }} 95 b.config.SnapshotName = "packer-{{timestamp}}" 96 } 97 98 if b.config.SSHUsername == "" { 99 // Default to "root". You can override this if your 100 // SourceImage has a different user account then the DO default 101 b.config.SSHUsername = "root" 102 } 103 104 if b.config.SSHPort == 0 { 105 // Default to port 22 per DO default 106 b.config.SSHPort = 22 107 } 108 109 if b.config.RawSSHTimeout == "" { 110 // Default to 1 minute timeouts 111 b.config.RawSSHTimeout = "1m" 112 } 113 114 if b.config.RawStateTimeout == "" { 115 // Default to 6 minute timeouts waiting for 116 // desired state. i.e waiting for droplet to become active 117 b.config.RawStateTimeout = "6m" 118 } 119 120 templates := map[string]*string{ 121 "client_id": &b.config.ClientID, 122 "api_key": &b.config.APIKey, 123 "snapshot_name": &b.config.SnapshotName, 124 "ssh_username": &b.config.SSHUsername, 125 "ssh_timeout": &b.config.RawSSHTimeout, 126 "state_timeout": &b.config.RawStateTimeout, 127 } 128 129 for n, ptr := range templates { 130 var err error 131 *ptr, err = b.config.tpl.Process(*ptr, nil) 132 if err != nil { 133 errs = packer.MultiErrorAppend( 134 errs, fmt.Errorf("Error processing %s: %s", n, err)) 135 } 136 } 137 138 // Required configurations that will display errors if not set 139 if b.config.ClientID == "" { 140 errs = packer.MultiErrorAppend( 141 errs, errors.New("a client_id must be specified")) 142 } 143 144 if b.config.APIKey == "" { 145 errs = packer.MultiErrorAppend( 146 errs, errors.New("an api_key must be specified")) 147 } 148 149 sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout) 150 if err != nil { 151 errs = packer.MultiErrorAppend( 152 errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) 153 } 154 b.config.sshTimeout = sshTimeout 155 156 stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout) 157 if err != nil { 158 errs = packer.MultiErrorAppend( 159 errs, fmt.Errorf("Failed parsing state_timeout: %s", err)) 160 } 161 b.config.stateTimeout = stateTimeout 162 163 if errs != nil && len(errs.Errors) > 0 { 164 return errs 165 } 166 167 log.Printf("Config: %+v", b.config) 168 return nil 169 } 170 171 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 172 // Initialize the DO API client 173 client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey) 174 175 // Set up the state 176 state := new(multistep.BasicStateBag) 177 state.Put("config", b.config) 178 state.Put("client", client) 179 state.Put("hook", hook) 180 state.Put("ui", ui) 181 182 // Build the steps 183 steps := []multistep.Step{ 184 new(stepCreateSSHKey), 185 new(stepCreateDroplet), 186 new(stepDropletInfo), 187 &common.StepConnectSSH{ 188 SSHAddress: sshAddress, 189 SSHConfig: sshConfig, 190 SSHWaitTimeout: 5 * time.Minute, 191 }, 192 new(common.StepProvision), 193 new(stepShutdown), 194 new(stepPowerOff), 195 new(stepSnapshot), 196 } 197 198 // Run the steps 199 if b.config.PackerDebug { 200 b.runner = &multistep.DebugRunner{ 201 Steps: steps, 202 PauseFn: common.MultistepDebugFn(ui), 203 } 204 } else { 205 b.runner = &multistep.BasicRunner{Steps: steps} 206 } 207 208 b.runner.Run(state) 209 210 // If there was an error, return that 211 if rawErr, ok := state.GetOk("error"); ok { 212 return nil, rawErr.(error) 213 } 214 215 if _, ok := state.GetOk("snapshot_name"); !ok { 216 log.Println("Failed to find snapshot_name in state. Bug?") 217 return nil, nil 218 } 219 220 artifact := &Artifact{ 221 snapshotName: state.Get("snapshot_name").(string), 222 snapshotId: state.Get("snapshot_image_id").(uint), 223 client: client, 224 } 225 226 return artifact, nil 227 } 228 229 func (b *Builder) Cancel() { 230 if b.runner != nil { 231 log.Println("Cancelling the step runner...") 232 b.runner.Cancel() 233 } 234 }