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 }