github.com/raghuse92/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  }