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  }