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  }