github.com/rahart/packer@v0.12.2-0.20161229105310-282bb6ad370f/post-processor/amazon-import/post-processor.go (about)

     1  package amazonimport
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"strings"
     8  
     9  	"github.com/aws/aws-sdk-go/aws"
    10  	"github.com/aws/aws-sdk-go/aws/session"
    11  	"github.com/aws/aws-sdk-go/service/ec2"
    12  	"github.com/aws/aws-sdk-go/service/s3"
    13  	"github.com/aws/aws-sdk-go/service/s3/s3manager"
    14  	awscommon "github.com/mitchellh/packer/builder/amazon/common"
    15  	"github.com/mitchellh/packer/common"
    16  	"github.com/mitchellh/packer/helper/config"
    17  	"github.com/mitchellh/packer/packer"
    18  	"github.com/mitchellh/packer/template/interpolate"
    19  )
    20  
    21  const BuilderId = "packer.post-processor.amazon-import"
    22  
    23  // Configuration of this post processor
    24  type Config struct {
    25  	common.PackerConfig    `mapstructure:",squash"`
    26  	awscommon.AccessConfig `mapstructure:",squash"`
    27  
    28  	// Variables specific to this post processor
    29  	S3Bucket  string            `mapstructure:"s3_bucket_name"`
    30  	S3Key     string            `mapstructure:"s3_key_name"`
    31  	SkipClean bool              `mapstructure:"skip_clean"`
    32  	Tags      map[string]string `mapstructure:"tags"`
    33  	Name      string            `mapstructure:"ami_name"`
    34  
    35  	ctx interpolate.Context
    36  }
    37  
    38  type PostProcessor struct {
    39  	config Config
    40  }
    41  
    42  // Entry point for configuration parisng when we've defined
    43  func (p *PostProcessor) Configure(raws ...interface{}) error {
    44  	p.config.ctx.Funcs = awscommon.TemplateFuncs
    45  	err := config.Decode(&p.config, &config.DecodeOpts{
    46  		Interpolate:        true,
    47  		InterpolateContext: &p.config.ctx,
    48  		InterpolateFilter: &interpolate.RenderFilter{
    49  			Exclude: []string{
    50  				"s3_key_name",
    51  			},
    52  		},
    53  	}, raws...)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	// Set defaults
    59  	if p.config.S3Key == "" {
    60  		p.config.S3Key = "packer-import-{{timestamp}}.ova"
    61  	}
    62  
    63  	errs := new(packer.MultiError)
    64  
    65  	// Check and render s3_key_name
    66  	if err = interpolate.Validate(p.config.S3Key, &p.config.ctx); err != nil {
    67  		errs = packer.MultiErrorAppend(
    68  			errs, fmt.Errorf("Error parsing s3_key_name template: %s", err))
    69  	}
    70  
    71  	// Check we have AWS access variables defined somewhere
    72  	errs = packer.MultiErrorAppend(errs, p.config.AccessConfig.Prepare(&p.config.ctx)...)
    73  
    74  	// define all our required paramaters
    75  	templates := map[string]*string{
    76  		"s3_bucket_name": &p.config.S3Bucket,
    77  	}
    78  	// Check out required params are defined
    79  	for key, ptr := range templates {
    80  		if *ptr == "" {
    81  			errs = packer.MultiErrorAppend(
    82  				errs, fmt.Errorf("%s must be set", key))
    83  		}
    84  	}
    85  
    86  	// Anything which flagged return back up the stack
    87  	if len(errs.Errors) > 0 {
    88  		return errs
    89  	}
    90  
    91  	log.Println(common.ScrubConfig(p.config, p.config.AccessKey, p.config.SecretKey))
    92  	return nil
    93  }
    94  
    95  func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
    96  	var err error
    97  
    98  	config, err := p.config.Config()
    99  	if err != nil {
   100  		return nil, false, err
   101  	}
   102  
   103  	// Render this key since we didn't in the configure phase
   104  	p.config.S3Key, err = interpolate.Render(p.config.S3Key, &p.config.ctx)
   105  	if err != nil {
   106  		return nil, false, fmt.Errorf("Error rendering s3_key_name template: %s", err)
   107  	}
   108  	log.Printf("Rendered s3_key_name as %s", p.config.S3Key)
   109  
   110  	log.Println("Looking for OVA in artifact")
   111  	// Locate the files output from the builder
   112  	source := ""
   113  	for _, path := range artifact.Files() {
   114  		if strings.HasSuffix(path, ".ova") {
   115  			source = path
   116  			break
   117  		}
   118  	}
   119  
   120  	// Hope we found something useful
   121  	if source == "" {
   122  		return nil, false, fmt.Errorf("No OVA file found in artifact from builder")
   123  	}
   124  
   125  	// Set up the AWS session
   126  	log.Println("Creating AWS session")
   127  	session, err := session.NewSession(config)
   128  	if err != nil {
   129  		return nil, false, err
   130  	}
   131  
   132  	// open the source file
   133  	log.Printf("Opening file %s to upload", source)
   134  	file, err := os.Open(source)
   135  	if err != nil {
   136  		return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
   137  	}
   138  
   139  	ui.Message(fmt.Sprintf("Uploading %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   140  
   141  	// Copy the OVA file into the S3 bucket specified
   142  	uploader := s3manager.NewUploader(session)
   143  	_, err = uploader.Upload(&s3manager.UploadInput{
   144  		Body:   file,
   145  		Bucket: &p.config.S3Bucket,
   146  		Key:    &p.config.S3Key,
   147  	})
   148  	if err != nil {
   149  		return nil, false, fmt.Errorf("Failed to upload %s: %s", source, err)
   150  	}
   151  
   152  	// May as well stop holding this open now
   153  	file.Close()
   154  
   155  	ui.Message(fmt.Sprintf("Completed upload of %s to s3://%s/%s", source, p.config.S3Bucket, p.config.S3Key))
   156  
   157  	// Call EC2 image import process
   158  	log.Printf("Calling EC2 to import from s3://%s/%s", p.config.S3Bucket, p.config.S3Key)
   159  
   160  	ec2conn := ec2.New(session)
   161  	import_start, err := ec2conn.ImportImage(&ec2.ImportImageInput{
   162  		DiskContainers: []*ec2.ImageDiskContainer{
   163  			{
   164  				UserBucket: &ec2.UserBucket{
   165  					S3Bucket: &p.config.S3Bucket,
   166  					S3Key:    &p.config.S3Key,
   167  				},
   168  			},
   169  		},
   170  	})
   171  
   172  	if err != nil {
   173  		return nil, false, fmt.Errorf("Failed to start import from s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   174  	}
   175  
   176  	ui.Message(fmt.Sprintf("Started import of s3://%s/%s, task id %s", p.config.S3Bucket, p.config.S3Key, *import_start.ImportTaskId))
   177  
   178  	// Wait for import process to complete, this takess a while
   179  	ui.Message(fmt.Sprintf("Waiting for task %s to complete (may take a while)", *import_start.ImportTaskId))
   180  
   181  	stateChange := awscommon.StateChangeConf{
   182  		Pending: []string{"pending", "active"},
   183  		Refresh: awscommon.ImportImageRefreshFunc(ec2conn, *import_start.ImportTaskId),
   184  		Target:  "completed",
   185  	}
   186  
   187  	// Actually do the wait for state change
   188  	// We ignore errors out of this and check job state in AWS API
   189  	awscommon.WaitForState(&stateChange)
   190  
   191  	// Retrieve what the outcome was for the import task
   192  	import_result, err := ec2conn.DescribeImportImageTasks(&ec2.DescribeImportImageTasksInput{
   193  		ImportTaskIds: []*string{
   194  			import_start.ImportTaskId,
   195  		},
   196  	})
   197  
   198  	if err != nil {
   199  		return nil, false, fmt.Errorf("Failed to find import task %s: %s", *import_start.ImportTaskId, err)
   200  	}
   201  
   202  	// Check it was actually completed
   203  	if *import_result.ImportImageTasks[0].Status != "completed" {
   204  		// The most useful error message is from the job itself
   205  		return nil, false, fmt.Errorf("Import task %s failed: %s", *import_start.ImportTaskId, *import_result.ImportImageTasks[0].StatusMessage)
   206  	}
   207  
   208  	ui.Message(fmt.Sprintf("Import task %s complete", *import_start.ImportTaskId))
   209  
   210  	// Pull AMI ID out of the completed job
   211  	createdami := *import_result.ImportImageTasks[0].ImageId
   212  
   213  	if p.config.Name != "" {
   214  
   215  		ui.Message(fmt.Sprintf("Starting rename of AMI (%s)", createdami))
   216  
   217  		resp, err := ec2conn.CopyImage(&ec2.CopyImageInput{
   218  			Name:          &p.config.Name,
   219  			SourceImageId: &createdami,
   220  			SourceRegion:  config.Region,
   221  		})
   222  
   223  		if err != nil {
   224  			return nil, false, fmt.Errorf("Error Copying AMI (%s): %s", createdami, err)
   225  		}
   226  
   227  		ui.Message(fmt.Sprintf("Waiting for AMI rename to complete (may take a while)"))
   228  
   229  		stateChange := awscommon.StateChangeConf{
   230  			Pending: []string{"pending"},
   231  			Target:  "available",
   232  			Refresh: awscommon.AMIStateRefreshFunc(ec2conn, *resp.ImageId),
   233  		}
   234  
   235  		if _, err := awscommon.WaitForState(&stateChange); err != nil {
   236  			return nil, false, fmt.Errorf("Error waiting for AMI (%s): %s", *resp.ImageId, err)
   237  		}
   238  
   239  		ec2conn.DeregisterImage(&ec2.DeregisterImageInput{
   240  			ImageId: &createdami,
   241  		})
   242  
   243  		if err != nil {
   244  			return nil, false, fmt.Errorf("Error deregistering existing AMI: %s", err)
   245  		}
   246  
   247  		ui.Message(fmt.Sprintf("AMI rename completed"))
   248  
   249  		createdami = *resp.ImageId
   250  	}
   251  
   252  	// If we have tags, then apply them now to both the AMI and snaps
   253  	// created by the import
   254  	if len(p.config.Tags) > 0 {
   255  		var ec2Tags []*ec2.Tag
   256  
   257  		log.Printf("Repacking tags into AWS format")
   258  
   259  		for key, value := range p.config.Tags {
   260  			ui.Message(fmt.Sprintf("Adding tag \"%s\": \"%s\"", key, value))
   261  			ec2Tags = append(ec2Tags, &ec2.Tag{
   262  				Key:   aws.String(key),
   263  				Value: aws.String(value),
   264  			})
   265  		}
   266  
   267  		resourceIds := []*string{&createdami}
   268  
   269  		log.Printf("Getting details of %s", createdami)
   270  
   271  		imageResp, err := ec2conn.DescribeImages(&ec2.DescribeImagesInput{
   272  			ImageIds: resourceIds,
   273  		})
   274  
   275  		if err != nil {
   276  			return nil, false, fmt.Errorf("Failed to retrieve details for AMI %s: %s", createdami, err)
   277  		}
   278  
   279  		if len(imageResp.Images) == 0 {
   280  			return nil, false, fmt.Errorf("AMI %s has no images", createdami)
   281  		}
   282  
   283  		image := imageResp.Images[0]
   284  
   285  		log.Printf("Walking block device mappings for %s to find snapshots", createdami)
   286  
   287  		for _, device := range image.BlockDeviceMappings {
   288  			if device.Ebs != nil && device.Ebs.SnapshotId != nil {
   289  				ui.Message(fmt.Sprintf("Tagging snapshot %s", *device.Ebs.SnapshotId))
   290  				resourceIds = append(resourceIds, device.Ebs.SnapshotId)
   291  			}
   292  		}
   293  
   294  		ui.Message(fmt.Sprintf("Tagging AMI %s", createdami))
   295  
   296  		_, err = ec2conn.CreateTags(&ec2.CreateTagsInput{
   297  			Resources: resourceIds,
   298  			Tags:      ec2Tags,
   299  		})
   300  
   301  		if err != nil {
   302  			return nil, false, fmt.Errorf("Failed to add tags to resources %#v: %s", resourceIds, err)
   303  		}
   304  
   305  	}
   306  
   307  	// Add the reported AMI ID to the artifact list
   308  	log.Printf("Adding created AMI ID %s in region %s to output artifacts", createdami, *config.Region)
   309  	artifact = &awscommon.Artifact{
   310  		Amis: map[string]string{
   311  			*config.Region: createdami,
   312  		},
   313  		BuilderIdValue: BuilderId,
   314  		Conn:           ec2conn,
   315  	}
   316  
   317  	if !p.config.SkipClean {
   318  		ui.Message(fmt.Sprintf("Deleting import source s3://%s/%s", p.config.S3Bucket, p.config.S3Key))
   319  		s3conn := s3.New(session)
   320  		_, err = s3conn.DeleteObject(&s3.DeleteObjectInput{
   321  			Bucket: &p.config.S3Bucket,
   322  			Key:    &p.config.S3Key,
   323  		})
   324  		if err != nil {
   325  			return nil, false, fmt.Errorf("Failed to delete s3://%s/%s: %s", p.config.S3Bucket, p.config.S3Key, err)
   326  		}
   327  	}
   328  
   329  	return artifact, false, nil
   330  }