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