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