packer-cn/post-processor/alicloud-import/post-processor.go

366 lines
12 KiB
Go

package alicloudimport
import (
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
packercommon "github.com/denverdino/aliyungo/common"
"github.com/denverdino/aliyungo/ecs"
"github.com/denverdino/aliyungo/ram"
packerecs "github.com/hashicorp/packer/builder/alicloud/ecs"
"github.com/hashicorp/packer/common"
"github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer"
"github.com/hashicorp/packer/template/interpolate"
)
const (
BuilderId = "packer.post-processor.alicloud-import"
OSSSuffix = "oss-"
RAWFileFormat = "raw"
VHDFileFormat = "vhd"
BUSINESSINFO = "packer"
AliyunECSImageImportDefaultRolePolicy = `{
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {
"Service": [
"ecs.aliyuncs.com"
]
}
}
],
"Version": "1"
}`
)
// Configuration of this post processor
type Config struct {
common.PackerConfig `mapstructure:",squash"`
packerecs.Config `mapstructure:",squash"`
// Variables specific to this post processor
OSSBucket string `mapstructure:"oss_bucket_name"`
OSSKey string `mapstructure:"oss_key_name"`
SkipClean bool `mapstructure:"skip_clean"`
Tags map[string]string `mapstructure:"tags"`
AlicloudImageName string `mapstructure:"image_name"`
AlicloudImageVersion string `mapstructure:"image_version"`
AlicloudImageDescription string `mapstructure:"image_description"`
AlicloudImageShareAccounts []string `mapstructure:"image_share_account"`
AlicloudImageDestinationRegions []string `mapstructure:"image_copy_regions"`
OSType string `mapstructure:"image_os_type"`
Platform string `mapstructure:"image_platform"`
Architecture string `mapstructure:"image_architecture"`
Size string `mapstructure:"image_system_size"`
Format string `mapstructure:"format"`
AlicloudImageForceDetele bool `mapstructure:"image_force_delete"`
ctx interpolate.Context
}
type PostProcessor struct {
config Config
DiskDeviceMapping []ecs.DiskDeviceMapping
}
// Entry point for configuration parsing when we've defined
func (p *PostProcessor) Configure(raws ...interface{}) error {
err := config.Decode(&p.config, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &p.config.ctx,
InterpolateFilter: &interpolate.RenderFilter{
Exclude: []string{
"oss_key_name",
},
},
}, raws...)
if err != nil {
return err
}
errs := new(packer.MultiError)
// Check and render oss_key_name
if err = interpolate.Validate(p.config.OSSKey, &p.config.ctx); err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Error parsing oss_key_name template: %s", err))
}
// Check we have alicloud access variables defined somewhere
errs = packer.MultiErrorAppend(errs, p.config.AlicloudAccessConfig.Prepare(&p.config.ctx)...)
// define all our required parameters
templates := map[string]*string{
"oss_bucket_name": &p.config.OSSBucket,
}
// Check out required params are defined
for key, ptr := range templates {
if *ptr == "" {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("%s must be set", key))
}
}
// Anything which flagged return back up the stack
if len(errs.Errors) > 0 {
return errs
}
log.Println(common.ScrubConfig(p.config, p.config.AlicloudAccessKey, p.config.AlicloudSecretKey))
return nil
}
func (p *PostProcessor) PostProcess(ui packer.Ui, artifact packer.Artifact) (packer.Artifact, bool, error) {
var err error
// Render this key since we didn't in the configure phase
p.config.OSSKey, err = interpolate.Render(p.config.OSSKey, &p.config.ctx)
if err != nil {
return nil, false, fmt.Errorf("Error rendering oss_key_name template: %s", err)
}
if p.config.OSSKey == "" {
p.config.OSSKey = "Packer_" + strconv.Itoa(time.Now().Nanosecond())
}
log.Printf("Rendered oss_key_name as %s", p.config.OSSKey)
log.Println("Looking for RAW or VHD in artifact")
// Locate the files output from the builder
source := ""
for _, path := range artifact.Files() {
if strings.HasSuffix(path, VHDFileFormat) || strings.HasSuffix(path, RAWFileFormat) {
source = path
break
}
}
// Hope we found something useful
if source == "" {
return nil, false, fmt.Errorf("No vhd or raw file found in artifact from builder")
}
ecsClient, err := p.config.AlicloudAccessConfig.Client()
if err != nil {
return nil, false, fmt.Errorf("Failed to connect alicloud ecs %s", err)
}
ecsClient.SetBusinessInfo(BUSINESSINFO)
images, _, err := ecsClient.DescribeImages(&ecs.DescribeImagesArgs{
RegionId: packercommon.Region(p.config.AlicloudRegion),
ImageName: p.config.AlicloudImageName,
})
if err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
if len(images) > 0 && !p.config.AlicloudImageForceDetele {
return nil, false, fmt.Errorf("Duplicated image exists, please delete the existing images " +
"or set the 'image_force_delete' value as true")
}
// Set up the OSS client
log.Println("Creating OSS Client")
client, err := oss.New(getEndPonit(p.config.AlicloudRegion), p.config.AlicloudAccessKey,
p.config.AlicloudSecretKey)
if err != nil {
return nil, false, fmt.Errorf("Creating oss connection failed: %s", err)
}
bucket, err := queryOrCreateBucket(p.config.OSSBucket, client)
if err != nil {
return nil, false, fmt.Errorf("Failed to query or create bucket %s: %s", p.config.OSSBucket, err)
}
if err != nil {
return nil, false, fmt.Errorf("Failed to open %s: %s", source, err)
}
err = bucket.PutObjectFromFile(p.config.OSSKey, source)
if err != nil {
return nil, false, fmt.Errorf("Failed to upload image %s: %s", source, err)
}
if len(images) > 0 && p.config.AlicloudImageForceDetele {
if err = ecsClient.DeleteImage(packercommon.Region(p.config.AlicloudRegion),
images[0].ImageId); err != nil {
return nil, false, fmt.Errorf("Delete duplicated image %s failed", images[0].ImageName)
}
}
diskDeviceMapping := ecs.DiskDeviceMapping{
Size: p.config.Size,
Format: p.config.Format,
OSSBucket: p.config.OSSBucket,
OSSObject: p.config.OSSKey,
}
imageImageArgs := &ecs.ImportImageArgs{
RegionId: packercommon.Region(p.config.AlicloudRegion),
ImageName: p.config.AlicloudImageName,
ImageVersion: p.config.AlicloudImageVersion,
Description: p.config.AlicloudImageDescription,
Architecture: p.config.Architecture,
OSType: p.config.OSType,
Platform: p.config.Platform,
}
imageImageArgs.DiskDeviceMappings.DiskDeviceMapping = []ecs.DiskDeviceMapping{
diskDeviceMapping,
}
imageId, err := ecsClient.ImportImage(imageImageArgs)
if err != nil {
e, _ := err.(*packercommon.Error)
if e.Code == "NoSetRoletoECSServiceAcount" {
ramClient := ram.NewClient(p.config.AlicloudAccessKey, p.config.AlicloudSecretKey)
roleResponse, err := ramClient.GetRole(ram.RoleQueryRequest{
RoleName: "AliyunECSImageImportDefaultRole",
})
if err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
if roleResponse.Role.RoleId == "" {
if _, err = ramClient.CreateRole(ram.RoleRequest{
RoleName: "AliyunECSImageImportDefaultRole",
AssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy,
}); err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{
PolicyRequest: ram.PolicyRequest{
PolicyName: "AliyunECSImageImportRolePolicy",
PolicyType: "System",
},
RoleName: "AliyunECSImageImportDefaultRole",
}); err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
} else {
policyListResponse, err := ramClient.ListPoliciesForRole(ram.RoleQueryRequest{
RoleName: "AliyunECSImageImportDefaultRole",
})
if err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
isAliyunECSImageImportRolePolicyNotExit := true
for _, policy := range policyListResponse.Policies.Policy {
if policy.PolicyName == "AliyunECSImageImportRolePolicy" &&
policy.PolicyType == "System" {
isAliyunECSImageImportRolePolicyNotExit = false
break
}
}
if isAliyunECSImageImportRolePolicyNotExit {
if _, err := ramClient.AttachPolicyToRole(ram.AttachPolicyToRoleRequest{
PolicyRequest: ram.PolicyRequest{
PolicyName: "AliyunECSImageImportRolePolicy",
PolicyType: "System",
},
RoleName: "AliyunECSImageImportDefaultRole",
}); err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
}
if _, err = ramClient.UpdateRole(
ram.UpdateRoleRequest{
RoleName: "AliyunECSImageImportDefaultRole",
NewAssumeRolePolicyDocument: AliyunECSImageImportDefaultRolePolicy,
}); err != nil {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
}
for i := 10; i > 0; i = i - 1 {
imageId, err = ecsClient.ImportImage(imageImageArgs)
if err != nil {
e, _ = err.(*packercommon.Error)
if e.Code == "NoSetRoletoECSServiceAcount" {
time.Sleep(5 * time.Second)
continue
} else if e.Code == "ImageIsImporting" ||
e.Code == "InvalidImageName.Duplicated" {
break
}
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
break
}
} else {
return nil, false, fmt.Errorf("Failed to start import from %s/%s: %s",
getEndPonit(p.config.OSSBucket), p.config.OSSKey, err)
}
}
err = ecsClient.WaitForImageReady(packercommon.Region(p.config.AlicloudRegion),
imageId, packerecs.ALICLOUD_DEFAULT_LONG_TIMEOUT)
// Add the reported Alicloud image ID to the artifact list
log.Printf("Importing created alicloud image ID %s in region %s Finished.", imageId, p.config.AlicloudRegion)
artifact = &packerecs.Artifact{
AlicloudImages: map[string]string{
p.config.AlicloudRegion: imageId,
},
BuilderIdValue: BuilderId,
Client: ecsClient,
}
if !p.config.SkipClean {
ui.Message(fmt.Sprintf("Deleting import source %s/%s/%s",
getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey))
if err = bucket.DeleteObject(p.config.OSSKey); err != nil {
return nil, false, fmt.Errorf("Failed to delete %s/%s/%s: %s",
getEndPonit(p.config.AlicloudRegion), p.config.OSSBucket, p.config.OSSKey, err)
}
}
return artifact, false, nil
}
func queryOrCreateBucket(bucketName string, client *oss.Client) (*oss.Bucket, error) {
isExist, err := client.IsBucketExist(bucketName)
if err != nil {
return nil, err
}
if !isExist {
err = client.CreateBucket(bucketName)
if err != nil {
return nil, err
}
}
bucket, err := client.Bucket(bucketName)
if err != nil {
return nil, err
}
return bucket, nil
}
func getEndPonit(region string) string {
return "https://" + GetOSSRegion(region) + ".aliyuncs.com"
}
func GetOSSRegion(region string) string {
if strings.HasPrefix(region, OSSSuffix) {
return region
}
return OSSSuffix + region
}
func GetECSRegion(region string) string {
if strings.HasPrefix(region, OSSSuffix) {
return strings.TrimSuffix(region, OSSSuffix)
}
return region
}