github.com/sneal/packer@v0.5.2/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 // The unique id for the builder 19 const BuilderId = "pearkes.digitalocean" 20 21 // Configuration tells the builder the credentials 22 // to use while communicating with DO and describes the image 23 // you are creating 24 type config struct { 25 common.PackerConfig `mapstructure:",squash"` 26 27 ClientID string `mapstructure:"client_id"` 28 APIKey string `mapstructure:"api_key"` 29 RegionID uint `mapstructure:"region_id"` 30 SizeID uint `mapstructure:"size_id"` 31 ImageID uint `mapstructure:"image_id"` 32 33 PrivateNetworking bool `mapstructure:"private_networking"` 34 SnapshotName string `mapstructure:"snapshot_name"` 35 DropletName string `mapstructure:"droplet_name"` 36 SSHUsername string `mapstructure:"ssh_username"` 37 SSHPort uint `mapstructure:"ssh_port"` 38 39 RawSSHTimeout string `mapstructure:"ssh_timeout"` 40 RawStateTimeout string `mapstructure:"state_timeout"` 41 42 // These are unexported since they're set by other fields 43 // being set. 44 sshTimeout time.Duration 45 stateTimeout time.Duration 46 47 tpl *packer.ConfigTemplate 48 } 49 50 type Builder struct { 51 config config 52 runner multistep.Runner 53 } 54 55 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 56 md, err := common.DecodeConfig(&b.config, raws...) 57 if err != nil { 58 return nil, err 59 } 60 61 b.config.tpl, err = packer.NewConfigTemplate() 62 if err != nil { 63 return nil, err 64 } 65 b.config.tpl.UserVars = b.config.PackerUserVars 66 67 // Accumulate any errors 68 errs := common.CheckUnusedConfig(md) 69 70 // Optional configuration with defaults 71 if b.config.APIKey == "" { 72 // Default to environment variable for api_key, if it exists 73 b.config.APIKey = os.Getenv("DIGITALOCEAN_API_KEY") 74 } 75 76 if b.config.ClientID == "" { 77 // Default to environment variable for client_id, if it exists 78 b.config.ClientID = os.Getenv("DIGITALOCEAN_CLIENT_ID") 79 } 80 81 if b.config.RegionID == 0 { 82 // Default to Region "New York" 83 b.config.RegionID = 1 84 } 85 86 if b.config.SizeID == 0 { 87 // Default to 512mb, the smallest droplet size 88 b.config.SizeID = 66 89 } 90 91 if b.config.ImageID == 0 { 92 // Default to base image "Ubuntu 12.04.3 x64 Server (id: 1505447)" 93 b.config.ImageID = 1505447 94 } 95 96 if b.config.SnapshotName == "" { 97 // Default to packer-{{ unix timestamp (utc) }} 98 b.config.SnapshotName = "packer-{{timestamp}}" 99 } 100 101 if b.config.DropletName == "" { 102 // Default to packer-[time-ordered-uuid] 103 b.config.DropletName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) 104 } 105 106 if b.config.SSHUsername == "" { 107 // Default to "root". You can override this if your 108 // SourceImage has a different user account then the DO default 109 b.config.SSHUsername = "root" 110 } 111 112 if b.config.SSHPort == 0 { 113 // Default to port 22 per DO default 114 b.config.SSHPort = 22 115 } 116 117 if b.config.RawSSHTimeout == "" { 118 // Default to 1 minute timeouts 119 b.config.RawSSHTimeout = "1m" 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 "droplet_name": &b.config.DropletName, 133 "ssh_username": &b.config.SSHUsername, 134 "ssh_timeout": &b.config.RawSSHTimeout, 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 stateTimeout, err := time.ParseDuration(b.config.RawStateTimeout) 166 if err != nil { 167 errs = packer.MultiErrorAppend( 168 errs, fmt.Errorf("Failed parsing state_timeout: %s", err)) 169 } 170 b.config.stateTimeout = stateTimeout 171 172 if errs != nil && len(errs.Errors) > 0 { 173 return nil, errs 174 } 175 176 common.ScrubConfig(b.config, b.config.ClientID, b.config.APIKey) 177 return nil, nil 178 } 179 180 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 181 // Initialize the DO API client 182 client := DigitalOceanClient{}.New(b.config.ClientID, b.config.APIKey) 183 184 // Set up the state 185 state := new(multistep.BasicStateBag) 186 state.Put("config", b.config) 187 state.Put("client", client) 188 state.Put("hook", hook) 189 state.Put("ui", ui) 190 191 // Build the steps 192 steps := []multistep.Step{ 193 new(stepCreateSSHKey), 194 new(stepCreateDroplet), 195 new(stepDropletInfo), 196 &common.StepConnectSSH{ 197 SSHAddress: sshAddress, 198 SSHConfig: sshConfig, 199 SSHWaitTimeout: 5 * time.Minute, 200 }, 201 new(common.StepProvision), 202 new(stepShutdown), 203 new(stepPowerOff), 204 new(stepSnapshot), 205 } 206 207 // Run the steps 208 if b.config.PackerDebug { 209 b.runner = &multistep.DebugRunner{ 210 Steps: steps, 211 PauseFn: common.MultistepDebugFn(ui), 212 } 213 } else { 214 b.runner = &multistep.BasicRunner{Steps: steps} 215 } 216 217 b.runner.Run(state) 218 219 // If there was an error, return that 220 if rawErr, ok := state.GetOk("error"); ok { 221 return nil, rawErr.(error) 222 } 223 224 if _, ok := state.GetOk("snapshot_name"); !ok { 225 log.Println("Failed to find snapshot_name in state. Bug?") 226 return nil, nil 227 } 228 229 region_id := state.Get("region_id").(uint) 230 231 regionName, err := client.RegionName(region_id) 232 if err != nil { 233 return nil, err 234 } 235 236 artifact := &Artifact{ 237 snapshotName: state.Get("snapshot_name").(string), 238 snapshotId: state.Get("snapshot_image_id").(uint), 239 regionId: region_id, 240 regionName: regionName, 241 client: client, 242 } 243 244 return artifact, nil 245 } 246 247 func (b *Builder) Cancel() { 248 if b.runner != nil { 249 log.Println("Cancelling the step runner...") 250 b.runner.Cancel() 251 } 252 }