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  }