github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/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 "log" 10 "os" 11 "time" 12 13 "github.com/mitchellh/multistep" 14 "github.com/mitchellh/packer/common" 15 "github.com/mitchellh/packer/common/uuid" 16 "github.com/mitchellh/packer/packer" 17 ) 18 19 // see https://api.digitalocean.com/images/?client_id=[client_id]&api_key=[api_key] 20 // name="Ubuntu 12.04.4 x64", id=3101045, 21 const DefaultImage = "ubuntu-12-04-x64" 22 23 // see https://api.digitalocean.com/regions/?client_id=[client_id]&api_key=[api_key] 24 // name="New York", id=1 25 const DefaultRegion = "nyc1" 26 27 // see https://api.digitalocean.com/sizes/?client_id=[client_id]&api_key=[api_key] 28 // name="512MB", id=66 (the smallest droplet size) 29 const DefaultSize = "512mb" 30 31 // The unique id for the builder 32 const BuilderId = "pearkes.digitalocean" 33 34 // Configuration tells the builder the credentials 35 // to use while communicating with DO and describes the image 36 // you are creating 37 type config struct { 38 common.PackerConfig `mapstructure:",squash"` 39 40 ClientID string `mapstructure:"client_id"` 41 APIKey string `mapstructure:"api_key"` 42 APIURL string `mapstructure:"api_url"` 43 RegionID uint `mapstructure:"region_id"` 44 SizeID uint `mapstructure:"size_id"` 45 ImageID uint `mapstructure:"image_id"` 46 47 Region string `mapstructure:"region"` 48 Size string `mapstructure:"size"` 49 Image string `mapstructure:"image"` 50 51 PrivateNetworking bool `mapstructure:"private_networking"` 52 SnapshotName string `mapstructure:"snapshot_name"` 53 DropletName string `mapstructure:"droplet_name"` 54 SSHUsername string `mapstructure:"ssh_username"` 55 SSHPort uint `mapstructure:"ssh_port"` 56 57 RawSSHTimeout string `mapstructure:"ssh_timeout"` 58 RawStateTimeout string `mapstructure:"state_timeout"` 59 60 // These are unexported since they're set by other fields 61 // being set. 62 sshTimeout time.Duration 63 stateTimeout time.Duration 64 65 tpl *packer.ConfigTemplate 66 } 67 68 type Builder struct { 69 config config 70 runner multistep.Runner 71 } 72 73 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 74 md, err := common.DecodeConfig(&b.config, raws...) 75 if err != nil { 76 return nil, err 77 } 78 79 b.config.tpl, err = packer.NewConfigTemplate() 80 if err != nil { 81 return nil, err 82 } 83 b.config.tpl.UserVars = b.config.PackerUserVars 84 85 // Accumulate any errors 86 errs := common.CheckUnusedConfig(md) 87 88 // Optional configuration with defaults 89 if b.config.APIKey == "" { 90 // Default to environment variable for api_key, if it exists 91 b.config.APIKey = os.Getenv("DIGITALOCEAN_API_KEY") 92 } 93 94 if b.config.ClientID == "" { 95 // Default to environment variable for client_id, if it exists 96 b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID") 97 } 98 99 if b.config.APIURL == "" { 100 // Default to environment variable for api_url, if it exists 101 b.config.APIURL = os.Getenv("DIGITALOCEAN_API_URL") 102 } 103 104 if b.config.Region == "" { 105 if b.config.RegionID != 0 { 106 b.config.Region = fmt.Sprintf("%v", b.config.RegionID) 107 } else { 108 b.config.Region = DefaultRegion 109 } 110 } 111 112 if b.config.Size == "" { 113 if b.config.SizeID != 0 { 114 b.config.Size = fmt.Sprintf("%v", b.config.SizeID) 115 } else { 116 b.config.Size = DefaultSize 117 } 118 } 119 120 if b.config.Image == "" { 121 if b.config.ImageID != 0 { 122 b.config.Image = fmt.Sprintf("%v", b.config.ImageID) 123 } else { 124 b.config.Image = DefaultImage 125 } 126 } 127 128 if b.config.SnapshotName == "" { 129 // Default to packer-{{ unix timestamp (utc) }} 130 b.config.SnapshotName = "packer-{{timestamp}}" 131 } 132 133 if b.config.DropletName == "" { 134 // Default to packer-[time-ordered-uuid] 135 b.config.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) 136 } 137 138 if b.config.SSHUsername == "" { 139 // Default to "root". You can override this if your 140 // SourceImage has a different user account then the DO default 141 b.config.SSHUsername = "root" 142 } 143 144 if b.config.SSHPort == 0 { 145 // Default to port 22 per DO default 146 b.config.SSHPort = 22 147 } 148 149 if b.config.RawSSHTimeout == "" { 150 // Default to 1 minute timeouts 151 b.config.RawSSHTimeout = "1m" 152 } 153 154 if b.config.RawStateTimeout == "" { 155 // Default to 6 minute timeouts waiting for 156 // desired state. i.e waiting for droplet to become active 157 b.config.RawStateTimeout = "6m" 158 } 159 160 templates := map[string]*string{ 161 "region": &b.config.Region, 162 "size": &b.config.Size, 163 "image": &b.config.Image, 164 "client_id": &b.config.ClientID, 165 "api_key": &b.config.APIKey, 166 "api_url": &b.config.APIURL, 167 "snapshot_name": &b.config.SnapshotName, 168 "droplet_name": &b.config.DropletName, 169 "ssh_username": &b.config.SSHUsername, 170 "ssh_timeout": &b.config.RawSSHTimeout, 171 "state_timeout": &b.config.RawStateTimeout, 172 } 173 174 for n, ptr := range templates { 175 var err error 176 *ptr, err = b.config.tpl.Process(*ptr, nil) 177 if err != nil { 178 errs = packer.MultiErrorAppend( 179 errs, fmt.Errorf("Error processing %s: %s", n, err)) 180 } 181 } 182 183 // Required configurations that will display errors if not set 184 if b.config.ClientID == "" { 185 errs = packer.MultiErrorAppend( 186 errs, errors.New("a client_id must be specified")) 187 } 188 189 if b.config.APIURL == "" { 190 b.config.APIURL = "https://api.digitalocean.com" 191 } 192 193 if b.config.APIKey == "" { 194 errs = packer.MultiErrorAppend( 195 errs, errors.New("an api_key must be specified")) 196 } 197 198 sshTimeout, err := time.ParseDuration(b.config.RawSSHTimeout) 199 if err != nil { 200 errs = packer.MultiErrorAppend( 201 errs, fmt.Errorf("Failed parsing ssh_timeout: %s", err)) 202 } 203 b.config.sshTimeout = sshTimeout 204 205 stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout) 206 if err != nil { 207 errs = packer.MultiErrorAppend( 208 errs, fmt.Errorf("Failed parsing state_timeout: %s", err)) 209 } 210 b.config.stateTimeout = stateTimeout 211 212 if errs != nil && len(errs.Errors) > 0 { 213 return nil, errs 214 } 215 216 common.ScrubConfig(b.config, b.config.ClientID, b.config.APIKey) 217 return nil, nil 218 } 219 220 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 221 // Initialize the DO API client 222 client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey, b.config.APIURL) 223 224 // Set up the state 225 state := new(multistep.BasicStateBag) 226 state.Put("config", b.config) 227 state.Put("client", client) 228 state.Put("hook", hook) 229 state.Put("ui", ui) 230 231 // Build the steps 232 steps := []multistep.Step{ 233 new(stepCreateSSHKey), 234 new(stepCreateDroplet), 235 new(stepDropletInfo), 236 &common.StepConnectSSH{ 237 SSHAddress: sshAddress, 238 SSHConfig: sshConfig, 239 SSHWaitTimeout: 5 * time.Minute, 240 }, 241 new(common.StepProvision), 242 new(stepShutdown), 243 new(stepPowerOff), 244 new(stepSnapshot), 245 } 246 247 // Run the steps 248 if b.config.PackerDebug { 249 b.runner = &multistep.DebugRunner{ 250 Steps: steps, 251 PauseFn: common.MultistepDebugFn(ui), 252 } 253 } else { 254 b.runner = &multistep.BasicRunner{Steps: steps} 255 } 256 257 b.runner.Run(state) 258 259 // If there was an error, return that 260 if rawErr, ok := state.GetOk("error"); ok { 261 return nil, rawErr.(error) 262 } 263 264 if _, ok := state.GetOk("snapshot_name"); !ok { 265 log.Println("Failed to find snapshot_name in state. Bug?") 266 return nil, nil 267 } 268 269 sregion := state.Get("region") 270 271 var region string 272 273 if sregion != nil { 274 region = sregion.(string) 275 } else { 276 region = fmt.Sprintf("%v", state.Get("region_id").(uint)) 277 } 278 279 found_region, err := client.Region(region) 280 281 if err != nil { 282 return nil, err 283 } 284 285 artifact := &Artifact{ 286 snapshotName: state.Get("snapshot_name").(string), 287 snapshotId: state.Get("snapshot_image_id").(uint), 288 regionName: found_region.Name, 289 client: client, 290 } 291 292 return artifact, nil 293 } 294 295 func (b *Builder) Cancel() { 296 if b.runner != nil { 297 log.Println("Cancelling the step runner...") 298 b.runner.Cancel() 299 } 300 }