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