parent
e666b60d16
commit
7655d2efb2
|
@ -0,0 +1,127 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
|
||||
)
|
||||
|
||||
type Region string
|
||||
|
||||
// below would be moved to tencentcloud sdk git repo
|
||||
const (
|
||||
Bangkok = Region("ap-bangkok")
|
||||
Beijing = Region("ap-beijing")
|
||||
Chengdu = Region("ap-chengdu")
|
||||
Chongqing = Region("ap-chongqing")
|
||||
Guangzhou = Region("ap-guangzhou")
|
||||
GuangzhouOpen = Region("ap-guangzhou-open")
|
||||
Hongkong = Region("ap-hongkong")
|
||||
Mumbai = Region("ap-mumbai")
|
||||
Seoul = Region("ap-seoul")
|
||||
Shanghai = Region("ap-shanghai")
|
||||
ShanghaiFsi = Region("ap-shanghai-fsi")
|
||||
ShenzhenFsi = Region("ap-shenzhen-fsi")
|
||||
Singapore = Region("ap-singapore")
|
||||
Tokyo = Region("ap-tokyo")
|
||||
Frankfurt = Region("eu-frankfurt")
|
||||
Moscow = Region("eu-moscow")
|
||||
Ashburn = Region("na-ashburn")
|
||||
Siliconvalley = Region("na-siliconvalley")
|
||||
Toronto = Region("na-toronto")
|
||||
)
|
||||
|
||||
var ValidRegions = []Region{
|
||||
Bangkok, Beijing, Chengdu, Chongqing, Guangzhou, GuangzhouOpen, Hongkong, Shanghai,
|
||||
ShanghaiFsi, ShenzhenFsi,
|
||||
Mumbai, Seoul, Singapore, Tokyo, Moscow,
|
||||
Frankfurt, Ashburn, Siliconvalley, Toronto,
|
||||
}
|
||||
|
||||
type TencentCloudAccessConfig struct {
|
||||
SecretId string `mapstructure:"secret_id"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Region string `mapstructure:"region"`
|
||||
Zone string `mapstructure:"zone"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
}
|
||||
|
||||
func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
|
||||
var (
|
||||
err error
|
||||
cvm_client *cvm.Client
|
||||
vpc_client *vpc.Client
|
||||
resp *cvm.DescribeZonesResponse
|
||||
)
|
||||
if err = cf.validateRegion(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
credential := common.NewCredential(
|
||||
cf.SecretId, cf.SecretKey)
|
||||
cpf := profile.NewClientProfile()
|
||||
if cvm_client, err = cvm.NewClient(credential, cf.Region, cpf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if vpc_client, err = vpc.NewClient(credential, cf.Region, cpf); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp, err = cvm_client.DescribeZones(nil); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if cf.Zone != "" {
|
||||
for _, zone := range resp.Response.ZoneSet {
|
||||
if cf.Zone == *zone.Zone {
|
||||
return cvm_client, vpc_client, nil
|
||||
}
|
||||
}
|
||||
return nil, nil, fmt.Errorf("unknown zone: %s", cf.Zone)
|
||||
} else {
|
||||
return nil, nil, fmt.Errorf("zone must be set")
|
||||
}
|
||||
}
|
||||
|
||||
func (cf *TencentCloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
if err := cf.Config(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if cf.Region == "" {
|
||||
errs = append(errs, fmt.Errorf("region must be set"))
|
||||
} else if !cf.SkipValidation {
|
||||
if err := cf.validateRegion(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cf *TencentCloudAccessConfig) Config() error {
|
||||
if cf.SecretId == "" {
|
||||
cf.SecretId = os.Getenv("TENCENTCLOUD_SECRET_ID")
|
||||
}
|
||||
if cf.SecretKey == "" {
|
||||
cf.SecretKey = os.Getenv("TENCENTCLOUD_SECRET_KEY")
|
||||
}
|
||||
if cf.SecretId == "" || cf.SecretKey == "" {
|
||||
return fmt.Errorf("TENCENTCLOUD_SECRET_ID and TENCENTCLOUD_SECRET_KEY must be set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cf *TencentCloudAccessConfig) validateRegion() error {
|
||||
for _, valid := range ValidRegions {
|
||||
if valid == Region(cf.Region) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown region: %s", cf.Region)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTencentCloudAccessConfig_Prepare(t *testing.T) {
|
||||
cf := TencentCloudAccessConfig{
|
||||
SecretId: "secret-id",
|
||||
SecretKey: "secret-key",
|
||||
}
|
||||
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should raise error: region not set")
|
||||
}
|
||||
|
||||
cf.Region = "ap-guangzhou"
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't raise error: %v", err)
|
||||
}
|
||||
|
||||
cf.Region = "unknown-region"
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should raise error: unknown region")
|
||||
}
|
||||
|
||||
cf.SkipValidation = true
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't raise error: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type Artifact struct {
|
||||
TencentCloudImages map[string]string
|
||||
BuilderIdValue string
|
||||
Client *cvm.Client
|
||||
}
|
||||
|
||||
func (a *Artifact) BuilderId() string {
|
||||
return a.BuilderIdValue
|
||||
}
|
||||
|
||||
func (*Artifact) Files() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Artifact) Id() string {
|
||||
parts := make([]string, 0, len(a.TencentCloudImages))
|
||||
for region, imageId := range a.TencentCloudImages {
|
||||
parts = append(parts, fmt.Sprintf("%s:%s", region, imageId))
|
||||
}
|
||||
|
||||
sort.Strings(parts)
|
||||
return strings.Join(parts, ",")
|
||||
}
|
||||
|
||||
func (a *Artifact) String() string {
|
||||
parts := make([]string, 0, len(a.TencentCloudImages))
|
||||
for region, imageId := range a.TencentCloudImages {
|
||||
parts = append(parts, fmt.Sprintf("%s: %s", region, imageId))
|
||||
}
|
||||
sort.Strings(parts)
|
||||
return fmt.Sprintf("Tencentcloud images(%s) were created:\n\n", strings.Join(parts, "\n"))
|
||||
}
|
||||
|
||||
func (a *Artifact) State(name string) interface{} {
|
||||
switch name {
|
||||
case "atlas.artifact.metadata":
|
||||
return a.stateAtlasMetadata()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) Destroy() error {
|
||||
errors := make([]error, 0)
|
||||
|
||||
for region, imageId := range a.TencentCloudImages {
|
||||
log.Printf("Delete tencentcloud image ID(%s) from region(%s)", imageId, region)
|
||||
|
||||
describeReq := cvm.NewDescribeImagesRequest()
|
||||
describeReq.ImageIds = []*string{&imageId}
|
||||
|
||||
describeResp, err := a.Client.DescribeImages(describeReq)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
if *describeResp.Response.TotalCount == 0 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"describe images failed, region(%s) ImageId(%s)", region, imageId))
|
||||
}
|
||||
|
||||
describeShareReq := cvm.NewDescribeImageSharePermissionRequest()
|
||||
describeShareReq.ImageId = &imageId
|
||||
|
||||
describeShareResp, err := a.Client.DescribeImageSharePermission(describeShareReq)
|
||||
var shareAccountIds []*string = nil
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
} else {
|
||||
for _, sharePermission := range describeShareResp.Response.SharePermissionSet {
|
||||
shareAccountIds = append(shareAccountIds, sharePermission.AccountId)
|
||||
}
|
||||
}
|
||||
|
||||
if shareAccountIds != nil && len(shareAccountIds) != 0 {
|
||||
cancelShareReq := cvm.NewModifyImageSharePermissionRequest()
|
||||
cancelShareReq.ImageId = &imageId
|
||||
cancelShareReq.AccountIds = shareAccountIds
|
||||
CANCEL := "CANCEL"
|
||||
cancelShareReq.Permission = &CANCEL
|
||||
_, err := a.Client.ModifyImageSharePermission(cancelShareReq)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
deleteReq := cvm.NewDeleteImagesRequest()
|
||||
deleteReq.ImageIds = []*string{&imageId}
|
||||
|
||||
_, err = a.Client.DeleteImages(deleteReq)
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errors) == 1 {
|
||||
return errors[0]
|
||||
} else if len(errors) > 1 {
|
||||
return &packer.MultiError{Errors: errors}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Artifact) stateAtlasMetadata() interface{} {
|
||||
metadata := make(map[string]string)
|
||||
for region, imageId := range a.TencentCloudImages {
|
||||
k := fmt.Sprintf("region.%s", region)
|
||||
metadata[k] = imageId
|
||||
}
|
||||
return metadata
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
const BuilderId = "tencent.cloud"
|
||||
|
||||
type Config struct {
|
||||
common.PackerConfig `mapstructure:",squash"`
|
||||
TencentCloudAccessConfig `mapstructure:",squash"`
|
||||
TencentCloudImageConfig `mapstructure:",squash"`
|
||||
TencentCloudRunConfig `mapstructure:",squash"`
|
||||
|
||||
ctx interpolate.Context
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
config Config
|
||||
runner multistep.Runner
|
||||
}
|
||||
|
||||
func (b *Builder) Prepare(raws ...interface{}) ([]string, error) {
|
||||
err := config.Decode(&b.config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
InterpolateContext: &b.config.ctx,
|
||||
InterpolateFilter: &interpolate.RenderFilter{
|
||||
Exclude: []string{
|
||||
"run_command",
|
||||
},
|
||||
},
|
||||
}, raws...)
|
||||
b.config.ctx.EnableEnv = true
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Accumulate any errors
|
||||
var errs *packer.MultiError
|
||||
errs = packer.MultiErrorAppend(errs, b.config.TencentCloudAccessConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.TencentCloudImageConfig.Prepare(&b.config.ctx)...)
|
||||
errs = packer.MultiErrorAppend(errs, b.config.TencentCloudRunConfig.Prepare(&b.config.ctx)...)
|
||||
|
||||
if errs != nil && len(errs.Errors) > 0 {
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
packer.LogSecretFilter.Set(b.config.SecretId, b.config.SecretKey)
|
||||
log.Println(b.config)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) {
|
||||
cvmClient, vpcClient, err := b.config.Client()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
state := new(multistep.BasicStateBag)
|
||||
state.Put("config", &b.config)
|
||||
state.Put("cvm_client", cvmClient)
|
||||
state.Put("vpc_client", vpcClient)
|
||||
state.Put("hook", hook)
|
||||
state.Put("ui", ui)
|
||||
var steps []multistep.Step
|
||||
|
||||
// Build the steps
|
||||
steps = []multistep.Step{
|
||||
&stepCheckSourceImage{b.config.SourceImageId},
|
||||
&stepConfigKeyPair{
|
||||
Debug: b.config.PackerDebug,
|
||||
Comm: &b.config.Comm,
|
||||
DebugKeyPath: fmt.Sprintf("cvm_%s.pem", b.config.PackerBuildName),
|
||||
},
|
||||
&stepConfigVPC{
|
||||
VpcId: b.config.VpcId,
|
||||
CidrBlock: b.config.CidrBlock,
|
||||
VpcName: b.config.VpcName,
|
||||
},
|
||||
&stepConfigSubnet{
|
||||
SubnetId: b.config.SubnetId,
|
||||
SubnetCidrBlock: b.config.SubnectCidrBlock,
|
||||
SubnetName: b.config.SubnetName,
|
||||
Zone: b.config.Zone,
|
||||
},
|
||||
&stepConfigSecurityGroup{
|
||||
SecurityGroupId: b.config.SecurityGroupId,
|
||||
SecurityGroupName: b.config.SecurityGroupName,
|
||||
Description: "a simple security group",
|
||||
},
|
||||
&stepRunInstance{
|
||||
InstanceType: b.config.InstanceType,
|
||||
UserData: b.config.UserData,
|
||||
UserDataFile: b.config.UserDataFile,
|
||||
ZoneId: b.config.Zone,
|
||||
InstanceName: b.config.InstanceName,
|
||||
DiskType: b.config.DiskType,
|
||||
DiskSize: b.config.DiskSize,
|
||||
HostName: b.config.HostName,
|
||||
InternetMaxBandwidthOut: b.config.InternetMaxBandwidthOut,
|
||||
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
|
||||
},
|
||||
&communicator.StepConnect{
|
||||
Config: &b.config.TencentCloudRunConfig.Comm,
|
||||
SSHConfig: b.config.TencentCloudRunConfig.Comm.SSHConfigFunc(),
|
||||
Host: SSHHost(b.config.AssociatePublicIpAddress),
|
||||
},
|
||||
&common.StepProvision{},
|
||||
&common.StepCleanupTempKeys{
|
||||
Comm: &b.config.TencentCloudRunConfig.Comm},
|
||||
&stepCreateImage{},
|
||||
&stepShareImage{b.config.ImageShareAccounts},
|
||||
&stepCopyImage{
|
||||
DesinationRegions: b.config.ImageCopyRegions,
|
||||
SourceRegion: b.config.Region,
|
||||
},
|
||||
}
|
||||
|
||||
b.runner = common.NewRunner(steps, b.config.PackerConfig, ui)
|
||||
b.runner.Run(state)
|
||||
|
||||
if rawErr, ok := state.GetOk("error"); ok {
|
||||
return nil, rawErr.(error)
|
||||
}
|
||||
|
||||
if _, ok := state.GetOk("image"); !ok {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
artifact := &Artifact{
|
||||
TencentCloudImages: state.Get("tencentcloudimages").(map[string]string),
|
||||
BuilderIdValue: BuilderId,
|
||||
Client: cvmClient,
|
||||
}
|
||||
return artifact, nil
|
||||
}
|
||||
|
||||
func (b *Builder) Cancel() {
|
||||
if b.runner != nil {
|
||||
log.Println("Cancelling the step runner...")
|
||||
b.runner.Cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
func CheckResourceIdFormat(resource string, id string) bool {
|
||||
regex := regexp.MustCompile(fmt.Sprintf("%s-[0-9a-z]{8}$", resource))
|
||||
if !regex.MatchString(id) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func MessageClean(state multistep.StateBag, module string) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if cancelled || halted {
|
||||
ui.Say(fmt.Sprintf("Deleting %s because of cancellation or error...", module))
|
||||
} else {
|
||||
ui.Say(fmt.Sprintf("Cleaning up '%s'", module))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const DefaultWaitForInterval = 5
|
||||
|
||||
func WaitForInstance(client *cvm.Client, instanceId string, status string, timeout int) error {
|
||||
req := cvm.NewDescribeInstancesRequest()
|
||||
req.InstanceIds = []*string{&instanceId}
|
||||
for {
|
||||
resp, err := client.DescribeInstances(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *resp.Response.TotalCount == 0 {
|
||||
return fmt.Errorf("instance(%s) not exist", instanceId)
|
||||
}
|
||||
if *resp.Response.InstanceSet[0].InstanceState == status {
|
||||
break
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return fmt.Errorf("wait instance(%s) status(%s) timeout", instanceId, status)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func WaitForImageReady(client *cvm.Client, imageName string, status string, timeout int) error {
|
||||
req := cvm.NewDescribeImagesRequest()
|
||||
FILTER_IMAGE_NAME := "image-name"
|
||||
req.Filters = []*cvm.Filter{
|
||||
{
|
||||
Name: &FILTER_IMAGE_NAME,
|
||||
Values: []*string{&imageName},
|
||||
},
|
||||
}
|
||||
for {
|
||||
resp, err := client.DescribeImages(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
find := false
|
||||
for _, image := range resp.Response.ImageSet {
|
||||
if *image.ImageName == imageName && *image.ImageState == status {
|
||||
find = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if find {
|
||||
break
|
||||
}
|
||||
time.Sleep(DefaultWaitForInterval * time.Second)
|
||||
timeout = timeout - DefaultWaitForInterval
|
||||
if timeout <= 0 {
|
||||
return fmt.Errorf("wait image(%s) ready timeout", imageName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SSHHost returns a function that can be given to the SSH communicator
|
||||
func SSHHost(pubilcIp bool) func(multistep.StateBag) (string, error) {
|
||||
return func(state multistep.StateBag) (string, error) {
|
||||
instance := state.Get("instance").(*cvm.Instance)
|
||||
if pubilcIp {
|
||||
return *instance.PublicIpAddresses[0], nil
|
||||
} else {
|
||||
return *instance.PrivateIpAddresses[0], nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
)
|
||||
|
||||
type TencentCloudImageConfig struct {
|
||||
ImageName string `mapstructure:"image_name"`
|
||||
ImageDescription string `mapstructure:"image_description"`
|
||||
Reboot bool `mapstructure:"reboot"`
|
||||
ForcePoweroff bool `mapstructure:"force_poweroff"`
|
||||
Sysprep bool `mapstructure:"sysprep"`
|
||||
ImageForceDelete bool `mapstructure:"image_force_delete"`
|
||||
ImageCopyRegions []string `mapstructure:"image_copy_regions"`
|
||||
ImageShareAccounts []string `mapstructure:"image_share_accounts"`
|
||||
SkipValidation bool `mapstructure:"skip_region_validation"`
|
||||
}
|
||||
|
||||
func (cf *TencentCloudImageConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
var errs []error
|
||||
cf.ForcePoweroff = true
|
||||
if cf.ImageName == "" {
|
||||
errs = append(errs, fmt.Errorf("image_name must be set"))
|
||||
} else if len(cf.ImageName) > 20 {
|
||||
errs = append(errs, fmt.Errorf("image_num length should not exceed 20 characters"))
|
||||
} else {
|
||||
regex := regexp.MustCompile("^[0-9a-zA-Z\\-]+$")
|
||||
if !regex.MatchString(cf.ImageName) {
|
||||
errs = append(errs, fmt.Errorf("image_name can only be composed of letters, numbers and minus sign"))
|
||||
}
|
||||
}
|
||||
|
||||
if len(cf.ImageDescription) > 60 {
|
||||
errs = append(errs, fmt.Errorf("image_description length should not exceed 60 characters"))
|
||||
}
|
||||
|
||||
if len(cf.ImageCopyRegions) > 0 {
|
||||
regionSet := make(map[string]struct{})
|
||||
regions := make([]string, 0, len(cf.ImageCopyRegions))
|
||||
|
||||
for _, region := range cf.ImageCopyRegions {
|
||||
if _, ok := regionSet[region]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
regionSet[region] = struct{}{}
|
||||
|
||||
if !cf.SkipValidation {
|
||||
if err := validRegion(region); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
regions = append(regions, region)
|
||||
}
|
||||
cf.ImageCopyRegions = regions
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validRegion(region string) error {
|
||||
for _, valid := range ValidRegions {
|
||||
if Region(region) == valid {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unknown region: %s", region)
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package cvm
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestTencentCloudImageConfig_Prepare(t *testing.T) {
|
||||
cf := &TencentCloudImageConfig{
|
||||
ImageName: "foo",
|
||||
}
|
||||
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %v", err)
|
||||
}
|
||||
|
||||
cf.ImageName = "foo:"
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
cf.ImageName = "foo"
|
||||
cf.ImageCopyRegions = []string{"ap-guangzhou", "ap-hongkong"}
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %v", err)
|
||||
}
|
||||
|
||||
cf.ImageCopyRegions = []string{"unknown"}
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
|
||||
cf.SkipValidation = true
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err:%v", err)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/packer/common/uuid"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/template/interpolate"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type TencentCloudRunConfig struct {
|
||||
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
|
||||
SourceImageId string `mapstructure:"source_image_id"`
|
||||
InstanceType string `mapstructure:"instance_type"`
|
||||
InstanceName string `mapstructure:"instance_name"`
|
||||
DiskType string `mapstructure:"disk_type"`
|
||||
DiskSize int64 `mapstructure:"disk_size"`
|
||||
VpcId string `mapstructure:"vpc_id"`
|
||||
VpcName string `mapstructure:"vpc_name"`
|
||||
VpcIp string `mapstructure:"vpc_ip"`
|
||||
SubnetId string `mapstructure:"subnet_id"`
|
||||
SubnetName string `mapstructure:"subnet_name"`
|
||||
CidrBlock string `mapstructure:"cidr_block"` // 10.0.0.0/16(default), 172.16.0.0/12, 192.168.0.0/16
|
||||
SubnectCidrBlock string `mapstructure:"subnect_cidr_block"`
|
||||
InternetChargeType string `mapstructure:"internet_charge_type"`
|
||||
InternetMaxBandwidthOut int64 `mapstructure:"internet_max_bandwidth_out"`
|
||||
SecurityGroupId string `mapstructure:"security_group_id"`
|
||||
SecurityGroupName string `mapstructure:"security_group_name"`
|
||||
UserData string `mapstructure:"user_data"`
|
||||
UserDataFile string `mapstructure:"user_data_file"`
|
||||
HostName string `mapstructure:"host_name"`
|
||||
|
||||
// Communicator settings
|
||||
Comm communicator.Config `mapstructure:",squash"`
|
||||
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
|
||||
}
|
||||
|
||||
var ValidCBSType = []string{
|
||||
"LOCAL_BASIC", "LOCAL_SSD", "CLOUD_BASIC", "CLOUD_SSD", "CLOUD_PREMIUM",
|
||||
}
|
||||
|
||||
func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error {
|
||||
if cf.Comm.SSHKeyPairName == "" && cf.Comm.SSHTemporaryKeyPairName == "" &&
|
||||
cf.Comm.SSHPrivateKeyFile == "" && cf.Comm.SSHPassword == "" && cf.Comm.WinRMPassword == "" {
|
||||
//tencentcloud support key pair name length max to 25
|
||||
cf.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()[:8])
|
||||
}
|
||||
|
||||
errs := cf.Comm.Prepare(ctx)
|
||||
if cf.SourceImageId == "" {
|
||||
errs = append(errs, errors.New("source_image_id must be specified"))
|
||||
}
|
||||
|
||||
if !CheckResourceIdFormat("img", cf.SourceImageId) {
|
||||
errs = append(errs, errors.New("source_image_id wrong format"))
|
||||
}
|
||||
|
||||
if cf.InstanceType == "" {
|
||||
errs = append(errs, errors.New("instance_type must be specified"))
|
||||
}
|
||||
|
||||
if cf.UserData != "" && cf.UserDataFile != "" {
|
||||
errs = append(errs, errors.New("only one of user_data or user_data_file can be specified"))
|
||||
} else if cf.UserDataFile != "" {
|
||||
if _, err := os.Stat(cf.UserDataFile); err != nil {
|
||||
errs = append(errs, errors.New("user_data_file not exist"))
|
||||
}
|
||||
}
|
||||
|
||||
if (cf.VpcId != "" || cf.CidrBlock != "") && cf.SubnetId == "" && cf.SubnectCidrBlock == "" {
|
||||
errs = append(errs, errors.New("if vpc cidr_block is specified, then "+
|
||||
"subnet_cidr_block must also be specified."))
|
||||
}
|
||||
|
||||
if cf.VpcId == "" {
|
||||
if cf.VpcName == "" {
|
||||
cf.VpcName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
if cf.CidrBlock == "" {
|
||||
cf.CidrBlock = "10.0.0.0/16"
|
||||
}
|
||||
if cf.SubnetId != "" {
|
||||
errs = append(errs, errors.New("can't set subnet_id without set vpc_id"))
|
||||
}
|
||||
}
|
||||
if cf.SubnetId == "" {
|
||||
if cf.SubnetName == "" {
|
||||
cf.SubnetName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
if cf.SubnectCidrBlock == "" {
|
||||
cf.SubnectCidrBlock = "10.0.8.0/24"
|
||||
}
|
||||
}
|
||||
|
||||
if cf.SecurityGroupId == "" && cf.SecurityGroupName == "" {
|
||||
cf.SecurityGroupName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if cf.DiskType != "" && !checkDiskType(cf.DiskType) {
|
||||
errs = append(errs, errors.New(fmt.Sprintf("specified disk_type(%s) is invalid", cf.DiskType)))
|
||||
} else if cf.DiskType == "" {
|
||||
cf.DiskType = "CLOUD_BASIC"
|
||||
}
|
||||
|
||||
if cf.DiskSize <= 0 {
|
||||
cf.DiskSize = 50
|
||||
}
|
||||
|
||||
if cf.AssociatePublicIpAddress && cf.InternetMaxBandwidthOut <= 0 {
|
||||
cf.InternetMaxBandwidthOut = 1
|
||||
}
|
||||
|
||||
if cf.InstanceName == "" {
|
||||
cf.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID())
|
||||
}
|
||||
|
||||
if cf.HostName == "" {
|
||||
cf.HostName = cf.InstanceName[:15]
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func checkDiskType(diskType string) bool {
|
||||
for _, valid := range ValidCBSType {
|
||||
if valid == diskType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConfig() *TencentCloudRunConfig {
|
||||
return &TencentCloudRunConfig{
|
||||
SourceImageId: "img-qwer1234",
|
||||
InstanceType: "S3.SMALL2",
|
||||
Comm: communicator.Config{
|
||||
SSHUsername: "tencentcloud",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTencentCloudRunConfig_Prepare(t *testing.T) {
|
||||
cf := testConfig()
|
||||
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %v", err)
|
||||
}
|
||||
|
||||
cf.InstanceType = ""
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
|
||||
cf.InstanceType = "S3.SMALL2"
|
||||
cf.SourceImageId = ""
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have err")
|
||||
}
|
||||
|
||||
cf.SourceImageId = "img-qwer1234"
|
||||
cf.Comm.SSHPort = 0
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %v", err)
|
||||
}
|
||||
|
||||
if cf.Comm.SSHPort != 22 {
|
||||
t.Fatalf("invalid ssh port value: %v", cf.Comm.SSHPort)
|
||||
}
|
||||
|
||||
cf.Comm.SSHPort = 44
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have err: %v", err)
|
||||
}
|
||||
|
||||
if cf.Comm.SSHPort != 44 {
|
||||
t.Fatalf("invalid ssh port value: %v", cf.Comm.SSHPort)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTencentCloudRunConfigPrepare_UserData(t *testing.T) {
|
||||
cf := testConfig()
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("new temp file failed: %v", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
cf.UserData = "text user_data"
|
||||
cf.UserDataFile = tf.Name()
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTencentCloudRunConfigPrepare_UserDataFile(t *testing.T) {
|
||||
cf := testConfig()
|
||||
cf.UserDataFile = "not-exist-file"
|
||||
if err := cf.Prepare(nil); err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
|
||||
tf, err := ioutil.TempFile("", "packer")
|
||||
if err != nil {
|
||||
t.Fatalf("new temp file failed: %v", err)
|
||||
}
|
||||
defer os.Remove(tf.Name())
|
||||
defer tf.Close()
|
||||
|
||||
cf.UserDataFile = tf.Name()
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTencentCloudRunConfigPrepare_TemporaryKeyPairName(t *testing.T) {
|
||||
cf := testConfig()
|
||||
cf.Comm.SSHTemporaryKeyPairName = ""
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have error: %v", err)
|
||||
}
|
||||
|
||||
if cf.Comm.SSHTemporaryKeyPairName == "" {
|
||||
t.Fatal("invalid ssh key pair value")
|
||||
}
|
||||
|
||||
cf.Comm.SSHTemporaryKeyPairName = "ssh-key-123"
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have error: %v", err)
|
||||
}
|
||||
|
||||
if cf.Comm.SSHTemporaryKeyPairName != "ssh-key-123" {
|
||||
t.Fatalf("invalid ssh key pair value: %v", cf.Comm.SSHTemporaryKeyPairName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTencentCloudRunConfigPrepare_SSHPrivateIp(t *testing.T) {
|
||||
cf := testConfig()
|
||||
if cf.SSHPrivateIp != false {
|
||||
t.Fatalf("invalid ssh_private_ip value: %v", cf.SSHPrivateIp)
|
||||
}
|
||||
cf.SSHPrivateIp = true
|
||||
if err := cf.Prepare(nil); err != nil {
|
||||
t.Fatalf("shouldn't have error: %v", err)
|
||||
}
|
||||
if cf.SSHPrivateIp != true {
|
||||
t.Fatalf("invalud ssh_private_ip value: %v", cf.SSHPrivateIp)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepCheckSourceImage struct {
|
||||
sourceImageId string
|
||||
}
|
||||
|
||||
func (s *stepCheckSourceImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
req := cvm.NewDescribeImagesRequest()
|
||||
req.ImageIds = []*string{&config.SourceImageId}
|
||||
req.InstanceType = &config.InstanceType
|
||||
|
||||
resp, err := client.DescribeImages(req)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("querying image info failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if *resp.Response.TotalCount > 0 { // public image or private image.
|
||||
state.Put("source_image", resp.Response.ImageSet[0])
|
||||
ui.Message(fmt.Sprintf("Image found: %s", *resp.Response.ImageSet[0].ImageId))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
// later market image will be included.
|
||||
err = fmt.Errorf("no image founded under current instance_type(%s) restriction", config.InstanceType)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
func (s *stepCheckSourceImage) Cleanup(bag multistep.StateBag) {}
|
|
@ -0,0 +1,132 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/communicator"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepConfigKeyPair struct {
|
||||
Debug bool
|
||||
Comm *communicator.Config
|
||||
DebugKeyPath string
|
||||
|
||||
keyID string
|
||||
}
|
||||
|
||||
func (s *stepConfigKeyPair) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if s.Comm.SSHPrivateKeyFile != "" {
|
||||
ui.Say("Using existing SSH private key")
|
||||
privateKeyBytes, err := ioutil.ReadFile(s.Comm.SSHPrivateKeyFile)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf(
|
||||
"loading configured private key file failed: %s", err))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
s.Comm.SSHPrivateKey = privateKeyBytes
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName == "" {
|
||||
ui.Say("Using SSH Agent with key pair in source image")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHAgentAuth && s.Comm.SSHKeyPairName != "" {
|
||||
ui.Say(fmt.Sprintf("Using SSH Agent for existing key pair %s", s.Comm.SSHKeyPairName))
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
if s.Comm.SSHTemporaryKeyPairName == "" {
|
||||
ui.Say("Not using temporary keypair")
|
||||
s.Comm.SSHKeyPairName = ""
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui.Say(fmt.Sprintf("Creating temporary keypair: %s", s.Comm.SSHTemporaryKeyPairName))
|
||||
req := cvm.NewCreateKeyPairRequest()
|
||||
req.KeyName = &s.Comm.SSHTemporaryKeyPairName
|
||||
defaultProjectId := int64(0)
|
||||
req.ProjectId = &defaultProjectId
|
||||
resp, err := client.CreateKeyPair(req)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("creating temporary keypair failed: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// set keyId to delete when Cleanup
|
||||
s.keyID = *resp.Response.KeyPair.KeyId
|
||||
|
||||
s.Comm.SSHKeyPairName = *resp.Response.KeyPair.KeyId
|
||||
s.Comm.SSHPrivateKey = []byte(*resp.Response.KeyPair.PrivateKey)
|
||||
|
||||
if s.Debug {
|
||||
ui.Message(fmt.Sprintf("Saving key for debug purposes: %s", s.DebugKeyPath))
|
||||
f, err := os.Create(s.DebugKeyPath)
|
||||
if err != nil {
|
||||
state.Put("error", fmt.Errorf("creating debug key file failed:%s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := f.Write([]byte(*resp.Response.KeyPair.PrivateKey)); err != nil {
|
||||
state.Put("error", fmt.Errorf("writing debug key file failed:%s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
if err := f.Chmod(0600); err != nil {
|
||||
state.Put("error", fmt.Errorf("setting debug key file's permission failed:%s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
}
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigKeyPair) Cleanup(state multistep.StateBag) {
|
||||
if s.Comm.SSHPrivateKeyFile != "" || (s.Comm.SSHKeyPairName == "" && s.keyID == "") {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Deleting temporary keypair...")
|
||||
req := cvm.NewDeleteKeyPairsRequest()
|
||||
req.KeyIds = []*string{&s.keyID}
|
||||
err := common.Retry(5, 5, 60, func(u uint) (bool, error) {
|
||||
_, err := client.DeleteKeyPairs(req)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if strings.Index(err.Error(), "NotSupported") != -1 {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf(
|
||||
"delete keypair failed, please delete it manually, keyId: %s, err: %s", s.keyID, err.Error()))
|
||||
}
|
||||
if s.Debug {
|
||||
if err := os.Remove(s.DebugKeyPath); err != nil {
|
||||
ui.Error(fmt.Sprintf("delete debug key file %s failed: %s", s.DebugKeyPath, err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/pkg/errors"
|
||||
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
|
||||
)
|
||||
|
||||
type stepConfigSecurityGroup struct {
|
||||
SecurityGroupId string
|
||||
SecurityGroupName string
|
||||
Description string
|
||||
isCreate bool
|
||||
}
|
||||
|
||||
func (s *stepConfigSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.SecurityGroupId) != 0 { // use existing security group
|
||||
req := vpc.NewDescribeSecurityGroupsRequest()
|
||||
req.SecurityGroupIds = []*string{&s.SecurityGroupId}
|
||||
resp, err := vpcClient.DescribeSecurityGroups(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("query security group failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if *resp.Response.TotalCount > 0 {
|
||||
state.Put("security_group_id", s.SecurityGroupId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
message := fmt.Sprintf("the specified security group(%s) does not exist", s.SecurityGroupId)
|
||||
ui.Error(message)
|
||||
state.Put("error", errors.New(message))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
// create a new security group
|
||||
req := vpc.NewCreateSecurityGroupRequest()
|
||||
req.GroupName = &s.SecurityGroupName
|
||||
req.GroupDescription = &s.Description
|
||||
resp, err := vpcClient.CreateSecurityGroup(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("create security group failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.SecurityGroupId = *resp.Response.SecurityGroup.SecurityGroupId
|
||||
state.Put("security_group_id", s.SecurityGroupId)
|
||||
s.isCreate = true
|
||||
|
||||
// bind security group ingress police
|
||||
pReq := vpc.NewCreateSecurityGroupPoliciesRequest()
|
||||
ACCEPT := "ACCEPT"
|
||||
DEFAULT_CIDR := "0.0.0.0/0"
|
||||
pReq.SecurityGroupId = &s.SecurityGroupId
|
||||
pReq.SecurityGroupPolicySet = &vpc.SecurityGroupPolicySet{
|
||||
Ingress: []*vpc.SecurityGroupPolicy{
|
||||
{
|
||||
CidrBlock: &DEFAULT_CIDR,
|
||||
Action: &ACCEPT,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = vpcClient.CreateSecurityGroupPolicies(pReq)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("bind security group police failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
// bind security group engress police
|
||||
pReq = vpc.NewCreateSecurityGroupPoliciesRequest()
|
||||
pReq.SecurityGroupId = &s.SecurityGroupId
|
||||
pReq.SecurityGroupPolicySet = &vpc.SecurityGroupPolicySet{
|
||||
Egress: []*vpc.SecurityGroupPolicy{
|
||||
{
|
||||
CidrBlock: &DEFAULT_CIDR,
|
||||
Action: &ACCEPT,
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = vpcClient.CreateSecurityGroupPolicies(pReq)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("bind security group police failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepConfigSecurityGroup) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
MessageClean(state, "VPC")
|
||||
req := vpc.NewDeleteSecurityGroupRequest()
|
||||
req.SecurityGroupId = &s.SecurityGroupId
|
||||
err := common.Retry(5, 5, 60, func(u uint) (bool, error) {
|
||||
_, err := vpcClient.DeleteSecurityGroup(req)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if strings.Index(err.Error(), "ResourceInUse") != -1 {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("delete security group(%s) failed: %s, you need to delete it by hand",
|
||||
s.SecurityGroupId, err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/pkg/errors"
|
||||
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
|
||||
)
|
||||
|
||||
type stepConfigSubnet struct {
|
||||
SubnetId string
|
||||
SubnetCidrBlock string
|
||||
SubnetName string
|
||||
Zone string
|
||||
isCreate bool
|
||||
}
|
||||
|
||||
func (s *stepConfigSubnet) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
vpcId := state.Get("vpc_id").(string)
|
||||
|
||||
if len(s.SubnetId) != 0 { // exist subnet
|
||||
ui.Say(fmt.Sprintf("Trying to use existing subnet(%s)", s.SubnetId))
|
||||
req := vpc.NewDescribeSubnetsRequest()
|
||||
req.SubnetIds = []*string{&s.SubnetId}
|
||||
resp, err := vpcClient.DescribeSubnets(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("query subnet failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if *resp.Response.TotalCount > 0 {
|
||||
subnet0 := *resp.Response.SubnetSet[0]
|
||||
if *subnet0.VpcId != vpcId {
|
||||
message := fmt.Sprintf("the specified subnet(%s) does not belong to "+
|
||||
"the specified vpc(%s)", s.SubnetId, vpcId)
|
||||
ui.Error(message)
|
||||
state.Put("error", errors.New(message))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("subnet_id", *subnet0.SubnetId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
message := fmt.Sprintf("the specified subnet(%s) does not exist", s.SubnetId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Error(message)
|
||||
return multistep.ActionHalt
|
||||
} else { // create a new subnet, tencentcloud create subnet api is synchronous, no need to wait for create.
|
||||
ui.Say(fmt.Sprintf("Trying to create a new subnet"))
|
||||
req := vpc.NewCreateSubnetRequest()
|
||||
req.VpcId = &vpcId
|
||||
req.SubnetName = &s.SubnetName
|
||||
req.CidrBlock = &s.SubnetCidrBlock
|
||||
req.Zone = &s.Zone
|
||||
resp, err := vpcClient.CreateSubnet(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("create subnet failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
subnet0 := *resp.Response.Subnet
|
||||
state.Put("subnet_id", *subnet0.SubnetId)
|
||||
s.SubnetId = *subnet0.SubnetId
|
||||
s.isCreate = true
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stepConfigSubnet) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
MessageClean(state, "SUBNET")
|
||||
req := vpc.NewDeleteSubnetRequest()
|
||||
req.SubnetId = &s.SubnetId
|
||||
err := common.Retry(5, 5, 60, func(u uint) (bool, error) {
|
||||
_, err := vpcClient.DeleteSubnet(req)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if strings.Index(err.Error(), "ResourceInUse") != -1 {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("delete subnet(%s) failed: %s, you need to delete it by hand",
|
||||
s.SubnetId, err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/packer/common"
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/pkg/errors"
|
||||
vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312"
|
||||
)
|
||||
|
||||
type stepConfigVPC struct {
|
||||
VpcId string
|
||||
CidrBlock string
|
||||
VpcName string
|
||||
isCreate bool
|
||||
}
|
||||
|
||||
func (s *stepConfigVPC) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
if len(s.VpcId) != 0 { // exist vpc
|
||||
ui.Say(fmt.Sprintf("Trying to use existing vpc(%s)", s.VpcId))
|
||||
req := vpc.NewDescribeVpcsRequest()
|
||||
req.VpcIds = []*string{&s.VpcId}
|
||||
resp, err := vpcClient.DescribeVpcs(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("query vpc failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if *resp.Response.TotalCount > 0 {
|
||||
vpc0 := *resp.Response.VpcSet[0]
|
||||
state.Put("vpc_id", *vpc0.VpcId)
|
||||
s.isCreate = false
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
message := fmt.Sprintf("the specified vpc(%s) does not exist", s.VpcId)
|
||||
state.Put("error", errors.New(message))
|
||||
ui.Error(message)
|
||||
return multistep.ActionHalt
|
||||
} else { // create a new vpc, tencentcloud create vpc api is synchronous, no need to wait for create.
|
||||
ui.Say(fmt.Sprintf("Trying to create a new vpc"))
|
||||
req := vpc.NewCreateVpcRequest()
|
||||
req.VpcName = &s.VpcName
|
||||
req.CidrBlock = &s.CidrBlock
|
||||
resp, err := vpcClient.CreateVpc(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("create vpc failed: %s", err.Error()))
|
||||
state.Put("error", err)
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
vpc0 := *resp.Response.Vpc
|
||||
state.Put("vpc_id", *vpc0.VpcId)
|
||||
s.VpcId = *vpc0.VpcId
|
||||
s.isCreate = true
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stepConfigVPC) Cleanup(state multistep.StateBag) {
|
||||
if !s.isCreate {
|
||||
return
|
||||
}
|
||||
|
||||
vpcClient := state.Get("vpc_client").(*vpc.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
MessageClean(state, "VPC")
|
||||
req := vpc.NewDeleteVpcRequest()
|
||||
req.VpcId = &s.VpcId
|
||||
err := common.Retry(5, 5, 60, func(u uint) (bool, error) {
|
||||
_, err := vpcClient.DeleteVpc(req)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
if strings.Index(err.Error(), "ResourceInUse") != -1 {
|
||||
return false, nil
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("delete vpc(%s) failed: %s, you need to delete it by hand",
|
||||
s.VpcId, err.Error()))
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepCopyImage struct {
|
||||
DesinationRegions []string
|
||||
SourceRegion string
|
||||
}
|
||||
|
||||
func (s *stepCopyImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if len(s.DesinationRegions) == 0 || (len(s.DesinationRegions) == 1 && s.DesinationRegions[0] == s.SourceRegion) {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("image").(*cvm.Image).ImageId
|
||||
|
||||
req := cvm.NewSyncImagesRequest()
|
||||
req.ImageIds = []*string{imageId}
|
||||
copyRegions := make([]*string, 0, len(s.DesinationRegions))
|
||||
for _, region := range s.DesinationRegions {
|
||||
if region != s.SourceRegion {
|
||||
copyRegions = append(copyRegions, ®ion)
|
||||
}
|
||||
}
|
||||
req.DestinationRegions = copyRegions
|
||||
|
||||
_, err := client.SyncImages(req)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(fmt.Sprintf("copy image failed: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCopyImage) Cleanup(state multistep.StateBag) {
|
||||
// just do nothing
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepCreateImage struct {
|
||||
imageId string
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
config := state.Get("config").(*Config)
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
instance := state.Get("instance").(*cvm.Instance)
|
||||
|
||||
ui.Say(fmt.Sprintf("Creating image %s", config.ImageName))
|
||||
|
||||
req := cvm.NewCreateImageRequest()
|
||||
req.ImageName = &config.ImageName
|
||||
req.ImageDescription = &config.ImageDescription
|
||||
req.InstanceId = instance.InstanceId
|
||||
|
||||
True := "True"
|
||||
False := "False"
|
||||
if config.ForcePoweroff {
|
||||
req.ForcePoweroff = &True
|
||||
} else {
|
||||
req.ForcePoweroff = &False
|
||||
}
|
||||
|
||||
if config.Reboot {
|
||||
req.Reboot = &True
|
||||
} else {
|
||||
req.Reboot = &False
|
||||
}
|
||||
|
||||
if config.Sysprep {
|
||||
req.Sysprep = &True
|
||||
} else {
|
||||
req.Sysprep = &False
|
||||
}
|
||||
|
||||
_, err := client.CreateImage(req)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("create image failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
err = WaitForImageReady(client, config.ImageName, "NORMAL", 3600)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("create image failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
describeReq := cvm.NewDescribeImagesRequest()
|
||||
FILTER_IMAGE_NAME := "image-name"
|
||||
describeReq.Filters = []*cvm.Filter{
|
||||
{
|
||||
Name: &FILTER_IMAGE_NAME,
|
||||
Values: []*string{&config.ImageName},
|
||||
},
|
||||
}
|
||||
describeResp, err := client.DescribeImages(describeReq)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("wait image ready failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if *describeResp.Response.TotalCount == 0 {
|
||||
err := fmt.Errorf("create image(%s) failed", config.ImageName)
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.imageId = *describeResp.Response.ImageSet[0].ImageId
|
||||
state.Put("image", describeResp.Response.ImageSet[0])
|
||||
|
||||
tencentCloudImages := make(map[string]string)
|
||||
tencentCloudImages[config.Region] = s.imageId
|
||||
state.Put("tencentcloudimages", tencentCloudImages)
|
||||
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepCreateImage) Cleanup(state multistep.StateBag) {
|
||||
if s.imageId == "" {
|
||||
return
|
||||
}
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if !cancelled && !halted {
|
||||
return
|
||||
}
|
||||
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
|
||||
ui.Say("Delete image because of cancellation or error...")
|
||||
req := cvm.NewDeleteImagesRequest()
|
||||
req.ImageIds = []*string{&s.imageId}
|
||||
_, err := client.DeleteImages(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("delete image(%s) failed", s.imageId))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
type stepPreValidate struct {
|
||||
DestImageName string
|
||||
ForceDelete bool
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
ui.Say("TencentCloud support images with same name, image_name check is not required.")
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepPreValidate) Cleanup(multistep.StateBag) {}
|
|
@ -0,0 +1,158 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepRunInstance struct {
|
||||
InstanceType string
|
||||
UserData string
|
||||
UserDataFile string
|
||||
instanceId string
|
||||
ZoneId string
|
||||
InstanceName string
|
||||
DiskType string
|
||||
DiskSize int64
|
||||
HostName string
|
||||
InternetMaxBandwidthOut int64
|
||||
AssociatePublicIpAddress bool
|
||||
}
|
||||
|
||||
func (s *stepRunInstance) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
config := state.Get("config").(*Config)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
source_image := state.Get("source_image").(*cvm.Image)
|
||||
vpc_id := state.Get("vpc_id").(string)
|
||||
subnet_id := state.Get("subnet_id").(string)
|
||||
security_group_id := state.Get("security_group_id").(string)
|
||||
|
||||
password := config.Comm.SSHPassword
|
||||
if password == "" && config.Comm.WinRMPassword != "" {
|
||||
password = config.Comm.WinRMPassword
|
||||
}
|
||||
userData, err := s.getUserData(state)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("get user_data failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
ui.Say("Creating Instance.")
|
||||
// config RunInstances parameters
|
||||
POSTPAID_BY_HOUR := "POSTPAID_BY_HOUR"
|
||||
req := cvm.NewRunInstancesRequest()
|
||||
if s.ZoneId != "" {
|
||||
req.Placement = &cvm.Placement{
|
||||
Zone: &s.ZoneId,
|
||||
}
|
||||
}
|
||||
req.ImageId = source_image.ImageId
|
||||
req.InstanceChargeType = &POSTPAID_BY_HOUR
|
||||
req.InstanceType = &s.InstanceType
|
||||
req.SystemDisk = &cvm.SystemDisk{
|
||||
DiskType: &s.DiskType,
|
||||
DiskSize: &s.DiskSize,
|
||||
}
|
||||
req.VirtualPrivateCloud = &cvm.VirtualPrivateCloud{
|
||||
VpcId: &vpc_id,
|
||||
SubnetId: &subnet_id,
|
||||
}
|
||||
TRAFFIC_POSTPAID_BY_HOUR := "TRAFFIC_POSTPAID_BY_HOUR"
|
||||
if s.AssociatePublicIpAddress {
|
||||
req.InternetAccessible = &cvm.InternetAccessible{
|
||||
InternetChargeType: &TRAFFIC_POSTPAID_BY_HOUR,
|
||||
InternetMaxBandwidthOut: &s.InternetMaxBandwidthOut,
|
||||
}
|
||||
}
|
||||
req.InstanceName = &s.InstanceName
|
||||
loginSettings := cvm.LoginSettings{}
|
||||
if password != "" {
|
||||
loginSettings.Password = &password
|
||||
}
|
||||
if config.Comm.SSHKeyPairName != "" {
|
||||
loginSettings.KeyIds = []*string{&config.Comm.SSHKeyPairName}
|
||||
}
|
||||
req.LoginSettings = &loginSettings
|
||||
req.SecurityGroupIds = []*string{&security_group_id}
|
||||
req.ClientToken = &s.InstanceName
|
||||
req.HostName = &s.HostName
|
||||
req.UserData = &userData
|
||||
|
||||
resp, err := client.RunInstances(req)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("create instance failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
if len(resp.Response.InstanceIdSet) != 1 {
|
||||
err := fmt.Errorf("create instance failed: %d instance(s) created", len(resp.Response.InstanceIdSet))
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
s.instanceId = *resp.Response.InstanceIdSet[0]
|
||||
|
||||
err = WaitForInstance(client, s.instanceId, "RUNNING", 1800)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("wait instance launch failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
|
||||
describeReq := cvm.NewDescribeInstancesRequest()
|
||||
describeReq.InstanceIds = []*string{&s.instanceId}
|
||||
describeResp, err := client.DescribeInstances(describeReq)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("wait instance launch failed: %s", err.Error())
|
||||
state.Put("error", err)
|
||||
ui.Error(err.Error())
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
state.Put("instance", describeResp.Response.InstanceSet[0])
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepRunInstance) getUserData(state multistep.StateBag) (string, error) {
|
||||
userData := s.UserData
|
||||
if userData == "" && s.UserDataFile != "" {
|
||||
data, err := ioutil.ReadFile(s.UserDataFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
userData = string(data)
|
||||
}
|
||||
userData = base64.StdEncoding.EncodeToString([]byte(userData))
|
||||
log.Printf(fmt.Sprintf("user_data: %s", userData))
|
||||
return userData, nil
|
||||
}
|
||||
|
||||
func (s *stepRunInstance) Cleanup(state multistep.StateBag) {
|
||||
if s.instanceId == "" {
|
||||
return
|
||||
}
|
||||
MessageClean(state, "instance")
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
req := cvm.NewTerminateInstancesRequest()
|
||||
req.InstanceIds = []*string{&s.instanceId}
|
||||
_, err := client.TerminateInstances(req)
|
||||
// The binding relation between instance and vpc would last few minutes after
|
||||
// instance terminate, we sleep here to give more time
|
||||
time.Sleep(2 * time.Minute)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("terminate instance(%s) failed: %s", s.instanceId, err.Error()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package cvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/packer/helper/multistep"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
|
||||
)
|
||||
|
||||
type stepShareImage struct {
|
||||
ShareAccounts []string
|
||||
}
|
||||
|
||||
func (s *stepShareImage) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
|
||||
if len(s.ShareAccounts) == 0 {
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
imageId := state.Get("image").(*cvm.Image).ImageId
|
||||
|
||||
req := cvm.NewModifyImageSharePermissionRequest()
|
||||
req.ImageId = imageId
|
||||
SHARE := "SHARE"
|
||||
req.Permission = &SHARE
|
||||
accounts := make([]*string, 0, len(s.ShareAccounts))
|
||||
for _, account := range s.ShareAccounts {
|
||||
accounts = append(accounts, &account)
|
||||
}
|
||||
req.AccountIds = accounts
|
||||
|
||||
_, err := client.ModifyImageSharePermission(req)
|
||||
if err != nil {
|
||||
state.Put("error", err)
|
||||
ui.Error(fmt.Sprintf("share image failed: %s", err.Error()))
|
||||
return multistep.ActionHalt
|
||||
}
|
||||
return multistep.ActionContinue
|
||||
}
|
||||
|
||||
func (s *stepShareImage) Cleanup(state multistep.StateBag) {
|
||||
_, cancelled := state.GetOk(multistep.StateCancelled)
|
||||
_, halted := state.GetOk(multistep.StateHalted)
|
||||
if cancelled || halted {
|
||||
ui := state.Get("ui").(packer.Ui)
|
||||
client := state.Get("cvm_client").(*cvm.Client)
|
||||
imageId := state.Get("image").(*cvm.Image).ImageId
|
||||
ui.Say("Cancel share image due to action cancelled or halted.")
|
||||
|
||||
req := cvm.NewModifyImageSharePermissionRequest()
|
||||
req.ImageId = imageId
|
||||
CANCEL := "CANCEL"
|
||||
req.Permission = &CANCEL
|
||||
accounts := make([]*string, 0, len(s.ShareAccounts))
|
||||
for _, account := range s.ShareAccounts {
|
||||
accounts = append(accounts, &account)
|
||||
}
|
||||
req.AccountIds = accounts
|
||||
|
||||
_, err := client.ModifyImageSharePermission(req)
|
||||
if err != nil {
|
||||
ui.Error(fmt.Sprintf("Cancel share image failed: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ import (
|
|||
profitbricksbuilder "github.com/hashicorp/packer/builder/profitbricks"
|
||||
qemubuilder "github.com/hashicorp/packer/builder/qemu"
|
||||
scalewaybuilder "github.com/hashicorp/packer/builder/scaleway"
|
||||
tencentcloudbuilder "github.com/hashicorp/packer/builder/tencentcloud/cvm"
|
||||
tritonbuilder "github.com/hashicorp/packer/builder/triton"
|
||||
virtualboxisobuilder "github.com/hashicorp/packer/builder/virtualbox/iso"
|
||||
virtualboxovfbuilder "github.com/hashicorp/packer/builder/virtualbox/ovf"
|
||||
|
@ -113,6 +114,7 @@ var Builders = map[string]packer.Builder{
|
|||
"profitbricks": new(profitbricksbuilder.Builder),
|
||||
"qemu": new(qemubuilder.Builder),
|
||||
"scaleway": new(scalewaybuilder.Builder),
|
||||
"tencentcloud-cvm": new(tencentcloudbuilder.Builder),
|
||||
"triton": new(tritonbuilder.Builder),
|
||||
"virtualbox-iso": new(virtualboxisobuilder.Builder),
|
||||
"virtualbox-ovf": new(virtualboxovfbuilder.Builder),
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"variables": {
|
||||
"secret_id": "{{env `TENCENTCLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `TENCENTCLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "tencentcloud-cvm",
|
||||
"secret_id": "{{user `secret_id`}}",
|
||||
"secret_key": "{{user `secret_key`}}",
|
||||
"region": "ap-guangzhou",
|
||||
"zone": "ap-guangzhou-3",
|
||||
"instance_type": "S3.SMALL1",
|
||||
"source_image_id": "img-oikl1tzv",
|
||||
"ssh_username" : "root",
|
||||
"image_name": "packerTest2",
|
||||
"packer_debug": true,
|
||||
"associate_public_ip_address": true
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sleep 30",
|
||||
"yum install redis.x86_64 -y"
|
||||
]
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"variables": {
|
||||
"secret_id": "{{env `TENCENTCLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `TENCENTCLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "tencentcloud-cvm",
|
||||
"secret_id": "{{user `secret_id`}}",
|
||||
"secret_key": "{{user `secret_key`}}",
|
||||
"region": "ap-guangzhou",
|
||||
"zone": "ap-guangzhou-3",
|
||||
"instance_type": "S3.SMALL1",
|
||||
"source_image_id": "img-oikl1tzv",
|
||||
"vpc_id": "vpc-gjusx3kd",
|
||||
"subnet_id": "subnet-pfditepm",
|
||||
"internet_max_bandwidth_out": 2,
|
||||
"security_group_id": "sg-rypoiksl",
|
||||
"ssh_username" : "root",
|
||||
"image_name": "packerTest",
|
||||
"host_name": "packerTest",
|
||||
"associate_public_ip_address": true,
|
||||
"image_description": "centosPacker",
|
||||
"image_copy_regions": ["ap-beijing"]
|
||||
}],
|
||||
"provisioners": [{
|
||||
"execute_command": "echo '{{user `ssh_pass`}}' | {{ .Vars }} sudo -S -E sh '{{ .Path }}'",
|
||||
"inline": [
|
||||
"yum update -y",
|
||||
"/usr/sbin/waagent -force -deprovision+user && export HISTSIZE=0 && sync"
|
||||
],
|
||||
"inline_shebang": "/bin/sh -x",
|
||||
"type": "shell",
|
||||
"skip_clean": true
|
||||
}]
|
||||
}
|
1
go.mod
1
go.mod
|
@ -154,6 +154,7 @@ require (
|
|||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346
|
||||
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 // indirect
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 // indirect
|
||||
github.com/ugorji/go v0.0.0-20151218193438-646ae4a518c1
|
||||
|
|
2
go.sum
2
go.sum
|
@ -345,6 +345,8 @@ github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpke
|
|||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346 h1:a014AaXz7AISMePv8xKRffUZZkr5z2XmSDf41gRV3+A=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181220135002-f1744d40d346/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
|
||||
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9 h1:/Bsw4C+DEdqPjt8vAqaC9LAqpAQnaCQQqmolqq3S1T4=
|
||||
github.com/tent/http-link-go v0.0.0-20130702225549-ac974c61c2f9/go.mod h1:RHkNRtSLfOK7qBTHaeSX1D6BNpI3qw7NTxsmNr4RvN8=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8=
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) 2017-2018 Tencent Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
88
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/client.go
generated
vendored
Normal file
88
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/client.go
generated
vendored
Normal file
|
@ -0,0 +1,88 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
region string
|
||||
httpClient *http.Client
|
||||
httpProfile *profile.HttpProfile
|
||||
credential *Credential
|
||||
signMethod string
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (c *Client) Send(request tchttp.Request, response tchttp.Response) (err error) {
|
||||
if request.GetDomain() == "" {
|
||||
domain := c.httpProfile.Endpoint
|
||||
if domain == "" {
|
||||
domain = tchttp.GetServiceDomain(request.GetService())
|
||||
}
|
||||
request.SetDomain(domain)
|
||||
}
|
||||
err = tchttp.ConstructParams(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tchttp.CompleteCommonParams(request, c.GetRegion())
|
||||
err = signRequest(request, c.credential, c.signMethod)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
httpRequest, err := http.NewRequest(request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if request.GetHttpMethod() == "POST" {
|
||||
httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
}
|
||||
//log.Printf("[DEBUG] http request=%v", httpRequest)
|
||||
httpResponse, err := c.httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tchttp.ParseFromHttpResponse(httpResponse, response)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Client) GetRegion() string {
|
||||
return c.region
|
||||
}
|
||||
|
||||
func (c *Client) Init(region string) *Client {
|
||||
c.httpClient = &http.Client{}
|
||||
c.region = region
|
||||
c.signMethod = "HmacSHA256"
|
||||
c.debug = false
|
||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) WithSecretId(secretId, secretKey string) *Client {
|
||||
c.credential = NewCredential(secretId, secretKey)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) WithProfile(clientProfile *profile.ClientProfile) *Client {
|
||||
c.signMethod = clientProfile.SignMethod
|
||||
c.httpProfile = clientProfile.HttpProfile
|
||||
c.httpClient.Timeout = time.Duration(c.httpProfile.ReqTimeout) * time.Second
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) WithSignatureMethod(method string) *Client {
|
||||
c.signMethod = method
|
||||
return c
|
||||
}
|
||||
|
||||
func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
client.Init(region).WithSecretId(secretId, secretKey)
|
||||
return
|
||||
}
|
40
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/credentials.go
generated
vendored
Normal file
40
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/credentials.go
generated
vendored
Normal file
|
@ -0,0 +1,40 @@
|
|||
package common
|
||||
|
||||
type Credential struct {
|
||||
SecretId string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
func NewCredential(secretId, secretKey string) *Credential {
|
||||
return &Credential{
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Credential) GetCredentialParams() map[string]string {
|
||||
return map[string]string{
|
||||
"SecretId": c.SecretId,
|
||||
}
|
||||
}
|
||||
|
||||
type TokenCredential struct {
|
||||
SecretId string
|
||||
SecretKey string
|
||||
Token string
|
||||
}
|
||||
|
||||
func NewTokenCredential(secretId, secretKey, token string) *TokenCredential {
|
||||
return &TokenCredential{
|
||||
SecretId: secretId,
|
||||
SecretKey: secretKey,
|
||||
Token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TokenCredential) GetCredentialParams() map[string]string {
|
||||
return map[string]string{
|
||||
"SecretId": c.SecretId,
|
||||
"Token": c.Token,
|
||||
}
|
||||
}
|
35
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors/errors.go
generated
vendored
Normal file
35
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors/errors.go
generated
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type TencentCloudSDKError struct {
|
||||
Code string
|
||||
Message string
|
||||
RequestId string
|
||||
}
|
||||
|
||||
func (e *TencentCloudSDKError) Error() string {
|
||||
return fmt.Sprintf("[TencentCloudSDKError] Code=%s, Message=%s, RequestId=%s", e.Code, e.Message, e.RequestId)
|
||||
}
|
||||
|
||||
func NewTencentCloudSDKError(code, message, requestId string) error {
|
||||
return &TencentCloudSDKError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
RequestId: requestId,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TencentCloudSDKError) GetCode() string {
|
||||
return e.Code
|
||||
}
|
||||
|
||||
func (e *TencentCloudSDKError) GetMessage() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e *TencentCloudSDKError) GetRequestId() string {
|
||||
return e.RequestId
|
||||
}
|
231
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go
generated
vendored
Normal file
231
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go
generated
vendored
Normal file
|
@ -0,0 +1,231 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
//"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
POST = "POST"
|
||||
GET = "GET"
|
||||
|
||||
RootDomain = "tencentcloudapi.com"
|
||||
Path = "/"
|
||||
)
|
||||
|
||||
type Request interface {
|
||||
GetAction() string
|
||||
GetBodyReader() io.Reader
|
||||
GetDomain() string
|
||||
GetHttpMethod() string
|
||||
GetParams() map[string]string
|
||||
GetPath() string
|
||||
GetService() string
|
||||
GetUrl() string
|
||||
GetVersion() string
|
||||
SetDomain(string)
|
||||
SetHttpMethod(string)
|
||||
}
|
||||
|
||||
type BaseRequest struct {
|
||||
httpMethod string
|
||||
domain string
|
||||
path string
|
||||
params map[string]string
|
||||
formParams map[string]string
|
||||
|
||||
service string
|
||||
version string
|
||||
action string
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetAction() string {
|
||||
return r.action
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetHttpMethod() string {
|
||||
return r.httpMethod
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetParams() map[string]string {
|
||||
return r.params
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetPath() string {
|
||||
return r.path
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetDomain() string {
|
||||
return r.domain
|
||||
}
|
||||
|
||||
func (r *BaseRequest) SetDomain(domain string) {
|
||||
r.domain = domain
|
||||
}
|
||||
|
||||
func (r *BaseRequest) SetHttpMethod(method string) {
|
||||
switch strings.ToUpper(method) {
|
||||
case POST:
|
||||
{
|
||||
r.httpMethod = POST
|
||||
}
|
||||
case GET:
|
||||
{
|
||||
r.httpMethod = GET
|
||||
}
|
||||
default:
|
||||
{
|
||||
r.httpMethod = GET
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetService() string {
|
||||
return r.service
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetUrl() string {
|
||||
if r.httpMethod == GET {
|
||||
return "https://" + r.domain + r.path + "?" + getUrlQueriesEncoded(r.params)
|
||||
} else if r.httpMethod == POST {
|
||||
return "https://" + r.domain + r.path
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetVersion() string {
|
||||
return r.version
|
||||
}
|
||||
|
||||
func getUrlQueriesEncoded(params map[string]string) string {
|
||||
values := url.Values{}
|
||||
for key, value := range params {
|
||||
if value != "" {
|
||||
values.Add(key, value)
|
||||
}
|
||||
}
|
||||
return values.Encode()
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetBodyReader() io.Reader {
|
||||
if r.httpMethod == POST {
|
||||
s := getUrlQueriesEncoded(r.params)
|
||||
//log.Printf("[DEBUG] body: %s", s)
|
||||
return strings.NewReader(s)
|
||||
} else {
|
||||
return strings.NewReader("")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BaseRequest) Init() *BaseRequest {
|
||||
r.httpMethod = GET
|
||||
r.domain = ""
|
||||
r.path = Path
|
||||
r.params = make(map[string]string)
|
||||
r.formParams = make(map[string]string)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *BaseRequest) WithApiInfo(service, version, action string) *BaseRequest {
|
||||
r.service = service
|
||||
r.version = version
|
||||
r.action = action
|
||||
return r
|
||||
}
|
||||
|
||||
func GetServiceDomain(service string) (domain string) {
|
||||
domain = service + "." + RootDomain
|
||||
return
|
||||
}
|
||||
|
||||
func CompleteCommonParams(request Request, region string) {
|
||||
params := request.GetParams()
|
||||
params["Region"] = region
|
||||
if request.GetVersion() != "" {
|
||||
params["Version"] = request.GetVersion()
|
||||
}
|
||||
params["Action"] = request.GetAction()
|
||||
params["Timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
params["Nonce"] = strconv.Itoa(rand.Int())
|
||||
params["RequestClient"] = "SDK_GO_3.0.26"
|
||||
}
|
||||
|
||||
func ConstructParams(req Request) (err error) {
|
||||
value := reflect.ValueOf(req).Elem()
|
||||
err = flatStructure(value, req, "")
|
||||
//log.Printf("[DEBUG] params=%s", req.GetParams())
|
||||
return
|
||||
}
|
||||
|
||||
func flatStructure(value reflect.Value, request Request, prefix string) (err error) {
|
||||
//log.Printf("[DEBUG] reflect value: %v", value.Type())
|
||||
valueType := value.Type()
|
||||
for i := 0; i < valueType.NumField(); i++ {
|
||||
tag := valueType.Field(i).Tag
|
||||
nameTag, hasNameTag := tag.Lookup("name")
|
||||
if !hasNameTag {
|
||||
continue
|
||||
}
|
||||
field := value.Field(i)
|
||||
kind := field.Kind()
|
||||
if kind == reflect.Ptr && field.IsNil() {
|
||||
continue
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
field = field.Elem()
|
||||
kind = field.Kind()
|
||||
}
|
||||
key := prefix + nameTag
|
||||
if kind == reflect.String {
|
||||
s := field.String()
|
||||
if s != "" {
|
||||
request.GetParams()[key] = s
|
||||
}
|
||||
} else if kind == reflect.Bool {
|
||||
request.GetParams()[key] = strconv.FormatBool(field.Bool())
|
||||
} else if kind == reflect.Int || kind == reflect.Int64 {
|
||||
request.GetParams()[key] = strconv.FormatInt(field.Int(), 10)
|
||||
} else if kind == reflect.Uint || kind == reflect.Uint64 {
|
||||
request.GetParams()[key] = strconv.FormatUint(field.Uint(), 10)
|
||||
} else if kind == reflect.Float64 {
|
||||
request.GetParams()[key] = strconv.FormatFloat(field.Float(), 'f', -1, 64)
|
||||
} else if kind == reflect.Slice {
|
||||
list := value.Field(i)
|
||||
for j := 0; j < list.Len(); j++ {
|
||||
vj := list.Index(j)
|
||||
key := prefix + nameTag + "." + strconv.Itoa(j)
|
||||
kind = vj.Kind()
|
||||
if kind == reflect.Ptr && vj.IsNil() {
|
||||
continue
|
||||
}
|
||||
if kind == reflect.Ptr {
|
||||
vj = vj.Elem()
|
||||
kind = vj.Kind()
|
||||
}
|
||||
if kind == reflect.String {
|
||||
request.GetParams()[key] = vj.String()
|
||||
} else if kind == reflect.Bool {
|
||||
request.GetParams()[key] = strconv.FormatBool(vj.Bool())
|
||||
} else if kind == reflect.Int || kind == reflect.Int64 {
|
||||
request.GetParams()[key] = strconv.FormatInt(vj.Int(), 10)
|
||||
} else if kind == reflect.Uint || kind == reflect.Uint64 {
|
||||
request.GetParams()[key] = strconv.FormatUint(vj.Uint(), 10)
|
||||
} else if kind == reflect.Float64 {
|
||||
request.GetParams()[key] = strconv.FormatFloat(vj.Float(), 'f', -1, 64)
|
||||
} else {
|
||||
flatStructure(vj, request, key+".")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
flatStructure(reflect.ValueOf(field.Interface()), request, prefix+nameTag+".")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
69
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go
generated
vendored
Normal file
69
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go
generated
vendored
Normal file
|
@ -0,0 +1,69 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
// "log"
|
||||
"net/http"
|
||||
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
)
|
||||
|
||||
type Response interface {
|
||||
ParseErrorFromHTTPResponse(body []byte) error
|
||||
}
|
||||
|
||||
type BaseResponse struct {
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Response struct {
|
||||
Error struct {
|
||||
Code string `json:"Code"`
|
||||
Message string `json:"Message"`
|
||||
} `json:"Error" omitempty`
|
||||
RequestId string `json:"RequestId"`
|
||||
} `json:"Response"`
|
||||
}
|
||||
|
||||
type DeprecatedAPIErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
CodeDesc string `json:"codeDesc"`
|
||||
}
|
||||
|
||||
func (r *BaseResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
|
||||
resp := &ErrorResponse{}
|
||||
err = json.Unmarshal(body, resp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if resp.Response.Error.Code != "" {
|
||||
return errors.NewTencentCloudSDKError(resp.Response.Error.Code, resp.Response.Error.Message, resp.Response.RequestId)
|
||||
}
|
||||
|
||||
deprecated := &DeprecatedAPIErrorResponse{}
|
||||
err = json.Unmarshal(body, deprecated)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if deprecated.Code != 0 {
|
||||
return errors.NewTencentCloudSDKError(deprecated.CodeDesc, deprecated.Message, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseFromHttpResponse(hr *http.Response, response Response) (err error) {
|
||||
defer hr.Body.Close()
|
||||
body, err := ioutil.ReadAll(hr.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//log.Printf("[DEBUG] Response Body=%s", body)
|
||||
err = response.ParseErrorFromHTTPResponse(body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(body, &response)
|
||||
return
|
||||
}
|
13
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/client_profile.go
generated
vendored
Normal file
13
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/client_profile.go
generated
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package profile
|
||||
|
||||
type ClientProfile struct {
|
||||
HttpProfile *HttpProfile
|
||||
SignMethod string
|
||||
}
|
||||
|
||||
func NewClientProfile() *ClientProfile {
|
||||
return &ClientProfile{
|
||||
HttpProfile: NewHttpProfile(),
|
||||
SignMethod: "HmacSHA256",
|
||||
}
|
||||
}
|
17
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/http_profile.go
generated
vendored
Normal file
17
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/http_profile.go
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
package profile
|
||||
|
||||
type HttpProfile struct {
|
||||
ReqMethod string
|
||||
ReqTimeout int
|
||||
Endpoint string
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func NewHttpProfile() *HttpProfile {
|
||||
return &HttpProfile{
|
||||
ReqMethod: "POST",
|
||||
ReqTimeout: 60,
|
||||
Endpoint: "",
|
||||
Protocol: "HTTPS",
|
||||
}
|
||||
}
|
75
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/sign.go
generated
vendored
Normal file
75
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/sign.go
generated
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
|
||||
)
|
||||
|
||||
const (
|
||||
SHA256 = "HmacSHA256"
|
||||
SHA1 = "HmacSHA1"
|
||||
)
|
||||
|
||||
func Sign(s, secretKey, method string) string {
|
||||
hashed := hmac.New(sha1.New, []byte(secretKey))
|
||||
if method == SHA256 {
|
||||
hashed = hmac.New(sha256.New, []byte(secretKey))
|
||||
}
|
||||
hashed.Write([]byte(s))
|
||||
|
||||
return base64.StdEncoding.EncodeToString(hashed.Sum(nil))
|
||||
}
|
||||
|
||||
func signRequest(request tchttp.Request, credential *Credential, method string) (err error) {
|
||||
if method != SHA256 {
|
||||
method = SHA1
|
||||
}
|
||||
checkAuthParams(request, credential, method)
|
||||
s := getStringToSign(request)
|
||||
signature := Sign(s, credential.SecretKey, method)
|
||||
request.GetParams()["Signature"] = signature
|
||||
return
|
||||
}
|
||||
|
||||
func checkAuthParams(request tchttp.Request, credential *Credential, method string) {
|
||||
params := request.GetParams()
|
||||
credentialParams := credential.GetCredentialParams()
|
||||
for key, value := range credentialParams {
|
||||
params[key] = value
|
||||
}
|
||||
params["SignatureMethod"] = method
|
||||
delete(params, "Signature")
|
||||
}
|
||||
|
||||
func getStringToSign(request tchttp.Request) string {
|
||||
method := request.GetHttpMethod()
|
||||
domain := request.GetDomain()
|
||||
path := request.GetPath()
|
||||
|
||||
text := method + domain + path + "?"
|
||||
|
||||
params := request.GetParams()
|
||||
// sort params
|
||||
keys := make([]string, 0, len(params))
|
||||
for k, _ := range params {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for i := range keys {
|
||||
k := keys[i]
|
||||
if params[k] == "" {
|
||||
continue
|
||||
}
|
||||
text += fmt.Sprintf("%v=%v&", strings.Replace(k, "_", ".", -1), params[k])
|
||||
}
|
||||
text = text[:len(text)-1]
|
||||
return text
|
||||
}
|
47
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/types.go
generated
vendored
Normal file
47
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/types.go
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
package common
|
||||
|
||||
func IntPtr(v int) *int {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Int64Ptr(v int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func UintPtr(v uint) *uint {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Uint64Ptr(v uint64) *uint64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func Float64Ptr(v float64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func StringPtr(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
func StringValues(ptrs []*string) []string {
|
||||
values := make([]string, len(ptrs))
|
||||
for i := 0; i < len(ptrs); i++ {
|
||||
if ptrs[i] != nil {
|
||||
values[i] = *ptrs[i]
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func StringPtrs(vals []string) []*string {
|
||||
ptrs := make([]*string, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func BoolPtr(v bool) *bool {
|
||||
return &v
|
||||
}
|
1679
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312/client.go
generated
vendored
Normal file
1679
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2771
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312/models.go
generated
vendored
Normal file
2771
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312/models.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3079
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312/client.go
generated
vendored
Normal file
3079
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
5024
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312/models.go
generated
vendored
Normal file
5024
vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312/models.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -1552,6 +1552,42 @@
|
|||
"version": "v1.2.1",
|
||||
"versionExact": "v1.2.1"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Tq2BiJUqHH6rryU5xGdD/WQlxSk=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "q+o6CYdmE6zUexIu50LJ8m5V9M8=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "0ZnDtNVslrK0P9j01yquQ0qg4rs=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "C2Ft9SlvvWAuKTymaJxyphqlTY0=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "HtMaGZy24OmXN85TYpsLpfWFNr0=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "Maw/I5DN4rRUO4WEenrg4hgPDJA=",
|
||||
"path": "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312",
|
||||
"revision": "649583d76d09eff104d18aae17490d2cbf01b373",
|
||||
"revisionTime": "2018-10-19T12:31:15Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "GQ9bu6PuydK3Yor1JgtVKUfEJm8=",
|
||||
"path": "github.com/tent/http-link-go",
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
---
|
||||
description: |
|
||||
The `tencentcloud-cvm` Packer builder plugin provide the capability to build
|
||||
customized images based on an existing base images.
|
||||
layout: docs
|
||||
page_title: Tencentcloud Image Builder
|
||||
sidebar_current: 'docs-builders-tencentcloud-ecs'
|
||||
---
|
||||
|
||||
# Tencentcloud Image Builder
|
||||
|
||||
Type: `tencentcloud-cvm`
|
||||
|
||||
The `tencentcloud-cvm` Packer builder plugin provide the capability to build
|
||||
customized images based on an existing base images.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
The following configuration options are available for building Tencentcloud images.
|
||||
In addition to the options listed here,
|
||||
a [communicator](/docs/templates/communicator.html) can be configured for this
|
||||
builder.
|
||||
|
||||
### Required:
|
||||
|
||||
- `secret_id` (string) - Tencentcloud secret id. You should set it directly,
|
||||
or set the `TENCENTCLOUD_ACCESS_KEY` environment variable.
|
||||
|
||||
- `secret_key` (string) - Tencentcloud secret key. You should set it directly,
|
||||
or set the `TENCENTCLOUD_SECRET_KEY` environment variable.
|
||||
|
||||
- `region` (string) - The region where your cvm will be launch. You should
|
||||
reference [Region and Zone](https://cloud.tencent.com/document/product/213/6091)
|
||||
for parameter taking.
|
||||
|
||||
- `zone` (string) - The zone where your cvm will be launch. You should
|
||||
reference [Region and Zone](https://cloud.tencent.com/document/product/213/6091)
|
||||
for parameter taking.
|
||||
|
||||
- `instance_type` (string) - The instance type your cvm will be launched by.
|
||||
You should reference [Instace Type](https://cloud.tencent.com/document/product/213/11518)
|
||||
for parameter taking.
|
||||
|
||||
- `source_image_id` (string) - The base image id of Image you want to create
|
||||
your customized image from.
|
||||
|
||||
- `image_name` (string) - The name you want to create your customize image,
|
||||
it should be composed of no more than 20 characters, of letters, numbers
|
||||
or minus sign.
|
||||
|
||||
### Optional:
|
||||
|
||||
- `force_poweroff` (boolean) - Whether to force power off cvm when create image.
|
||||
Default value is `false`.
|
||||
|
||||
Your cvm will try to shutdown normally; if shutdown failed and `force_poweroff`
|
||||
set, your cvm will be powered off, otherwise task will fail.
|
||||
|
||||
- `image_description` (string) - Image description.
|
||||
|
||||
- `reboot` (boolean) - Whether shutdown cvm to create Image. Default value is
|
||||
`false`.
|
||||
|
||||
If `reboot` is not set and cvm is running, create image task will fail.
|
||||
|
||||
- `sysprep` (boolean) - Whether enable Sysprep during creating windows image.
|
||||
|
||||
- `image_copy_regions` (array of strings) - regions that will be copied to after
|
||||
your image created.
|
||||
|
||||
- `image_share_accounts` (array of strings) - accounts that will be shared to
|
||||
after your image created.
|
||||
|
||||
- `skip_region_validation` (boolean) - Do not check region and zone when validate.
|
||||
|
||||
- `associate_public_ip_address` (boolean) - Whether allocate public ip to your cvm.
|
||||
Default value is `false`.
|
||||
|
||||
If not set, you could access your cvm from the same vpc.
|
||||
|
||||
- `instance_name` (string) - Instance name.
|
||||
|
||||
- `disk_type` (string) - Root disk type your cvm will be launched by. you could
|
||||
reference [Disk Type](https://cloud.tencent.com/document/api/213/15753#SystemDisk)
|
||||
for parameter taking.
|
||||
|
||||
- `disk_size` (number) - Root disk size your cvm will be launched by. values range(in GB):
|
||||
- LOCAL_BASIC: 50
|
||||
- Other: 50 ~ 1000 (need whitelist if > 50)
|
||||
|
||||
- `vpc_id` - Specify vpc your cvm will be launched by.
|
||||
|
||||
- `vpc_name` - Specify vpc name you will create. if `vpc_id` is not set, packer will
|
||||
create a vpc for you named this parameter.
|
||||
|
||||
- `cidr_block` (boolean) - Specify cider block of the vpc you will create if `vpc_id` not set
|
||||
|
||||
- `subnet_id` - Specify subnet your cvm will be launched by.
|
||||
|
||||
- 'subnet_name' - Specify subnet name you will create. if `subnet_id` is not set, packer will
|
||||
create a subnet for you named this parameter.
|
||||
|
||||
- `subnect_cidr_block` (boolean) - Specify cider block of the subnet you will create if
|
||||
`subnet_id` not set
|
||||
|
||||
- `internet_max_bandwidth_out` (number) - Max bandwidth out your cvm will be launched by(in MB).
|
||||
values can be set between 1 ~ 100.
|
||||
|
||||
- `security_group_id` - Specify security group your cvm will be launched by.
|
||||
|
||||
- `security_group_name` - Specify security name you will create if `security_group_id` not set.
|
||||
|
||||
- `user_data` - userdata.
|
||||
|
||||
- `user_data_file` - userdata file.
|
||||
|
||||
- `host_name` - host name.
|
||||
|
||||
## Basic Example
|
||||
|
||||
Here is a basic example for Tencentcloud.
|
||||
|
||||
``` json
|
||||
{
|
||||
"variables": {
|
||||
"secret_id": "{{env `TENCENTCLOUD_ACCESS_KEY`}}",
|
||||
"secret_key": "{{env `TENCENTCLOUD_SECRET_KEY`}}"
|
||||
},
|
||||
"builders": [{
|
||||
"type": "tencentcloud-cvm",
|
||||
"secret_id": "{{user `secret_id`}}",
|
||||
"secret_key": "{{user `secret_key`}}",
|
||||
"region": "ap-guangzhou",
|
||||
"zone": "ap-guangzhou-3",
|
||||
"instance_type": "S3.SMALL1",
|
||||
"source_image_id": "img-oikl1tzv",
|
||||
"ssh_username" : "root",
|
||||
"image_name": "packerTest2",
|
||||
"packer_debug": true,
|
||||
"associate_public_ip_address": true
|
||||
}],
|
||||
"provisioners": [{
|
||||
"type": "shell",
|
||||
"inline": [
|
||||
"sleep 30",
|
||||
"yum install redis.x86_64 -y"
|
||||
]
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
See the
|
||||
[examples/tencentcloud](https://github.com/hashicorp/packer/tree/master/examples/tencentcloud)
|
||||
folder in the packer project for more examples.
|
Loading…
Reference in New Issue