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