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