github.com/askholme/packer@v0.7.2-0.20140924152349-70d9566a6852/builder/amazon/common/step_run_source_instance.go (about)

     1  package common
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"strconv"
     8  	"time"
     9  
    10  	"github.com/mitchellh/goamz/ec2"
    11  	"github.com/mitchellh/multistep"
    12  	"github.com/mitchellh/packer/packer"
    13  )
    14  
    15  type StepRunSourceInstance struct {
    16  	AssociatePublicIpAddress bool
    17  	AvailabilityZone         string
    18  	BlockDevices             BlockDevices
    19  	Debug                    bool
    20  	ExpectedRootDevice       string
    21  	InstanceType             string
    22  	IamInstanceProfile       string
    23  	SourceAMI                string
    24  	SpotPrice                string
    25  	SpotPriceProduct         string
    26  	SubnetId                 string
    27  	Tags                     map[string]string
    28  	UserData                 string
    29  	UserDataFile             string
    30  
    31  	instance    *ec2.Instance
    32  	spotRequest *ec2.SpotRequestResult
    33  }
    34  
    35  func (s *StepRunSourceInstance) Run(state multistep.StateBag) multistep.StepAction {
    36  	ec2conn := state.Get("ec2").(*ec2.EC2)
    37  	keyName := state.Get("keyPair").(string)
    38  	securityGroupIds := state.Get("securityGroupIds").([]string)
    39  	ui := state.Get("ui").(packer.Ui)
    40  
    41  	userData := s.UserData
    42  	if s.UserDataFile != "" {
    43  		contents, err := ioutil.ReadFile(s.UserDataFile)
    44  		if err != nil {
    45  			state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
    46  			return multistep.ActionHalt
    47  		}
    48  
    49  		userData = string(contents)
    50  	}
    51  
    52  	securityGroups := make([]ec2.SecurityGroup, len(securityGroupIds))
    53  	for n, securityGroupId := range securityGroupIds {
    54  		securityGroups[n] = ec2.SecurityGroup{Id: securityGroupId}
    55  	}
    56  
    57  	ui.Say("Launching a source AWS instance...")
    58  	imageResp, err := ec2conn.Images([]string{s.SourceAMI}, ec2.NewFilter())
    59  	if err != nil {
    60  		state.Put("error", fmt.Errorf("There was a problem with the source AMI: %s", err))
    61  		return multistep.ActionHalt
    62  	}
    63  
    64  	if len(imageResp.Images) != 1 {
    65  		state.Put("error", fmt.Errorf("The source AMI '%s' could not be found.", s.SourceAMI))
    66  		return multistep.ActionHalt
    67  	}
    68  
    69  	if s.ExpectedRootDevice != "" && imageResp.Images[0].RootDeviceType != s.ExpectedRootDevice {
    70  		state.Put("error", fmt.Errorf(
    71  			"The provided source AMI has an invalid root device type.\n"+
    72  				"Expected '%s', got '%s'.",
    73  			s.ExpectedRootDevice, imageResp.Images[0].RootDeviceType))
    74  		return multistep.ActionHalt
    75  	}
    76  
    77  	spotPrice := s.SpotPrice
    78  	if spotPrice == "auto" {
    79  		ui.Message(fmt.Sprintf(
    80  			"Finding spot price for %s %s...",
    81  			s.SpotPriceProduct, s.InstanceType))
    82  
    83  		// Detect the spot price
    84  		startTime := time.Now().Add(-1 * time.Hour)
    85  		resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistory{
    86  			InstanceType:       []string{s.InstanceType},
    87  			ProductDescription: []string{s.SpotPriceProduct},
    88  			AvailabilityZone:   s.AvailabilityZone,
    89  			StartTime:          startTime,
    90  		})
    91  		if err != nil {
    92  			err := fmt.Errorf("Error finding spot price: %s", err)
    93  			state.Put("error", err)
    94  			ui.Error(err.Error())
    95  			return multistep.ActionHalt
    96  		}
    97  
    98  		var price float64
    99  		for _, history := range resp.History {
   100  			log.Printf("[INFO] Candidate spot price: %s", history.SpotPrice)
   101  			current, err := strconv.ParseFloat(history.SpotPrice, 64)
   102  			if err != nil {
   103  				log.Printf("[ERR] Error parsing spot price: %s", err)
   104  				continue
   105  			}
   106  			if price == 0 || current < price {
   107  				price = current
   108  			}
   109  		}
   110  		if price == 0 {
   111  			err := fmt.Errorf("No candidate spot prices found!")
   112  			state.Put("error", err)
   113  			ui.Error(err.Error())
   114  			return multistep.ActionHalt
   115  		}
   116  
   117  		spotPrice = strconv.FormatFloat(price, 'f', -1, 64)
   118  	}
   119  
   120  	var instanceId string
   121  
   122  	if spotPrice == "" {
   123  		runOpts := &ec2.RunInstances{
   124  			KeyName:                  keyName,
   125  			ImageId:                  s.SourceAMI,
   126  			InstanceType:             s.InstanceType,
   127  			UserData:                 []byte(userData),
   128  			MinCount:                 0,
   129  			MaxCount:                 0,
   130  			SecurityGroups:           securityGroups,
   131  			IamInstanceProfile:       s.IamInstanceProfile,
   132  			SubnetId:                 s.SubnetId,
   133  			AssociatePublicIpAddress: s.AssociatePublicIpAddress,
   134  			BlockDevices:             s.BlockDevices.BuildLaunchDevices(),
   135  			AvailZone:                s.AvailabilityZone,
   136  		}
   137  		runResp, err := ec2conn.RunInstances(runOpts)
   138  		if err != nil {
   139  			err := fmt.Errorf("Error launching source instance: %s", err)
   140  			state.Put("error", err)
   141  			ui.Error(err.Error())
   142  			return multistep.ActionHalt
   143  		}
   144  		instanceId = runResp.Instances[0].InstanceId
   145  	} else {
   146  		ui.Message(fmt.Sprintf(
   147  			"Requesting spot instance '%s' for: %s",
   148  			s.InstanceType, spotPrice))
   149  
   150  		runOpts := &ec2.RequestSpotInstances{
   151  			SpotPrice:                spotPrice,
   152  			KeyName:                  keyName,
   153  			ImageId:                  s.SourceAMI,
   154  			InstanceType:             s.InstanceType,
   155  			UserData:                 []byte(userData),
   156  			SecurityGroups:           securityGroups,
   157  			IamInstanceProfile:       s.IamInstanceProfile,
   158  			SubnetId:                 s.SubnetId,
   159  			AssociatePublicIpAddress: s.AssociatePublicIpAddress,
   160  			BlockDevices:             s.BlockDevices.BuildLaunchDevices(),
   161  			AvailZone:                s.AvailabilityZone,
   162  		}
   163  		runSpotResp, err := ec2conn.RequestSpotInstances(runOpts)
   164  		if err != nil {
   165  			err := fmt.Errorf("Error launching source spot instance: %s", err)
   166  			state.Put("error", err)
   167  			ui.Error(err.Error())
   168  			return multistep.ActionHalt
   169  		}
   170  
   171  		s.spotRequest = &runSpotResp.SpotRequestResults[0]
   172  
   173  		spotRequestId := s.spotRequest.SpotRequestId
   174  		ui.Message(fmt.Sprintf("Waiting for spot request (%s) to become active...", spotRequestId))
   175  		stateChange := StateChangeConf{
   176  			Pending:   []string{"open"},
   177  			Target:    "active",
   178  			Refresh:   SpotRequestStateRefreshFunc(ec2conn, spotRequestId),
   179  			StepState: state,
   180  		}
   181  		_, err = WaitForState(&stateChange)
   182  		if err != nil {
   183  			err := fmt.Errorf("Error waiting for spot request (%s) to become ready: %s", spotRequestId, err)
   184  			state.Put("error", err)
   185  			ui.Error(err.Error())
   186  			return multistep.ActionHalt
   187  		}
   188  		spotResp, err := ec2conn.DescribeSpotRequests([]string{spotRequestId}, nil)
   189  		if err != nil {
   190  			err := fmt.Errorf("Error finding spot request (%s): %s", spotRequestId, err)
   191  			state.Put("error", err)
   192  			ui.Error(err.Error())
   193  			return multistep.ActionHalt
   194  		}
   195  		instanceId = spotResp.SpotRequestResults[0].InstanceId
   196  	}
   197  
   198  	instanceResp, err := ec2conn.Instances([]string{instanceId}, nil)
   199  	if err != nil {
   200  		err := fmt.Errorf("Error finding source instance (%s): %s", instanceId, err)
   201  		state.Put("error", err)
   202  		ui.Error(err.Error())
   203  		return multistep.ActionHalt
   204  	}
   205  	s.instance = &instanceResp.Reservations[0].Instances[0]
   206  	ui.Message(fmt.Sprintf("Instance ID: %s", s.instance.InstanceId))
   207  
   208  	ui.Say(fmt.Sprintf("Waiting for instance (%s) to become ready...", s.instance.InstanceId))
   209  	stateChange := StateChangeConf{
   210  		Pending:   []string{"pending"},
   211  		Target:    "running",
   212  		Refresh:   InstanceStateRefreshFunc(ec2conn, s.instance),
   213  		StepState: state,
   214  	}
   215  	latestInstance, err := WaitForState(&stateChange)
   216  	if err != nil {
   217  		err := fmt.Errorf("Error waiting for instance (%s) to become ready: %s", s.instance.InstanceId, err)
   218  		state.Put("error", err)
   219  		ui.Error(err.Error())
   220  		return multistep.ActionHalt
   221  	}
   222  
   223  	s.instance = latestInstance.(*ec2.Instance)
   224  
   225  	ec2Tags := make([]ec2.Tag, 1, len(s.Tags)+1)
   226  	ec2Tags[0] = ec2.Tag{"Name", "Packer Builder"}
   227  	for k, v := range s.Tags {
   228  		ec2Tags = append(ec2Tags, ec2.Tag{k, v})
   229  	}
   230  
   231  	_, err = ec2conn.CreateTags([]string{s.instance.InstanceId}, ec2Tags)
   232  	if err != nil {
   233  		ui.Message(
   234  			fmt.Sprintf("Failed to tag a Name on the builder instance: %s", err))
   235  	}
   236  
   237  	if s.Debug {
   238  		if s.instance.DNSName != "" {
   239  			ui.Message(fmt.Sprintf("Public DNS: %s", s.instance.DNSName))
   240  		}
   241  
   242  		if s.instance.PublicIpAddress != "" {
   243  			ui.Message(fmt.Sprintf("Public IP: %s", s.instance.PublicIpAddress))
   244  		}
   245  
   246  		if s.instance.PrivateIpAddress != "" {
   247  			ui.Message(fmt.Sprintf("Private IP: %s", s.instance.PrivateIpAddress))
   248  		}
   249  	}
   250  
   251  	state.Put("instance", s.instance)
   252  
   253  	return multistep.ActionContinue
   254  }
   255  
   256  func (s *StepRunSourceInstance) Cleanup(state multistep.StateBag) {
   257  
   258  	ec2conn := state.Get("ec2").(*ec2.EC2)
   259  	ui := state.Get("ui").(packer.Ui)
   260  
   261  	// Cancel the spot request if it exists
   262  	if s.spotRequest != nil {
   263  		ui.Say("Cancelling the spot request...")
   264  		if _, err := ec2conn.CancelSpotRequests([]string{s.spotRequest.SpotRequestId}); err != nil {
   265  			ui.Error(fmt.Sprintf("Error cancelling the spot request, may still be around: %s", err))
   266  			return
   267  		}
   268  		stateChange := StateChangeConf{
   269  			Pending: []string{"active", "open"},
   270  			Refresh: SpotRequestStateRefreshFunc(ec2conn, s.spotRequest.SpotRequestId),
   271  			Target:  "cancelled",
   272  		}
   273  
   274  		WaitForState(&stateChange)
   275  
   276  	}
   277  
   278  	// Terminate the source instance if it exists
   279  	if s.instance != nil {
   280  
   281  		ui.Say("Terminating the source AWS instance...")
   282  		if _, err := ec2conn.TerminateInstances([]string{s.instance.InstanceId}); err != nil {
   283  			ui.Error(fmt.Sprintf("Error terminating instance, may still be around: %s", err))
   284  			return
   285  		}
   286  		stateChange := StateChangeConf{
   287  			Pending: []string{"pending", "running", "shutting-down", "stopped", "stopping"},
   288  			Refresh: InstanceStateRefreshFunc(ec2conn, s.instance),
   289  			Target:  "terminated",
   290  		}
   291  
   292  		WaitForState(&stateChange)
   293  	}
   294  }