github.com/kimor79/packer@v0.8.7-0.20151221212622-d507b18eb4cf/builder/amazon/common/step_run_source_instance.go (about)

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