github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/post-processor/alicloud-import/post-processor.go (about) 1 package alicloudimport 2 3 import ( 4 "fmt" 5 "log" 6 "strconv" 7 "strings" 8 "time" 9 10 "github.com/aliyun/aliyun-oss-go-sdk/oss" 11 packercommon "github.com/denverdino/aliyungo/common" 12 "github.com/denverdino/aliyungo/ecs" 13 "github.com/denverdino/aliyungo/ram" 14 packerecs "github.com/hashicorp/packer/builder/alicloud/ecs" 15 "github.com/hashicorp/packer/common" 16 "github.com/hashicorp/packer/helper/config" 17 "github.com/hashicorp/packer/packer" 18 "github.com/hashicorp/packer/template/interpolate" 19 ) 20 21 const ( 22 BuilderId = "packer.post-processor.alicloud-import" 23 OSSSuffix = "oss-" 24 RAWFileFormat = "raw" 25 VHDFileFormat = "vhd" 26 BUSINESSINFO = "packer" 27 AliyunECSImageImportDefaultRolePolicy = `{ 28 "Statement": [ 29 { 30 "Action": "sts:AssumeRole", 31 "Effect": "Allow", 32 "Principal": { 33 "Service": [ 34 "ecs.aliyuncs.com" 35 ] 36 } 37 } 38 ], 39 "Version": "1" 40 }` 41 ) 42 43 // Configuration of this post processor 44 type Config struct { 45 common.PackerConfig `mapstructure:",squash"` 46 packerecs.Config `mapstructure:",squash"` 47 48 // Variables specific to this post processor 49 OSSBucket string `mapstructure:"oss_bucket_name"` 50 OSSKey string `mapstructure:"oss_key_name"` 51 SkipClean bool `mapstructure:"skip_clean"` 52 Tags map[string]string `mapstructure:"tags"` 53 AlicloudImageName string `mapstructure:"image_name"` 54 AlicloudImageVersion string `mapstructure:"image_version"` 55 AlicloudImageDescription string `mapstructure:"image_description"` 56 AlicloudImageShareAccounts []string `mapstructure:"image_share_account"` 57 AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"` 58 OSType string `mapstructure:"image_os_type"` 59 Platform string `mapstructure:"image_platform"` 60 Architecture string `mapstructure:"image_architecture"` 61 Size string `mapstructure:"image_system_size"` 62 Format string `mapstructure:"format"` 63 AlicloudImageForceDetele bool `mapstructure:"image_force_delete"` 64 65 ctx interpolate.Context 66 } 67 68 type PostProcessor struct { 69 config Config 70 DiskDeviceMapping []ecs.DiskDeviceMapping 71 } 72 73 // Entry point for configuration parsing when we've defined 74 func (p *PostProcessor) Configure(raws ...interface{}) error { 75 err := config.Decode(&p.config, &config.DecodeOpts{ 76 Interpolate: true, 77 InterpolateContext: &p.config.ctx, 78 InterpolateFilter: &interpolate.RenderFilter{ 79 Exclude: []string{ 80 "oss_key_name", 81 }, 82 }, 83 }, raws...) 84 if err != nil { 85 return err 86 } 87 88 errs := new(packer.MultiError) 89 90 // Check and render oss_key_name 91 if err = interpolate.Validate(p.config.OSSKey, &p.config.ctx); err != nil { 92 errs = packer.MultiErrorAppend( 93 errs, fmt.Errorf("Error parsing oss_key_name template: %s", err)) 94 } 95 96 // Check we have alicloud access variables defined somewhere 97 errs = packer.MultiErrorAppend(errs, p.config.AlicloudAccessConfig.Prepare(&p.config.ctx)...) 98 99 // define all our required parameters 100 templates := map[string]*string{ 101 "oss_bucket_name": &p.config.OSSBucket, 102 } 103 // Check out required params are defined 104 for key, ptr := range templates { 105 if *ptr == "" { 106 errs = packer.MultiErrorAppend( 107 errs, fmt.Errorf("%s must be set", key)) 108 } 109 } 110 111 // Anything which flagged return back up the stack 112 if len(errs.Errors) > 0 { 113 return errs 114 } 115 116 log.Println(common.ScrubConfig(p.config, p.config.AlicloudAccessKey, p.config.AlicloudSecretKey)) 117 return nil 118 } 119 120 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 121 var err error 122 123 // Render this key since we didn't in the configure phase 124 p.config.OSSKey, err = interpolate.Render(p.config.OSSKey, &p.config.ctx) 125 if err != nil { 126 return nil, false, fmt.Errorf("Error rendering oss_key_name template: %s", err) 127 } 128 if p.config.OSSKey == "" { 129 p.config.OSSKey = "Packer_" + strconv.Itoa(time.Now().Nanosecond()) 130 } 131 log.Printf("Rendered oss_key_name as %s", p.config.OSSKey) 132 133 log.Println("Looking for RAW or VHD in artifact") 134 // Locate the files output from the builder 135 source := "" 136 for _, path := range artifact.Files() { 137 if strings.HasSuffix(path, VHDFileFormat) || strings.HasSuffix(path, RAWFileFormat) { 138 source = path 139 break 140 } 141 } 142 143 // Hope we found something useful 144 if source == "" { 145 return nil, false, fmt.Errorf("No vhd or raw file found in artifact from builder") 146 } 147 148 ecsClient, err := p.config.AlicloudAccessConfig.Client() 149 if err != nil { 150 return nil, false, fmt.Errorf("Failed to connect alicloud ecs %s", err) 151 } 152 ecsClient.SetBusinessInfo(BUSINESSINFO) 153 154 images, _, err := ecsClient.DescribeImages(&ecs.DescribeImagesArgs{ 155 RegionId: packercommon.Region(p.config.AlicloudRegion), 156 ImageName: p.config.AlicloudImageName, 157 }) 158 if err != nil { 159 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 160 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 161 } 162 163 if len(images) > 0 && !p.config.AlicloudImageForceDetele { 164 return nil, false, fmt.Errorf("Duplicated image exists, please delete the existing images " + 165 "or set the 'image_force_delete' value as true") 166 } 167 168 // Set up the OSS client 169 log.Println("Creating OSS Client") 170 client, err := oss.New(getEndPonit(p.config.AlicloudRegion), p.config.AlicloudAccessKey, 171 p.config.AlicloudSecretKey) 172 if err != nil { 173 return nil, false, fmt.Errorf("Creating oss connection failed: %s", err) 174 } 175 bucket, err := queryOrCreateBucket(p.config.OSSBucket, client) 176 if err != nil { 177 return nil, false, fmt.Errorf("Failed to query or create bucket %s: %s", p.config.OSSBucket, err) 178 } 179 180 if err != nil { 181 return nil, false, fmt.Errorf("Failed to open %s: %s", source, err) 182 } 183 184 err = bucket.PutObjectFromFile(p.config.OSSKey, source) 185 if err != nil { 186 return nil, false, fmt.Errorf("Failed to upload image %s: %s", source, err) 187 } 188 if len(images) > 0 && p.config.AlicloudImageForceDetele { 189 if err = ecsClient.DeleteImage(packercommon.Region(p.config.AlicloudRegion), 190 images[0].ImageId); err != nil { 191 return nil, false, fmt.Errorf("Delete duplicated image %s failed", images[0].ImageName) 192 } 193 } 194 195 diskDeviceMapping := ecs.DiskDeviceMapping{ 196 Size: p.config.Size, 197 Format: p.config.Format, 198 OSSBucket: p.config.OSSBucket, 199 OSSObject: p.config.OSSKey, 200 } 201 imageImageArgs := &ecs.ImportImageArgs{ 202 RegionId: packercommon.Region(p.config.AlicloudRegion), 203 ImageName: p.config.AlicloudImageName, 204 ImageVersion: p.config.AlicloudImageVersion, 205 Description: p.config.AlicloudImageDescription, 206 Architecture: p.config.Architecture, 207 OSType: p.config.OSType, 208 Platform: p.config.Platform, 209 } 210 imageImageArgs.DiskDeviceMappings.DiskDeviceMapping = []ecs.DiskDeviceMapping{ 211 diskDeviceMapping, 212 } 213 imageId, err := ecsClient.ImportImage(imageImageArgs) 214 215 if err != nil { 216 e, _ := err.(*packercommon.Error) 217 if e.Code == "NoSetRoletoECSServiceAcount" { 218 ramClient := ram.NewClient(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey) 219 roleResponse, err := ramClient.GetRole(ram.RoleQueryRequest{ 220 RoleName: "AliyunECSImageImportDefaultRole", 221 }) 222 if err != nil { 223 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 224 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 225 } 226 if roleResponse.Role.RoleId == "" { 227 if _, err = ramClient.CreateRole(ram.RoleRequest{ 228 RoleName: "AliyunECSImageImportDefaultRole", 229 AssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy, 230 }); err != nil { 231 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 232 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 233 } 234 if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{ 235 PolicyRequest: ram.PolicyRequest{ 236 PolicyName: "AliyunECSImageImportRolePolicy", 237 PolicyType: "System", 238 }, 239 RoleName: "AliyunECSImageImportDefaultRole", 240 }); err != nil { 241 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 242 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 243 } 244 } else { 245 policyListResponse, err := ramClient.ListPoliciesForRole(ram.RoleQueryRequest{ 246 RoleName: "AliyunECSImageImportDefaultRole", 247 }) 248 if err != nil { 249 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 250 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 251 } 252 isAliyunECSImageImportRolePolicyNotExit := true 253 for _, policy := range policyListResponse.Policies.Policy { 254 if policy.PolicyName == "AliyunECSImageImportRolePolicy" && 255 policy.PolicyType == "System" { 256 isAliyunECSImageImportRolePolicyNotExit = false 257 break 258 } 259 } 260 if isAliyunECSImageImportRolePolicyNotExit { 261 if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{ 262 PolicyRequest: ram.PolicyRequest{ 263 PolicyName: "AliyunECSImageImportRolePolicy", 264 PolicyType: "System", 265 }, 266 RoleName: "AliyunECSImageImportDefaultRole", 267 }); err != nil { 268 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 269 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 270 } 271 } 272 if _, err = ramClient.UpdateRole( 273 ram.UpdateRoleRequest{ 274 RoleName: "AliyunECSImageImportDefaultRole", 275 NewAssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy, 276 }); err != nil { 277 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 278 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 279 } 280 } 281 for i := 10; i > 0; i = i - 1 { 282 imageId, err = ecsClient.ImportImage(imageImageArgs) 283 if err != nil { 284 e, _ = err.(*packercommon.Error) 285 if e.Code == "NoSetRoletoECSServiceAcount" { 286 time.Sleep(5 * time.Second) 287 continue 288 } else if e.Code == "ImageIsImporting" || 289 e.Code == "InvalidImageName.Duplicated" { 290 break 291 } 292 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 293 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 294 } 295 break 296 } 297 298 } else { 299 300 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 301 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 302 } 303 } 304 305 err = ecsClient.WaitForImageReady(packercommon.Region(p.config.AlicloudRegion), 306 imageId, packerecs.ALICLOUD_DEFAULT_LONG_TIMEOUT) 307 // Add the reported Alicloud image ID to the artifact list 308 log.Printf("Importing created alicloud image ID %s in region %s Finished.", imageId, p.config.AlicloudRegion) 309 artifact = &packerecs.Artifact{ 310 AlicloudImages: map[string]string{ 311 p.config.AlicloudRegion: imageId, 312 }, 313 BuilderIdValue: BuilderId, 314 Client: ecsClient, 315 } 316 317 if !p.config.SkipClean { 318 ui.Message(fmt.Sprintf("Deleting import source %s/%s/%s", 319 getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey)) 320 if err = bucket.DeleteObject(p.config.OSSKey); err != nil { 321 return nil, false, fmt.Errorf("Failed to delete %s/%s/%s: %s", 322 getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey, err) 323 } 324 } 325 326 return artifact, false, nil 327 } 328 329 func queryOrCreateBucket(bucketName string, client *oss.Client) (*oss.Bucket, error) { 330 isExist, err := client.IsBucketExist(bucketName) 331 if err != nil { 332 return nil, err 333 } 334 if !isExist { 335 err = client.CreateBucket(bucketName) 336 if err != nil { 337 return nil, err 338 } 339 } 340 bucket, err := client.Bucket(bucketName) 341 if err != nil { 342 return nil, err 343 } 344 return bucket, nil 345 346 } 347 348 func getEndPonit(region string) string { 349 return "https://" + GetOSSRegion(region) + ".aliyuncs.com" 350 } 351 352 func GetOSSRegion(region string) string { 353 if strings.HasPrefix(region, OSSSuffix) { 354 return region 355 } 356 return OSSSuffix + region 357 } 358 359 func GetECSRegion(region string) string { 360 if strings.HasPrefix(region, OSSSuffix) { 361 return strings.TrimSuffix(region, OSSSuffix) 362 } 363 return region 364 365 }