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  }