github.com/aclaygray/packer@v1.3.2/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 AlicloudImageForceDelete 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 packer.LogSecretFilter.Set(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey) 117 log.Println(p.config) 118 return nil 119 } 120 121 func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) { 122 var err error 123 124 // Render this key since we didn't in the configure phase 125 p.config.OSSKey, err = interpolate.Render(p.config.OSSKey, &p.config.ctx) 126 if err != nil { 127 return nil, false, fmt.Errorf("Error rendering oss_key_name template: %s", err) 128 } 129 if p.config.OSSKey == "" { 130 p.config.OSSKey = "Packer_" + strconv.Itoa(time.Now().Nanosecond()) 131 } 132 log.Printf("Rendered oss_key_name as %s", p.config.OSSKey) 133 134 log.Println("Looking for RAW or VHD in artifact") 135 // Locate the files output from the builder 136 source := "" 137 for _, path := range artifact.Files() { 138 if strings.HasSuffix(path, VHDFileFormat) || strings.HasSuffix(path, RAWFileFormat) { 139 source = path 140 break 141 } 142 } 143 144 // Hope we found something useful 145 if source == "" { 146 return nil, false, fmt.Errorf("No vhd or raw file found in artifact from builder") 147 } 148 149 ecsClient, err := p.config.AlicloudAccessConfig.Client() 150 if err != nil { 151 return nil, false, fmt.Errorf("Failed to connect alicloud ecs %s", err) 152 } 153 ecsClient.SetBusinessInfo(BUSINESSINFO) 154 155 images, _, err := ecsClient.DescribeImages(&ecs.DescribeImagesArgs{ 156 RegionId: packercommon.Region(p.config.AlicloudRegion), 157 ImageName: p.config.AlicloudImageName, 158 }) 159 if err != nil { 160 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 161 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 162 } 163 164 if len(images) > 0 && !p.config.AlicloudImageForceDelete { 165 return nil, false, fmt.Errorf("Duplicated image exists, please delete the existing images " + 166 "or set the 'image_force_delete' value as true") 167 } 168 169 // Set up the OSS client 170 log.Println("Creating OSS Client") 171 client, err := oss.New(getEndPonit(p.config.AlicloudRegion), p.config.AlicloudAccessKey, 172 p.config.AlicloudSecretKey) 173 if err != nil { 174 return nil, false, fmt.Errorf("Creating oss connection failed: %s", err) 175 } 176 bucket, err := queryOrCreateBucket(p.config.OSSBucket, client) 177 if err != nil { 178 return nil, false, fmt.Errorf("Failed to query or create bucket %s: %s", p.config.OSSBucket, err) 179 } 180 181 if err != nil { 182 return nil, false, fmt.Errorf("Failed to open %s: %s", source, err) 183 } 184 185 err = bucket.PutObjectFromFile(p.config.OSSKey, source) 186 if err != nil { 187 return nil, false, fmt.Errorf("Failed to upload image %s: %s", source, err) 188 } 189 if len(images) > 0 && p.config.AlicloudImageForceDelete { 190 if err = ecsClient.DeleteImage(packercommon.Region(p.config.AlicloudRegion), 191 images[0].ImageId); err != nil { 192 return nil, false, fmt.Errorf("Delete duplicated image %s failed", images[0].ImageName) 193 } 194 } 195 196 diskDeviceMapping := ecs.DiskDeviceMapping{ 197 Size: p.config.Size, 198 Format: p.config.Format, 199 OSSBucket: p.config.OSSBucket, 200 OSSObject: p.config.OSSKey, 201 } 202 imageImageArgs := &ecs.ImportImageArgs{ 203 RegionId: packercommon.Region(p.config.AlicloudRegion), 204 ImageName: p.config.AlicloudImageName, 205 ImageVersion: p.config.AlicloudImageVersion, 206 Description: p.config.AlicloudImageDescription, 207 Architecture: p.config.Architecture, 208 OSType: p.config.OSType, 209 Platform: p.config.Platform, 210 } 211 imageImageArgs.DiskDeviceMappings.DiskDeviceMapping = []ecs.DiskDeviceMapping{ 212 diskDeviceMapping, 213 } 214 imageId, err := ecsClient.ImportImage(imageImageArgs) 215 216 if err != nil { 217 e, _ := err.(*packercommon.Error) 218 if e.Code == "NoSetRoletoECSServiceAccount" { 219 ramClient := ram.NewClient(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey) 220 roleResponse, err := ramClient.GetRole(ram.RoleQueryRequest{ 221 RoleName: "AliyunECSImageImportDefaultRole", 222 }) 223 if err != nil { 224 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 225 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 226 } 227 if roleResponse.Role.RoleId == "" { 228 if _, err = ramClient.CreateRole(ram.RoleRequest{ 229 RoleName: "AliyunECSImageImportDefaultRole", 230 AssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy, 231 }); err != nil { 232 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 233 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 234 } 235 if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{ 236 PolicyRequest: ram.PolicyRequest{ 237 PolicyName: "AliyunECSImageImportRolePolicy", 238 PolicyType: "System", 239 }, 240 RoleName: "AliyunECSImageImportDefaultRole", 241 }); err != nil { 242 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 243 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 244 } 245 } else { 246 policyListResponse, err := ramClient.ListPoliciesForRole(ram.RoleQueryRequest{ 247 RoleName: "AliyunECSImageImportDefaultRole", 248 }) 249 if err != nil { 250 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 251 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 252 } 253 isAliyunECSImageImportRolePolicyNotExit := true 254 for _, policy := range policyListResponse.Policies.Policy { 255 if policy.PolicyName == "AliyunECSImageImportRolePolicy" && 256 policy.PolicyType == "System" { 257 isAliyunECSImageImportRolePolicyNotExit = false 258 break 259 } 260 } 261 if isAliyunECSImageImportRolePolicyNotExit { 262 if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{ 263 PolicyRequest: ram.PolicyRequest{ 264 PolicyName: "AliyunECSImageImportRolePolicy", 265 PolicyType: "System", 266 }, 267 RoleName: "AliyunECSImageImportDefaultRole", 268 }); err != nil { 269 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 270 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 271 } 272 } 273 if _, err = ramClient.UpdateRole( 274 ram.UpdateRoleRequest{ 275 RoleName: "AliyunECSImageImportDefaultRole", 276 NewAssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy, 277 }); err != nil { 278 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 279 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 280 } 281 } 282 for i := 10; i > 0; i = i - 1 { 283 imageId, err = ecsClient.ImportImage(imageImageArgs) 284 if err != nil { 285 e, _ = err.(*packercommon.Error) 286 if e.Code == "NoSetRoletoECSServiceAccount" { 287 time.Sleep(5 * time.Second) 288 continue 289 } else if e.Code == "ImageIsImporting" || 290 e.Code == "InvalidImageName.Duplicated" { 291 break 292 } 293 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 294 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 295 } 296 break 297 } 298 299 } else { 300 301 return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s", 302 getEndPonit(p.config.OSSBucket), p.config.OSSKey, err) 303 } 304 } 305 306 err = ecsClient.WaitForImageReady(packercommon.Region(p.config.AlicloudRegion), 307 imageId, packerecs.ALICLOUD_DEFAULT_LONG_TIMEOUT) 308 // Add the reported Alicloud image ID to the artifact list 309 log.Printf("Importing created alicloud image ID %s in region %s Finished.", imageId, p.config.AlicloudRegion) 310 artifact = &packerecs.Artifact{ 311 AlicloudImages: map[string]string{ 312 p.config.AlicloudRegion: imageId, 313 }, 314 BuilderIdValue: BuilderId, 315 Client: ecsClient, 316 } 317 318 if !p.config.SkipClean { 319 ui.Message(fmt.Sprintf("Deleting import source %s/%s/%s", 320 getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey)) 321 if err = bucket.DeleteObject(p.config.OSSKey); err != nil { 322 return nil, false, fmt.Errorf("Failed to delete %s/%s/%s: %s", 323 getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey, err) 324 } 325 } 326 327 return artifact, false, nil 328 } 329 330 func queryOrCreateBucket(bucketName string, client *oss.Client) (*oss.Bucket, error) { 331 isExist, err := client.IsBucketExist(bucketName) 332 if err != nil { 333 return nil, err 334 } 335 if !isExist { 336 err = client.CreateBucket(bucketName) 337 if err != nil { 338 return nil, err 339 } 340 } 341 bucket, err := client.Bucket(bucketName) 342 if err != nil { 343 return nil, err 344 } 345 return bucket, nil 346 347 } 348 349 func getEndPonit(region string) string { 350 return "https://" + GetOSSRegion(region) + ".aliyuncs.com" 351 } 352 353 func GetOSSRegion(region string) string { 354 if strings.HasPrefix(region, OSSSuffix) { 355 return region 356 } 357 return OSSSuffix + region 358 } 359 360 func GetECSRegion(region string) string { 361 if strings.HasPrefix(region, OSSSuffix) { 362 return strings.TrimSuffix(region, OSSSuffix) 363 } 364 return region 365 366 }