diff --git a/.github/workflows/notify-integration-release-via-tag.yaml b/.github/workflows/notify-integration-release-via-tag.yaml index a99a2aef..9c9a17c6 100644 --- a/.github/workflows/notify-integration-release-via-tag.yaml +++ b/.github/workflows/notify-integration-release-via-tag.yaml @@ -1,55 +1,55 @@ -name: Notify Integration Release (Tag) -on: - push: - tags: - - '*.*.*' # Proper releases -jobs: - strip-version: - runs-on: ubuntu-latest - outputs: - packer-version: ${{ steps.strip.outputs.packer-version }} - steps: - - name: Strip leading v from version tag - id: strip - env: - REF: ${{ github.ref_name }} - run: | - echo "packer-version=$(echo "$REF" | sed -E 's/v?([0-9]+\.[0-9]+\.[0-9]+)/\1/')" >> "$GITHUB_OUTPUT" - notify-release: - needs: - - strip-version - runs-on: ubuntu-latest - steps: - - name: Checkout this repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - with: - ref: ${{ github.ref }} - # Ensure that Docs are Compiled - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - - shell: bash - run: make generate - - shell: bash - run: | - uncommitted="$(git status -s)" - if [[ -z "$uncommitted" ]]; then - echo "OK" - else - echo "Docs have been updated, but the compiled docs have not been committed." - echo "Run 'make generate', and commit the result to resolve this error." - echo "Generated but uncommitted files:" - echo "$uncommitted" - exit 1 - fi - # Perform the Release - - name: Checkout integration-release-action - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - with: - repository: hashicorp/integration-release-action - path: ./integration-release-action - - name: Notify Release - uses: ./integration-release-action - with: - integration_identifier: "packer/hashicorp/tencentcloud" - release_version: ${{ needs.strip-version.outputs.packer-version }} - release_sha: ${{ github.ref }} - github_token: ${{ secrets.GITHUB_TOKEN }} +# name: Notify Integration Release (Tag) +# on: +# push: +# tags: +# - '*.*.*' # Proper releases +# jobs: +# strip-version: +# runs-on: ubuntu-latest +# outputs: +# packer-version: ${{ steps.strip.outputs.packer-version }} +# steps: +# - name: Strip leading v from version tag +# id: strip +# env: +# REF: ${{ github.ref_name }} +# run: | +# echo "packer-version=$(echo "$REF" | sed -E 's/v?([0-9]+\.[0-9]+\.[0-9]+)/\1/')" >> "$GITHUB_OUTPUT" +# notify-release: +# needs: +# - strip-version +# runs-on: ubuntu-latest +# steps: +# - name: Checkout this repo +# uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 +# with: +# ref: ${{ github.ref }} +# # Ensure that Docs are Compiled +# - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 +# - shell: bash +# run: make generate +# - shell: bash +# run: | +# uncommitted="$(git status -s)" +# if [[ -z "$uncommitted" ]]; then +# echo "OK" +# else +# echo "Docs have been updated, but the compiled docs have not been committed." +# echo "Run 'make generate', and commit the result to resolve this error." +# echo "Generated but uncommitted files:" +# echo "$uncommitted" +# exit 1 +# fi +# # Perform the Release +# - name: Checkout integration-release-action +# uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 +# with: +# repository: hashicorp/integration-release-action +# path: ./integration-release-action +# - name: Notify Release +# uses: ./integration-release-action +# with: +# integration_identifier: "packer/hashicorp/tencentcloud" +# release_version: ${{ needs.strip-version.outputs.packer-version }} +# release_sha: ${{ github.ref }} +# github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec35d84b..0e5870b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,26 +38,26 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@v4 - name: Unshallow run: git fetch --prune --unshallow - name: Set up Go - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + uses: actions/setup-go@v5 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Describe plugin id: plugin_describe run: echo "api_version=$(go run . describe | jq -r '.api_version')" >> "$GITHUB_OUTPUT" - - name: Install signore - uses: hashicorp/setup-signore-package@v1 + # - name: Install signore + # uses: hashicorp/setup-signore-package@v1 - name: Run GoReleaser - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + uses: goreleaser/goreleaser-action@v6 with: version: latest args: release --clean --timeout 120m env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} API_VERSION: ${{ steps.plugin_describe.outputs.api_version }} - SIGNORE_CLIENT_ID: ${{ secrets.SIGNORE_CLIENT_ID }} - SIGNORE_CLIENT_SECRET: ${{ secrets.SIGNORE_CLIENT_SECRET }} - SIGNORE_SIGNER: ${{ secrets.SIGNORE_SIGNER }} + # SIGNORE_CLIENT_ID: ${{ secrets.SIGNORE_CLIENT_ID }} + # SIGNORE_CLIENT_SECRET: ${{ secrets.SIGNORE_CLIENT_SECRET }} + # SIGNORE_SIGNER: ${{ secrets.SIGNORE_SIGNER }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 4e2db89a..e5e53b50 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -93,11 +93,11 @@ archives: checksum: name_template: '{{ .ProjectName }}_v{{ .Version }}_SHA256SUMS' algorithm: sha256 -signs: - - cmd: signore - args: ["sign", "--dearmor", "--file", "${artifact}", "--out", "${signature}"] - artifacts: checksum - signature: ${artifact}.sig +# signs: +# - cmd: signore +# args: ["sign", "--dearmor", "--file", "${artifact}", "--out", "${signature}"] +# artifacts: checksum +# signature: ${artifact}.sig changelog: use: github-native diff --git a/.web-docs/components/builder/cvm/README.md b/.web-docs/components/builder/cvm/README.md index 227bbb61..ea04312c 100644 --- a/.web-docs/components/builder/cvm/README.md +++ b/.web-docs/components/builder/cvm/README.md @@ -24,10 +24,6 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can reference [Region and Zone](https://intl.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://intl.cloud.tencent.com/document/product/213/6091) - for parameter taking. - @@ -37,6 +33,10 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can You should reference [Instance Type](https://intl.cloud.tencent.com/document/product/213/11518) for parameter taking. +- `zone` (string) - The zone where your cvm will be launch. You should + reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091) + for parameter taking. + @@ -59,6 +59,12 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can - `vpc_endpoint` (string) - The endpoint you want to reach the cloud endpoint, if tce cloud you should set a tce vpc endpoint. +- `tag_endpoint` (string) - The endpoint you want to reach the cloud endpoint, + if tce cloud you should set a tce tag endpoint. + +- `org_endpoint` (string) - The endpoint you want to reach the cloud endpoint, + if tce cloud you should set a tce organization endpoint. + - `security_token` (string) - STS access token, can be set through template or by exporting as environment variable such as `export TENCENTCLOUD_SECURITY_TOKEN=value`. @@ -101,6 +107,18 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can - `image_tags` (map[string]string) - Key/value pair tags that will be applied to the resulting image. +- `snapshot_tags` (map[string]string) - Key/value pair tags that will be applied to snapshot. + +- `skip_create_image` (bool) - Skip creating an image. When set to true, you don't need to enter target image information, share, copy, etc. The default value is false. + +- `is_share_org_members` (bool) - After creating the image, + whether to share it with other accounts in the organization + where the current account is located. + The image can be copied to a maximum of 50 accounts, + with ImageShareAccounts being the priority. + +- `image_family` (string) - Image family. Example value: business-daily-update. + @@ -110,10 +128,16 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can Default value is `false`. - `source_image_id` (string) - The base image id of Image you want to create + You can also specify `source_image_family`. If both `source_image` and `source_image_family` are specified, `source_image` takes precedence. your customized image from. - `source_image_name` (string) - The base image name of Image you want to create your - customized image from.Conflict with SourceImageId. + customized image from.Conflict with SourceImageId and SourceImageName. + +- `source_image_family` (string) - The source image family to use to create the new image from. + The image family always returns its latest image that is not deprecated. + Conflict with SourceImageId and SourceImageName. It takes effect when SourceImageId and SourceImageName are empty. + Example value: business-daily-update. - `instance_charge_type` (string) - Charge type of cvm, values can be `POSTPAID_BY_HOUR` (default) `SPOTPAID` @@ -134,7 +158,7 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can type for all data disks, and each data disk size will use the origin value in source image. The data disks allow for the following argument: - - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM` and `CLOUD_SSD`. + - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM`, `CLOUD_SSD`, `CLOUD_BSSD`, `CLOUD_HSSD` and `CLOUD_TSSD`. - `disk_size` - Size of the data disk. - `disk_snapshot_id` - Id of the snapshot for a data disk. @@ -180,6 +204,8 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can [`dynamic_block`](/packer/docs/templates/hcl_templates/expressions#dynamic-blocks) will allow you to create those programatically. +- `cdc_id` (string) - Support for local dedicated cluster CDC + - `ssh_private_ip` (bool) - SSH Private Ip diff --git a/builder/tencentcloud/cvm/access_config.go b/builder/tencentcloud/cvm/access_config.go index 66a376d9..f5992696 100644 --- a/builder/tencentcloud/cvm/access_config.go +++ b/builder/tencentcloud/cvm/access_config.go @@ -7,19 +7,11 @@ package cvm import ( - "context" - "encoding/json" "fmt" - "io/ioutil" "os" - "runtime" "strconv" - "strings" "github.com/hashicorp/packer-plugin-sdk/template/interpolate" - "github.com/mitchellh/go-homedir" - cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" - vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312" ) const ( @@ -81,16 +73,18 @@ type TencentCloudAccessConfig struct { // reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091) // for parameter taking. Region string `mapstructure:"region" required:"true"` - // The zone where your cvm will be launch. You should - // reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091) - // for parameter taking. - Zone string `mapstructure:"zone" required:"true"` // The endpoint you want to reach the cloud endpoint, // if tce cloud you should set a tce cvm endpoint. CvmEndpoint string `mapstructure:"cvm_endpoint" required:"false"` // The endpoint you want to reach the cloud endpoint, // if tce cloud you should set a tce vpc endpoint. VpcEndpoint string `mapstructure:"vpc_endpoint" required:"false"` + // The endpoint you want to reach the cloud endpoint, + // if tce cloud you should set a tce tag endpoint. + TagEndpoint string `mapstructure:"tag_endpoint" required:"false"` + // The endpoint you want to reach the cloud endpoint, + // if tce cloud you should set a tce organization endpoint. + OrgEndpoint string `mapstructure:"org_endpoint" required:"false"` // The region validation can be skipped if this value is true, the default // value is false. skipValidation bool @@ -131,47 +125,42 @@ type TencentCloudAccessRole struct { SessionDuration int `mapstructure:"session_duration" required:"false"` } -func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) { +func (cf *TencentCloudAccessConfig) Client() (map[string]interface{}, error) { var ( - err error - cvm_client *cvm.Client - vpc_client *vpc.Client - resp *cvm.DescribeZonesResponse + err error + // cvm_client *cvm.Client + // vpc_client *vpc.Client + // tag_client *tag.Client + // org_client *org.Client + // cam_client *cam.Client ) if err = cf.validateRegion(); err != nil { - return nil, nil, err + return nil, err } - if cf.Zone == "" { - return nil, nil, fmt.Errorf("parameter zone must be set") + clientMap := map[string]interface{}{} + if clientMap["cvm_client"], err = NewCvmClient(cf); err != nil { + return nil, err } - if cvm_client, err = NewCvmClient(cf); err != nil { - return nil, nil, err + if clientMap["vpc_client"], err = NewVpcClient(cf); err != nil { + return nil, err } - if vpc_client, err = NewVpcClient(cf); err != nil { - return nil, nil, err + if clientMap["tag_client"], err = NewTagClient(cf); err != nil { + return nil, err } - ctx := context.TODO() - err = Retry(ctx, func(ctx context.Context) error { - var e error - resp, e = cvm_client.DescribeZones(nil) - return e - }) - if err != nil { - return nil, nil, err + if clientMap["org_client"], err = NewOrgClient(cf); err != nil { + return nil, err } - for _, zone := range resp.Response.ZoneSet { - if cf.Zone == *zone.Zone { - return cvm_client, vpc_client, nil - } + if clientMap["cam_client"], err = NewCamClient(cf); err != nil { + return nil, err } - return nil, nil, fmt.Errorf("unknown zone: %s", cf.Zone) + return clientMap, nil } func (cf *TencentCloudAccessConfig) Prepare(ctx *interpolate.Context) []error { @@ -181,9 +170,9 @@ func (cf *TencentCloudAccessConfig) Prepare(ctx *interpolate.Context) []error { errs = append(errs, err) } - if (cf.CvmEndpoint != "" && cf.VpcEndpoint == "") || - (cf.CvmEndpoint == "" && cf.VpcEndpoint != "") { - errs = append(errs, fmt.Errorf("parameter cvm_endpoint and vpc_endpoint must be set simultaneously")) + if !((cf.CvmEndpoint == "" && cf.VpcEndpoint == "" && cf.TagEndpoint == "") || + (cf.CvmEndpoint != "" && cf.VpcEndpoint != "" && cf.TagEndpoint != "")) { + errs = append(errs, fmt.Errorf("parameter cvm_endpoint, vpc_endpoint and tag_endpoint must be set simultaneously")) } if cf.Region == "" { @@ -222,35 +211,23 @@ func (cf *TencentCloudAccessConfig) Config() error { cf.SharedCredentialsDir = os.Getenv(PACKER_SHARED_CREDENTIALS_DIR) } - var value map[string]interface{} - var err error - getProviderConfig := func(key string) string { - var str string - if value != nil { - if v, ok := value[key]; ok { - str = v.(string) - } - } - return str - } - if cf.SecretId == "" || cf.SecretKey == "" { - value, err = loadConfigProfile(cf) + profile, err := loadConfigProfile(cf) if err != nil { return err } if cf.SecretId == "" { - cf.SecretId = getProviderConfig("secretId") + cf.SecretId = profile.SecretId } if cf.SecretKey == "" { - cf.SecretKey = getProviderConfig("secretKey") + cf.SecretKey = profile.SecretKey } if cf.SecurityToken == "" { - cf.SecurityToken = getProviderConfig("securityToken") + cf.SecurityToken = profile.Token } if cf.Region == "" { - cf.Region = getProviderConfig("region") + cf.Region = profile.Region } } @@ -293,33 +270,29 @@ func (cf *TencentCloudAccessConfig) Config() error { } if cf.AssumeRole.RoleArn == "" || cf.AssumeRole.SessionName == "" { - value, err = loadConfigProfile(cf) + profile, err := loadConfigProfile(cf) if err != nil { return err } if cf.AssumeRole.RoleArn == "" { - roleArn := getProviderConfig("role-arn") + roleArn := profile.RoleArn if roleArn != "" { cf.AssumeRole.RoleArn = roleArn } } if cf.AssumeRole.SessionName == "" { - sessionName := getProviderConfig("role-session-name") + sessionName := profile.RoleSessionName if sessionName != "" { cf.AssumeRole.SessionName = sessionName } } if cf.AssumeRole.SessionDuration == 0 { - duration := getProviderConfig("role-session-duration") - if duration != "" { - durationInt, err := strconv.Atoi(duration) - if err != nil { - return err - } - cf.AssumeRole.SessionDuration = durationInt + duration := profile.RoleSessionDuration + if duration != 0 { + cf.AssumeRole.SessionDuration = int(duration) } } } @@ -344,104 +317,3 @@ func validRegion(region string) error { return fmt.Errorf("unknown region: %s", region) } - -func getProfilePatch(cf *TencentCloudAccessConfig) (string, string, error) { - var ( - profile string - sharedCredentialsDir string - credentialPath string - configurePath string - ) - - if cf.Profile != "" { - profile = cf.Profile - } else { - profile = DEFAULT_PROFILE - } - - if cf.SharedCredentialsDir != "" { - sharedCredentialsDir = cf.SharedCredentialsDir - } - - tmpSharedCredentialsDir, err := homedir.Expand(sharedCredentialsDir) - if err != nil { - return "", "", err - } - - if tmpSharedCredentialsDir == "" { - credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("HOME"), profile) - configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("HOME"), profile) - if runtime.GOOS == "windows" { - credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("USERPROFILE"), profile) - configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("USERPROFILE"), profile) - } - } else { - credentialPath = fmt.Sprintf("%s/%s.credential", tmpSharedCredentialsDir, profile) - configurePath = fmt.Sprintf("%s/%s.configure", tmpSharedCredentialsDir, profile) - } - - return credentialPath, configurePath, nil -} - -func loadConfigProfile(cf *TencentCloudAccessConfig) (map[string]interface{}, error) { - var ( - credentialPath string - configurePath string - ) - - credentialPath, configurePath, err := getProfilePatch(cf) - if err != nil { - return nil, err - } - - packerConfig := make(map[string]interface{}) - _, err = os.Stat(credentialPath) - if !os.IsNotExist(err) { - data, err := ioutil.ReadFile(credentialPath) - if err != nil { - return nil, err - } - - config := map[string]interface{}{} - err = json.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("credential file unmarshal failed, %s", err) - } - - for k, v := range config { - packerConfig[k] = strings.TrimSpace(v.(string)) - } - } else { - return nil, fmt.Errorf("please set a valid secret_id and secret_key or shared_credentials_dir, %s", err) - } - _, err = os.Stat(configurePath) - if !os.IsNotExist(err) { - data, err := ioutil.ReadFile(configurePath) - if err != nil { - return nil, err - } - - config := map[string]interface{}{} - err = json.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("configure file unmarshal failed, %s", err) - } - - outerLoop: - for k, v := range config { - if k == "_sys_param" { - tmpMap := v.(map[string]interface{}) - for tmpK, tmpV := range tmpMap { - if tmpK == "region" { - packerConfig[tmpK] = strings.TrimSpace(tmpV.(string)) - break outerLoop - } - } - } - } - } else { - return nil, fmt.Errorf("please set a valid region or shared_credentials_dir, %s", err) - } - - return packerConfig, nil -} diff --git a/builder/tencentcloud/cvm/builder.go b/builder/tencentcloud/cvm/builder.go index cda74a0c..0ac3a8c0 100644 --- a/builder/tencentcloud/cvm/builder.go +++ b/builder/tencentcloud/cvm/builder.go @@ -17,6 +17,11 @@ import ( packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template/config" "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" + vm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + org "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization/v20210331" + tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" + vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312" ) const BuilderId = "tencent.cloud" @@ -75,22 +80,31 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) { } func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) (packersdk.Artifact, error) { - cvmClient, vpcClient, err := b.config.Client() + clientMap, 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("cvm_client", clientMap["cvm_client"].(*vm.Client)) + state.Put("vpc_client", clientMap["vpc_client"].(*vpc.Client)) + state.Put("tag_client", clientMap["tag_client"].(*tag.Client)) + state.Put("org_client", clientMap["org_client"].(*org.Client)) + state.Put("cam_client", clientMap["cam_client"].(*cam.Client)) state.Put("hook", hook) state.Put("ui", ui) // Build the steps var steps []multistep.Step steps = []multistep.Step{ - &stepPreValidate{}, + &stepPreValidate{ + b.config.SkipCreateImage, + }, + &stepCheckSourceImageFamily{ + b.config.SourceImageId, + b.config.SourceImageName, + }, &stepCheckSourceImage{ b.config.SourceImageId, }, @@ -109,6 +123,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) SubnetCidrBlock: b.config.SubnectCidrBlock, SubnetName: b.config.SubnetName, Zone: b.config.Zone, + CdcId: b.config.CdcId, }, &stepConfigSecurityGroup{ SecurityGroupId: b.config.SecurityGroupId, @@ -132,6 +147,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) AssociatePublicIpAddress: b.config.AssociatePublicIpAddress, CamRoleName: b.config.CamRoleName, Tags: b.config.RunTags, + CdcId: b.config.CdcId, }, &communicator.StepConnect{ Config: &b.config.TencentCloudRunConfig.Comm, @@ -145,13 +161,17 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) // We need this step to detach keypair from instance, otherwise // it always fails to delete the key. &stepDetachTempKeyPair{}, - &stepCreateImage{}, + &stepCreateImage{ + SkipCreateImage: b.config.SkipCreateImage, + }, &stepShareImage{ b.config.ImageShareAccounts, + b.config.IsShareOrgMembers, }, &stepCopyImage{ - DesinationRegions: b.config.ImageCopyRegions, - SourceRegion: b.config.Region, + DestinationRegions: b.config.ImageCopyRegions, + SourceRegion: b.config.Region, + SkipCreateImage: b.config.SkipCreateImage, }, } @@ -169,7 +189,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook) artifact := &Artifact{ TencentCloudImages: state.Get("tencentcloudimages").(map[string]string), BuilderIdValue: BuilderId, - Client: cvmClient, + Client: clientMap["cvm_client"].(*vm.Client), StateData: map[string]interface{}{"generated_data": state.Get("generated_data")}, } diff --git a/builder/tencentcloud/cvm/builder.hcl2spec.go b/builder/tencentcloud/cvm/builder.hcl2spec.go index 51eadf70..1009e462 100644 --- a/builder/tencentcloud/cvm/builder.hcl2spec.go +++ b/builder/tencentcloud/cvm/builder.hcl2spec.go @@ -22,9 +22,10 @@ type FlatConfig struct { SecretId *string `mapstructure:"secret_id" required:"true" cty:"secret_id" hcl:"secret_id"` SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"` Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` - Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"` CvmEndpoint *string `mapstructure:"cvm_endpoint" required:"false" cty:"cvm_endpoint" hcl:"cvm_endpoint"` VpcEndpoint *string `mapstructure:"vpc_endpoint" required:"false" cty:"vpc_endpoint" hcl:"vpc_endpoint"` + TagEndpoint *string `mapstructure:"tag_endpoint" required:"false" cty:"tag_endpoint" hcl:"tag_endpoint"` + OrgEndpoint *string `mapstructure:"org_endpoint" required:"false" cty:"org_endpoint" hcl:"org_endpoint"` SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token" hcl:"security_token"` AssumeRole *FlatTencentCloudAccessRole `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"` Profile *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"` @@ -36,9 +37,14 @@ type FlatConfig struct { ImageCopyRegions []string `mapstructure:"image_copy_regions" required:"false" cty:"image_copy_regions" hcl:"image_copy_regions"` ImageShareAccounts []string `mapstructure:"image_share_accounts" required:"false" cty:"image_share_accounts" hcl:"image_share_accounts"` ImageTags map[string]string `mapstructure:"image_tags" required:"false" cty:"image_tags" hcl:"image_tags"` + SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false" cty:"snapshot_tags" hcl:"snapshot_tags"` + SkipCreateImage *bool `mapstructure:"skip_create_image" required:"false" cty:"skip_create_image" hcl:"skip_create_image"` + IsShareOrgMembers *bool `mapstructure:"is_share_org_members" required:"false" cty:"is_share_org_members" hcl:"is_share_org_members"` + ImageFamily *string `mapstructure:"image_family" required:"false" cty:"image_family" hcl:"image_family"` AssociatePublicIpAddress *bool `mapstructure:"associate_public_ip_address" required:"false" cty:"associate_public_ip_address" hcl:"associate_public_ip_address"` SourceImageId *string `mapstructure:"source_image_id" required:"false" cty:"source_image_id" hcl:"source_image_id"` SourceImageName *string `mapstructure:"source_image_name" required:"false" cty:"source_image_name" hcl:"source_image_name"` + SourceImageFamily *string `mapstructure:"source_image_family" required:"false" cty:"source_image_family" hcl:"source_image_family"` InstanceChargeType *string `mapstructure:"instance_charge_type" required:"false" cty:"instance_charge_type" hcl:"instance_charge_type"` InstanceType *string `mapstructure:"instance_type" required:"true" cty:"instance_type" hcl:"instance_type"` InstanceName *string `mapstructure:"instance_name" required:"false" cty:"instance_name" hcl:"instance_name"` @@ -62,6 +68,7 @@ type FlatConfig struct { CamRoleName *string `mapstructure:"cam_role_name" required:"false" cty:"cam_role_name" hcl:"cam_role_name"` RunTags map[string]string `mapstructure:"run_tags" required:"false" cty:"run_tags" hcl:"run_tags"` RunTag []config.FlatKeyValue `mapstructure:"run_tag" required:"false" cty:"run_tag" hcl:"run_tag"` + CdcId *string `mapstructure:"cdc_id" required:"false" cty:"cdc_id" hcl:"cdc_id"` Type *string `mapstructure:"communicator" cty:"communicator" hcl:"communicator"` PauseBeforeConnect *string `mapstructure:"pause_before_connecting" cty:"pause_before_connecting" hcl:"pause_before_connecting"` SSHHost *string `mapstructure:"ssh_host" cty:"ssh_host" hcl:"ssh_host"` @@ -112,6 +119,7 @@ type FlatConfig struct { WinRMInsecure *bool `mapstructure:"winrm_insecure" cty:"winrm_insecure" hcl:"winrm_insecure"` WinRMUseNTLM *bool `mapstructure:"winrm_use_ntlm" cty:"winrm_use_ntlm" hcl:"winrm_use_ntlm"` SSHPrivateIp *bool `mapstructure:"ssh_private_ip" cty:"ssh_private_ip" hcl:"ssh_private_ip"` + Zone *string `mapstructure:"zone" required:"true" cty:"zone" hcl:"zone"` SkipRegionValidation *bool `mapstructure:"skip_region_validation" required:"false" cty:"skip_region_validation" hcl:"skip_region_validation"` } @@ -138,9 +146,10 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "secret_id": &hcldec.AttrSpec{Name: "secret_id", Type: cty.String, Required: false}, "secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false}, "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, - "zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false}, "cvm_endpoint": &hcldec.AttrSpec{Name: "cvm_endpoint", Type: cty.String, Required: false}, "vpc_endpoint": &hcldec.AttrSpec{Name: "vpc_endpoint", Type: cty.String, Required: false}, + "tag_endpoint": &hcldec.AttrSpec{Name: "tag_endpoint", Type: cty.String, Required: false}, + "org_endpoint": &hcldec.AttrSpec{Name: "org_endpoint", Type: cty.String, Required: false}, "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, "assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*FlatTencentCloudAccessRole)(nil).HCL2Spec())}, "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, @@ -152,9 +161,14 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "image_copy_regions": &hcldec.AttrSpec{Name: "image_copy_regions", Type: cty.List(cty.String), Required: false}, "image_share_accounts": &hcldec.AttrSpec{Name: "image_share_accounts", Type: cty.List(cty.String), Required: false}, "image_tags": &hcldec.AttrSpec{Name: "image_tags", Type: cty.Map(cty.String), Required: false}, + "snapshot_tags": &hcldec.AttrSpec{Name: "snapshot_tags", Type: cty.Map(cty.String), Required: false}, + "skip_create_image": &hcldec.AttrSpec{Name: "skip_create_image", Type: cty.Bool, Required: false}, + "is_share_org_members": &hcldec.AttrSpec{Name: "is_share_org_members", Type: cty.Bool, Required: false}, + "image_family": &hcldec.AttrSpec{Name: "image_family", Type: cty.String, Required: false}, "associate_public_ip_address": &hcldec.AttrSpec{Name: "associate_public_ip_address", Type: cty.Bool, Required: false}, "source_image_id": &hcldec.AttrSpec{Name: "source_image_id", Type: cty.String, Required: false}, "source_image_name": &hcldec.AttrSpec{Name: "source_image_name", Type: cty.String, Required: false}, + "source_image_family": &hcldec.AttrSpec{Name: "source_image_family", Type: cty.String, Required: false}, "instance_charge_type": &hcldec.AttrSpec{Name: "instance_charge_type", Type: cty.String, Required: false}, "instance_type": &hcldec.AttrSpec{Name: "instance_type", Type: cty.String, Required: false}, "instance_name": &hcldec.AttrSpec{Name: "instance_name", Type: cty.String, Required: false}, @@ -178,6 +192,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "cam_role_name": &hcldec.AttrSpec{Name: "cam_role_name", Type: cty.String, Required: false}, "run_tags": &hcldec.AttrSpec{Name: "run_tags", Type: cty.Map(cty.String), Required: false}, "run_tag": &hcldec.BlockListSpec{TypeName: "run_tag", Nested: hcldec.ObjectSpec((*config.FlatKeyValue)(nil).HCL2Spec())}, + "cdc_id": &hcldec.AttrSpec{Name: "cdc_id", Type: cty.String, Required: false}, "communicator": &hcldec.AttrSpec{Name: "communicator", Type: cty.String, Required: false}, "pause_before_connecting": &hcldec.AttrSpec{Name: "pause_before_connecting", Type: cty.String, Required: false}, "ssh_host": &hcldec.AttrSpec{Name: "ssh_host", Type: cty.String, Required: false}, @@ -228,6 +243,7 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "winrm_insecure": &hcldec.AttrSpec{Name: "winrm_insecure", Type: cty.Bool, Required: false}, "winrm_use_ntlm": &hcldec.AttrSpec{Name: "winrm_use_ntlm", Type: cty.Bool, Required: false}, "ssh_private_ip": &hcldec.AttrSpec{Name: "ssh_private_ip", Type: cty.Bool, Required: false}, + "zone": &hcldec.AttrSpec{Name: "zone", Type: cty.String, Required: false}, "skip_region_validation": &hcldec.AttrSpec{Name: "skip_region_validation", Type: cty.Bool, Required: false}, } return s diff --git a/builder/tencentcloud/cvm/client.go b/builder/tencentcloud/cvm/client.go index 7f6bef26..5eb73c6b 100644 --- a/builder/tencentcloud/cvm/client.go +++ b/builder/tencentcloud/cvm/client.go @@ -4,10 +4,13 @@ package cvm import ( + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" "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" + org "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization/v20210331" sts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813" + tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312" ) @@ -20,6 +23,9 @@ type TencentCloudClient struct { vpcConn *vpc.Client cvmConn *cvm.Client stsConn *sts.Client + tagConn *tag.Client + orgConn *org.Client + camConn *cam.Client } func (me *TencentCloudClient) UseVpcClient(cpf *profile.ClientProfile) *vpc.Client { @@ -28,7 +34,7 @@ func (me *TencentCloudClient) UseVpcClient(cpf *profile.ClientProfile) *vpc.Clie } me.vpcConn, _ = vpc.NewClient(me.Credential, me.Region, cpf) - // me.vpcConn.WithHttpTransport(&LogRoundTripper{}) + me.vpcConn.WithHttpTransport(&LogRoundTripper{}) return me.vpcConn } @@ -39,6 +45,7 @@ func (me *TencentCloudClient) UseCvmClient(cpf *profile.ClientProfile) *cvm.Clie } me.cvmConn, _ = cvm.NewClient(me.Credential, me.Region, cpf) + me.cvmConn.WithHttpTransport(&LogRoundTripper{}) return me.cvmConn } @@ -50,6 +57,41 @@ func (me *TencentCloudClient) UseStsClient() *sts.Client { cpf := me.ClientProfile me.stsConn, _ = sts.NewClient(me.Credential, me.Region, cpf) + me.stsConn.WithHttpTransport(&LogRoundTripper{}) return me.stsConn } + +func (me *TencentCloudClient) UseTagClient(cpf *profile.ClientProfile) *tag.Client { + if me.tagConn != nil { + return me.tagConn + } + + me.tagConn, _ = tag.NewClient(me.Credential, me.Region, cpf) + me.tagConn.WithHttpTransport(&LogRoundTripper{}) + + return me.tagConn +} + +func (me *TencentCloudClient) UseOrgClient(cpf *profile.ClientProfile) *org.Client { + if me.orgConn != nil { + return me.orgConn + } + + me.orgConn, _ = org.NewClient(me.Credential, me.Region, cpf) + me.orgConn.WithHttpTransport(&LogRoundTripper{}) + + return me.orgConn +} + +func (me *TencentCloudClient) UseCamClient() *cam.Client { + if me.camConn != nil { + return me.camConn + } + + cpf := me.ClientProfile + me.camConn, _ = cam.NewClient(me.Credential, me.Region, cpf) + me.camConn.WithHttpTransport(&LogRoundTripper{}) + + return me.camConn +} diff --git a/builder/tencentcloud/cvm/common.go b/builder/tencentcloud/cvm/common.go index 13ae9cb2..adda19c1 100644 --- a/builder/tencentcloud/cvm/common.go +++ b/builder/tencentcloud/cvm/common.go @@ -14,11 +14,14 @@ import ( "github.com/hashicorp/packer-plugin-sdk/multistep" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/retry" + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + org "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization/v20210331" sts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts/v20180813" + tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" vpc "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc/v20170312" ) @@ -78,6 +81,27 @@ func WaitForImageReady(ctx context.Context, client *cvm.Client, imageName string } } +func AddResourceTag(ctx context.Context, client *tag.Client, resourceName string, tags map[string]string) error { + request := tag.NewModifyResourceTagsRequest() + request.Resource = &resourceName + request.ReplaceTags = make([]*tag.Tag, 0, len(tags)) + for k, v := range tags { + key := k + value := v + replaceTag := &tag.Tag{ + TagKey: &key, + TagValue: &value, + } + request.ReplaceTags = append(request.ReplaceTags, replaceTag) + } + + err := Retry(ctx, func(ctx context.Context) error { + _, e := client.ModifyResourceTags(request) + return e + }) + return err +} + // GetImageByName get image by image name func GetImageByName(ctx context.Context, client *cvm.Client, imageName string) (*cvm.Image, error) { req := cvm.NewDescribeImagesRequest() @@ -143,6 +167,51 @@ func NewVpcClient(cf *TencentCloudAccessConfig) (client *vpc.Client, err error) return } +// UseTagClient returns a new tag client +func NewTagClient(cf *TencentCloudAccessConfig) (client *tag.Client, err error) { + apiV3Conn, err := packerConfigClient(cf) + if err != nil { + return nil, err + } + + tagClientProfile, err := newClientProfile(cf.TagEndpoint) + if err != nil { + return nil, err + } + client = apiV3Conn.UseTagClient(tagClientProfile) + + return +} + +// NewVpcClient returns a new organization client +func NewOrgClient(cf *TencentCloudAccessConfig) (client *org.Client, err error) { + apiV3Conn, err := packerConfigClient(cf) + if err != nil { + return nil, err + } + + orgClientProfile, err := newClientProfile(cf.OrgEndpoint) + if err != nil { + return nil, err + } + + client = apiV3Conn.UseOrgClient(orgClientProfile) + + return +} + +// NewCamClient returns a new cam client +func NewCamClient(cf *TencentCloudAccessConfig) (client *cam.Client, err error) { + apiV3Conn, err := packerConfigClient(cf) + if err != nil { + return nil, err + } + + client = apiV3Conn.UseCamClient() + + return +} + // CheckResourceIdFormat check resource id format func CheckResourceIdFormat(resource string, id string) bool { regex := regexp.MustCompile(fmt.Sprintf("%s-[0-9a-z]{8}$", resource)) @@ -322,3 +391,10 @@ func IntUint64(i int) *uint64 { u := uint64(i) return &u } + +// BuildTagResourceName builds the Tencent Cloud specific name of a resource description. +// The format is `qcs:project_id:service_type:region:account:resource`. +// For more information, go to https://cloud.tencent.com/document/product/598/10606. +func BuildTagResourceName(serviceType, resourceType, region, id string) string { + return fmt.Sprintf("qcs::%s:%s:uin/:%s/%s", serviceType, region, resourceType, id) +} diff --git a/builder/tencentcloud/cvm/image_config.go b/builder/tencentcloud/cvm/image_config.go index 036c30dd..63a9b148 100644 --- a/builder/tencentcloud/cvm/image_config.go +++ b/builder/tencentcloud/cvm/image_config.go @@ -31,13 +31,29 @@ type TencentCloudImageConfig struct { // after your image created. ImageShareAccounts []string `mapstructure:"image_share_accounts" required:"false"` // Key/value pair tags that will be applied to the resulting image. - ImageTags map[string]string `mapstructure:"image_tags" required:"false"` + ImageTags map[string]string `mapstructure:"image_tags" required:"false"` + // Key/value pair tags that will be applied to snapshot. + SnapshotTags map[string]string `mapstructure:"snapshot_tags" required:"false"` skipValidation bool + // Skip creating an image. When set to true, you don't need to enter target image information, share, copy, etc. The default value is false. + SkipCreateImage bool `mapstructure:"skip_create_image" required:"false"` + // After creating the image, + // whether to share it with other accounts in the organization + // where the current account is located. + // The image can be copied to a maximum of 50 accounts, + // with ImageShareAccounts being the priority. + IsShareOrgMembers bool `mapstructure:"is_share_org_members" required:"false"` + // Image family. Example value: business-daily-update. + ImageFamily string `mapstructure:"image_family" required:"false"` } func (cf *TencentCloudImageConfig) Prepare(ctx *interpolate.Context) []error { var errs []error + if cf.SkipCreateImage { + return nil + } + if cf.ImageName == "" { errs = append(errs, fmt.Errorf("image_name must be specified")) } else if utf8.RuneCountInString(cf.ImageName) > 60 { @@ -73,6 +89,9 @@ func (cf *TencentCloudImageConfig) Prepare(ctx *interpolate.Context) []error { if cf.ImageTags == nil { cf.ImageTags = make(map[string]string) } + if cf.SnapshotTags == nil { + cf.SnapshotTags = make(map[string]string) + } if len(errs) > 0 { return errs diff --git a/builder/tencentcloud/cvm/oauth.go b/builder/tencentcloud/cvm/oauth.go new file mode 100644 index 00000000..cc140ffb --- /dev/null +++ b/builder/tencentcloud/cvm/oauth.go @@ -0,0 +1,191 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cvm + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/google/uuid" +) + +const _API_ENDPOINT = "https://cli.cloud.tencent.com" + +func GetOauthConfig(p *Profile) error { + if p.Oauth == nil || p.Oauth.RefreshToken == "" || p.Oauth.OpenId == "" { + return fmt.Errorf("Oauth authentication information is not configured correctly") + } + client := NewAPIClient() + + expired := false + if p.Oauth.ExpiresAt != 0 { + now := time.Now() + futureTime := now.Add(30 * time.Second) + targetTime := time.Unix(p.Oauth.ExpiresAt, 0) + if futureTime.After(targetTime) { + expired = true + } + } + + if expired { + response, err := client.RefreshUserToken(p.Oauth.RefreshToken, p.Oauth.OpenId, p.Oauth.Site) + if err != nil { + return err + } + if response != nil { + if response.AccessToken != "" { + p.Oauth.AccessToken = response.AccessToken + } + if response.ExpiresAt != 0 { + p.Oauth.ExpiresAt = response.ExpiresAt + } + } + } + + // 获取临时token + response, err := client.GetThirdPartyFederationToken(p.Oauth.AccessToken, p.Oauth.Site) + if err != nil { + return err + } + if response != nil { + if response.SecretId != "" { + p.SecretId = response.SecretId + } + if response.SecretKey != "" { + p.SecretKey = response.SecretKey + } + if response.Token != "" { + p.Token = response.Token + } + } + + return nil +} + +type APIClient struct { + Client *http.Client +} + +// 创建新的APIClient +func NewAPIClient() *APIClient { + return &APIClient{ + Client: &http.Client{ + Timeout: 10 * time.Second, + }, + } +} + +// GetThirdPartyFederationToken Obtaining a temporary user certificate +func (c *APIClient) GetThirdPartyFederationToken(accessToken, site string) (*GetTempCredResponse, error) { + apiEndpoint := _API_ENDPOINT + "/get_temp_cred" + traceId := uuid.New().String() + + body := GetTempCredRequest{ + TraceId: traceId, + AccessToken: accessToken, + Site: site, + } + + jsonData, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %v", err) + } + + req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + response := &GetTempCredResponse{} + if err := json.NewDecoder(resp.Body).Decode(response); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + if response.Error != "" { + return nil, fmt.Errorf("get_temp_cred: %s", response.Error) + } + + return response, err +} + +// RefreshUserToken Refresh user third-party access_token +func (c *APIClient) RefreshUserToken(refToken, openId, site string) (*RefreshTokenResponse, error) { + apiEndpoint := _API_ENDPOINT + "/refresh_user_token" + + traceId := uuid.New().String() + + body := RefreshTokenRequest{ + TraceId: traceId, + RefreshToken: refToken, + OpenId: openId, + Site: site, + } + + jsonData, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %v", err) + } + + // 创建POST请求 + req, err := http.NewRequest("POST", apiEndpoint, bytes.NewBuffer(jsonData)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %v", err) + } + + req.Header.Set("Content-Type", "application/json") + + resp, err := c.Client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + response := &RefreshTokenResponse{} + if err := json.NewDecoder(resp.Body).Decode(response); err != nil { + return nil, fmt.Errorf("failed to decode response body: %v", err) + } + + if response.Error != "" { + return nil, fmt.Errorf("refresh_user_token: %s", response.Error) + } + return response, nil +} + +type RefreshTokenRequest struct { + TraceId string `json:"TraceId"` + RefreshToken string `json:"RefreshToken"` + OpenId string `json:"OpenId"` + Site string `json:"Site"` +} + +type RefreshTokenResponse struct { + AccessToken string `json:"AccessToken"` + ExpiresAt int64 `json:"ExpiresAt"` + Error string `json:"Error"` +} + +type GetTempCredRequest struct { + TraceId string `json:"TraceId"` + AccessToken string `json:"AccessToken"` + Site string `json:"Site"` +} + +type GetTempCredResponse struct { + SecretId string `json:"SecretId"` + SecretKey string `json:"SecretKey"` + Token string `json:"Token"` + ExpiresAt int64 `json:"ExpiresAt"` + Error string `json:"Error"` +} diff --git a/builder/tencentcloud/cvm/profile.go b/builder/tencentcloud/cvm/profile.go new file mode 100644 index 00000000..ca37d5a1 --- /dev/null +++ b/builder/tencentcloud/cvm/profile.go @@ -0,0 +1,139 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cvm + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "runtime" + "strings" + + "github.com/mitchellh/go-homedir" +) + +type Profile struct { + Type string `json:"type,omitempty"` + Region string + SecretId string `json:"secretId,omitempty"` + SecretKey string `json:"secretKey,omitempty"` + Token string `json:"token,omitempty"` + ExpiresAt int64 `json:"expiresAt,omitempty"` + RoleArn string `json:"role-arn,omitempty"` + RoleSessionName string `json:"role-session-name,omitempty"` + RoleSessionDuration int64 `json:"role-session-duration,omitempty"` + Oauth *Oauth `json:"oauth,omitempty"` +} + +type Oauth struct { + OpenId string `json:"openId,omitempty"` + AccessToken string `json:"accessToken,omitempty"` + ExpiresAt int64 `json:"expiresAt,omitempty"` + RefreshToken string `json:"refreshToken,omitempty"` + Site string `json:"site,omitempty"` +} + +func getProfilePatch(cf *TencentCloudAccessConfig) (string, string, error) { + var ( + profile string + sharedCredentialsDir string + credentialPath string + configurePath string + ) + + if cf.Profile != "" { + profile = cf.Profile + } else { + profile = DEFAULT_PROFILE + } + + if cf.SharedCredentialsDir != "" { + sharedCredentialsDir = cf.SharedCredentialsDir + } + + tmpSharedCredentialsDir, err := homedir.Expand(sharedCredentialsDir) + if err != nil { + return "", "", err + } + + if tmpSharedCredentialsDir == "" { + credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("HOME"), profile) + configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("HOME"), profile) + if runtime.GOOS == "windows" { + credentialPath = fmt.Sprintf("%s/.tccli/%s.credential", os.Getenv("USERPROFILE"), profile) + configurePath = fmt.Sprintf("%s/.tccli/%s.configure", os.Getenv("USERPROFILE"), profile) + } + } else { + credentialPath = fmt.Sprintf("%s/%s.credential", tmpSharedCredentialsDir, profile) + configurePath = fmt.Sprintf("%s/%s.configure", tmpSharedCredentialsDir, profile) + } + + return credentialPath, configurePath, nil +} + +func loadConfigProfile(cf *TencentCloudAccessConfig) (*Profile, error) { + var ( + credentialPath string + configurePath string + ) + + credentialPath, configurePath, err := getProfilePatch(cf) + if err != nil { + return nil, err + } + + tcProfile := &Profile{} + _, err = os.Stat(credentialPath) + if !os.IsNotExist(err) { + data, err := ioutil.ReadFile(credentialPath) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, tcProfile) + if err != nil { + return nil, fmt.Errorf("credential file unmarshal failed, %s", err) + } + + if tcProfile.Type == "oauth" { + err := GetOauthConfig(tcProfile) + if err != nil { + return nil, fmt.Errorf("getOauthConfig failed, %v", err) + } + } + } else { + return nil, fmt.Errorf("please set a valid secret_id and secret_key or shared_credentials_dir, %s", err) + } + _, err = os.Stat(configurePath) + if !os.IsNotExist(err) { + data, err := ioutil.ReadFile(configurePath) + if err != nil { + return nil, err + } + + config := map[string]interface{}{} + err = json.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("configure file unmarshal failed, %s", err) + } + + outerLoop: + for k, v := range config { + if k == "_sys_param" { + tmpMap := v.(map[string]interface{}) + for tmpK, tmpV := range tmpMap { + if tmpK == "region" { + tcProfile.Region = strings.TrimSpace(tmpV.(string)) + break outerLoop + } + } + } + } + } else { + return nil, fmt.Errorf("please set a valid region or shared_credentials_dir, %s", err) + } + + return tcProfile, nil +} diff --git a/builder/tencentcloud/cvm/run_config.go b/builder/tencentcloud/cvm/run_config.go index e4a45e11..4269ed0d 100644 --- a/builder/tencentcloud/cvm/run_config.go +++ b/builder/tencentcloud/cvm/run_config.go @@ -29,11 +29,17 @@ type TencentCloudRunConfig struct { // Default value is `false`. AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address" required:"false"` // The base image id of Image you want to create + // You can also specify `source_image_family`. If both `source_image` and `source_image_family` are specified, `source_image` takes precedence. // your customized image from. SourceImageId string `mapstructure:"source_image_id" required:"false"` // The base image name of Image you want to create your - // customized image from.Conflict with SourceImageId. + // customized image from.Conflict with SourceImageId and SourceImageName. SourceImageName string `mapstructure:"source_image_name" required:"false"` + // The source image family to use to create the new image from. + // The image family always returns its latest image that is not deprecated. + // Conflict with SourceImageId and SourceImageName. It takes effect when SourceImageId and SourceImageName are empty. + // Example value: business-daily-update. + SourceImageFamily string `mapstructure:"source_image_family" required:"false"` // Charge type of cvm, values can be `POSTPAID_BY_HOUR` (default) `SPOTPAID` InstanceChargeType string `mapstructure:"instance_charge_type" required:"false"` // The instance type your cvm will be launched by. @@ -57,7 +63,7 @@ type TencentCloudRunConfig struct { // type for all data disks, and each data disk size will use the origin // value in source image. // The data disks allow for the following argument: - // - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM` and `CLOUD_SSD`. + // - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM`, `CLOUD_SSD`, `CLOUD_BSSD`, `CLOUD_HSSD` and `CLOUD_TSSD`. // - `disk_size` - Size of the data disk. // - `disk_snapshot_id` - Id of the snapshot for a data disk. DataDisks []tencentCloudDataDisk `mapstructure:"data_disks"` @@ -103,18 +109,25 @@ type TencentCloudRunConfig struct { // [`dynamic_block`](/packer/docs/templates/hcl_templates/expressions#dynamic-blocks) // will allow you to create those programatically. RunTag config.KeyValues `mapstructure:"run_tag" required:"false"` + // Support for local dedicated cluster CDC + CdcId string `mapstructure:"cdc_id" required:"false"` // Communicator settings Comm communicator.Config `mapstructure:",squash"` SSHPrivateIp bool `mapstructure:"ssh_private_ip"` + // The zone where your cvm will be launch. You should + // reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091) + // for parameter taking. + Zone string `mapstructure:"zone" required:"true"` } var ValidCBSType = []string{ - "LOCAL_BASIC", "LOCAL_SSD", "CLOUD_BASIC", "CLOUD_SSD", "CLOUD_PREMIUM", + "LOCAL_BASIC", "LOCAL_SSD", "CLOUD_BASIC", "CLOUD_PREMIUM", "CLOUD_BSSD", "CLOUD_SSD", "CLOUD_HSSD", "CLOUD_TSSD", } func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error { - packerId := fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()[:8]) + timeOrderedUUID := uuid.TimeOrderedUUID() + packerId := fmt.Sprintf("packer_%s_%s", timeOrderedUUID[:8], timeOrderedUUID[9:13]) 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 @@ -122,8 +135,12 @@ func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error { } errs := cf.Comm.Prepare(ctx) - if cf.SourceImageId == "" && cf.SourceImageName == "" { - errs = append(errs, errors.New("source_image_id or source_image_name must be specified")) + if cf.Zone == "" { + errs = append(errs, errors.New("zone must be specified")) + } + + if cf.SourceImageId == "" && cf.SourceImageName == "" && cf.SourceImageFamily == "" { + errs = append(errs, errors.New("source_image_id or source_image_name or source_image_family must be specified")) } if cf.SourceImageId != "" && !CheckResourceIdFormat("img", cf.SourceImageId) { diff --git a/builder/tencentcloud/cvm/run_config_test.go b/builder/tencentcloud/cvm/run_config_test.go index 97536ea6..711f9748 100644 --- a/builder/tencentcloud/cvm/run_config_test.go +++ b/builder/tencentcloud/cvm/run_config_test.go @@ -13,6 +13,7 @@ import ( func testConfig() *TencentCloudRunConfig { return &TencentCloudRunConfig{ + Zone: "ap-guangzhou", SourceImageId: "img-qwer1234", InstanceType: "S3.SMALL2", Comm: communicator.Config{ diff --git a/builder/tencentcloud/cvm/step_check_source_image.go b/builder/tencentcloud/cvm/step_check_source_image.go index 616c4734..acbad68e 100644 --- a/builder/tencentcloud/cvm/step_check_source_image.go +++ b/builder/tencentcloud/cvm/step_check_source_image.go @@ -24,12 +24,19 @@ func (s *stepCheckSourceImage) Run(ctx context.Context, state multistep.StateBag config := state.Get("config").(*Config) client := state.Get("cvm_client").(*cvm.Client) + var source_image *cvm.Image + if state.Get("source_image") != nil { + source_image = state.Get("source_image").(*cvm.Image) + } + Say(state, config.SourceImageId, "Trying to check source image") req := cvm.NewDescribeImagesRequest() req.InstanceType = &config.InstanceType if config.SourceImageId != "" { req.ImageIds = []*string{&config.SourceImageId} + } else if source_image != nil && *source_image.ImageId != "" { + req.ImageIds = []*string{source_image.ImageId} } else { imageNameRegex, err = regexp.Compile(config.SourceImageName) if err != nil { diff --git a/builder/tencentcloud/cvm/step_check_source_image_family.go b/builder/tencentcloud/cvm/step_check_source_image_family.go new file mode 100644 index 00000000..01fe46d9 --- /dev/null +++ b/builder/tencentcloud/cvm/step_check_source_image_family.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cvm + +import ( + "context" + "fmt" + + "github.com/hashicorp/packer-plugin-sdk/multistep" + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" +) + +type stepCheckSourceImageFamily struct { + sourceImageId string + sourceImageName string +} + +func (s *stepCheckSourceImageFamily) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + if s.sourceImageId != "" || s.sourceImageName != "" { + return multistep.ActionContinue + } + config := state.Get("config").(*Config) + client := state.Get("cvm_client").(*cvm.Client) + + Say(state, config.SourceImageFamily, "Try to check the source image and get the latest valid image of the image family") + + req := cvm.NewDescribeImageFromFamilyRequest() + req.ImageFamily = &config.SourceImageFamily + + var resp *cvm.DescribeImageFromFamilyResponse + err := Retry(ctx, func(ctx context.Context) error { + var err error + resp, err = client.DescribeImageFromFamily(req) + return err + }) + if err != nil { + return Halt(state, err, "Failed to get source image info from the image family") + } + + if resp != nil && resp.Response != nil && resp.Response.Image != nil { + image := resp.Response.Image + if image.ImageId != nil && !*image.ImageDeprecated { + state.Put("source_image", image) + Message(state, fmt.Sprintf("Get the latest image from the image family, id: %v", *image.ImageId), "Image found") + return multistep.ActionContinue + } + } else { + return Halt(state, fmt.Errorf("failed to get source image: %v", resp.ToJsonString()), "No image family found") + } + + return Halt(state, fmt.Errorf("No image found under current instance_type(%s) restriction", config.InstanceType), "") +} + +func (s *stepCheckSourceImageFamily) Cleanup(bag multistep.StateBag) {} diff --git a/builder/tencentcloud/cvm/step_config_subnet.go b/builder/tencentcloud/cvm/step_config_subnet.go index 085ada76..855512f4 100644 --- a/builder/tencentcloud/cvm/step_config_subnet.go +++ b/builder/tencentcloud/cvm/step_config_subnet.go @@ -17,6 +17,7 @@ type stepConfigSubnet struct { SubnetName string Zone string isCreate bool + CdcId string } func (s *stepConfigSubnet) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -57,6 +58,9 @@ func (s *stepConfigSubnet) Run(ctx context.Context, state multistep.StateBag) mu req.SubnetName = &s.SubnetName req.CidrBlock = &s.SubnetCidrBlock req.Zone = &s.Zone + if s.CdcId != "" { + req.CdcId = &s.CdcId + } var resp *vpc.CreateSubnetResponse err := Retry(ctx, func(ctx context.Context) error { var e error diff --git a/builder/tencentcloud/cvm/step_copy_image.go b/builder/tencentcloud/cvm/step_copy_image.go index bc481e87..6e8f6b5a 100644 --- a/builder/tencentcloud/cvm/step_copy_image.go +++ b/builder/tencentcloud/cvm/step_copy_image.go @@ -14,12 +14,16 @@ import ( ) type stepCopyImage struct { - DesinationRegions []string - SourceRegion string + DestinationRegions []string + SourceRegion string + SkipCreateImage bool } func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - if len(s.DesinationRegions) == 0 || (len(s.DesinationRegions) == 1 && s.DesinationRegions[0] == s.SourceRegion) { + if s.SkipCreateImage { + return multistep.ActionContinue + } + if len(s.DestinationRegions) == 0 || (len(s.DestinationRegions) == 1 && s.DestinationRegions[0] == s.SourceRegion) { return multistep.ActionContinue } @@ -28,12 +32,12 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi imageId := state.Get("image").(*cvm.Image).ImageId - Say(state, strings.Join(s.DesinationRegions, ","), "Trying to copy image to") + Say(state, strings.Join(s.DestinationRegions, ","), "Trying to copy image to") req := cvm.NewSyncImagesRequest() req.ImageIds = []*string{imageId} - copyRegions := make([]*string, 0, len(s.DesinationRegions)) - for _, region := range s.DesinationRegions { + copyRegions := make([]*string, 0, len(s.DestinationRegions)) + for _, region := range s.DestinationRegions { if region != s.SourceRegion { copyRegions = append(copyRegions, common.StringPtr(region)) } @@ -54,7 +58,6 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi cf := &TencentCloudAccessConfig{ SecretId: config.SecretId, SecretKey: config.SecretKey, - Zone: config.Zone, CvmEndpoint: config.CvmEndpoint, SecurityToken: config.SecurityToken, AssumeRole: TencentCloudAccessRole{ diff --git a/builder/tencentcloud/cvm/step_create_image.go b/builder/tencentcloud/cvm/step_create_image.go index b337fbeb..bb823404 100644 --- a/builder/tencentcloud/cvm/step_create_image.go +++ b/builder/tencentcloud/cvm/step_create_image.go @@ -9,23 +9,33 @@ import ( "github.com/hashicorp/packer-plugin-sdk/multistep" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813" ) type stepCreateImage struct { - imageId string + imageId string + SkipCreateImage bool } func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { client := state.Get("cvm_client").(*cvm.Client) + tagClient := state.Get("tag_client").(*tag.Client) config := state.Get("config").(*Config) instance := state.Get("instance").(*cvm.Instance) + // Optionally skip this step + if s.SkipCreateImage { + Say(state, "Skipping image creation step", "") + return multistep.ActionContinue + } + Say(state, config.ImageName, "Trying to create a new image") req := cvm.NewCreateImageRequest() req.ImageName = &config.ImageName req.ImageDescription = &config.ImageDescription + req.ImageFamily = &config.ImageFamily req.InstanceId = instance.InstanceId // TODO: We should allow user to specify which data disk should be @@ -99,6 +109,20 @@ func (s *stepCreateImage) Run(ctx context.Context, state multistep.StateBag) mul state.Put("image", image) Message(state, s.imageId, "Image created") + snapshotTags := config.SnapshotTags + if len(snapshotTags) > 0 { + for _, snapshot := range image.SnapshotSet { + if snapshot == nil || snapshot.SnapshotId == nil { + return Halt(state, err, "snapshot or snapshotId is nil") + } + resourceName := BuildTagResourceName("cvm", "snapshot", config.Region, *snapshot.SnapshotId) + err := AddResourceTag(ctx, tagClient, resourceName, snapshotTags) + if err != nil { + return Halt(state, err, fmt.Sprintf("Failed to set tag for snapshot(%s)", *snapshot.SnapshotId)) + } + } + } + tencentCloudImages := make(map[string]string) tencentCloudImages[config.Region] = s.imageId state.Put("tencentcloudimages", tencentCloudImages) diff --git a/builder/tencentcloud/cvm/step_pre_validate.go b/builder/tencentcloud/cvm/step_pre_validate.go index 4f654e93..4365c4d2 100644 --- a/builder/tencentcloud/cvm/step_pre_validate.go +++ b/builder/tencentcloud/cvm/step_pre_validate.go @@ -12,9 +12,15 @@ import ( ) type stepPreValidate struct { + SkipCreateImage bool } func (s *stepPreValidate) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { + // No need to validate the image name if we're not creating an image + if s.SkipCreateImage { + return multistep.ActionContinue + } + config := state.Get("config").(*Config) client := state.Get("cvm_client").(*cvm.Client) diff --git a/builder/tencentcloud/cvm/step_run_instance.go b/builder/tencentcloud/cvm/step_run_instance.go index 943eabf6..6ec2449d 100644 --- a/builder/tencentcloud/cvm/step_run_instance.go +++ b/builder/tencentcloud/cvm/step_run_instance.go @@ -11,6 +11,7 @@ import ( "log" "github.com/hashicorp/packer-plugin-sdk/multistep" + "github.com/hashicorp/packer-plugin-sdk/uuid" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" ) @@ -32,6 +33,7 @@ type stepRunInstance struct { AssociatePublicIpAddress bool Tags map[string]string DataDisks []tencentCloudDataDisk + CdcId string } func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { @@ -53,7 +55,7 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul return Halt(state, err, "Failed to get user_data") } - Say(state, "Trying to create a new instance", "") + Say(state, *source_image.ImageId, "Try to create a new instance based on image") // config RunInstances parameters req := cvm.NewRunInstancesRequest() @@ -66,6 +68,10 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul if instanceChargeType == "" { instanceChargeType = "POSTPAID_BY_HOUR" } + if s.CdcId != "" { + instanceChargeType = "CDCPAID" + req.DedicatedClusterId = &s.CdcId + } req.InstanceChargeType = &instanceChargeType req.ImageId = source_image.ImageId req.InstanceType = &s.InstanceType @@ -125,7 +131,11 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul req.InternetAccessible.BandwidthPackageId = &s.BandwidthPackageId } } - req.InstanceName = &s.InstanceName + + // Generate a unique ClientToken for each RunInstances request + clientToken := uuid.TimeOrderedUUID() + req.ClientToken = &clientToken + loginSettings := cvm.LoginSettings{} if password != "" { loginSettings.Password = &password @@ -135,7 +145,7 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul } req.LoginSettings = &loginSettings req.SecurityGroupIds = []*string{&security_group_id} - req.ClientToken = &s.InstanceName + req.InstanceName = &s.InstanceName req.HostName = &s.HostName req.UserData = &userData req.CamRoleName = &s.CamRoleName @@ -173,7 +183,7 @@ func (s *stepRunInstance) Run(ctx context.Context, state multistep.StateBag) mul } s.instanceId = *resp.Response.InstanceIdSet[0] - Message(state, "Waiting for instance ready", "") + Message(state, fmt.Sprintf("Instance %s created, waiting for instance ready", s.instanceId), "") err = WaitForInstance(ctx, client, s.instanceId, "RUNNING", 1800) if err != nil { diff --git a/builder/tencentcloud/cvm/step_share_image.go b/builder/tencentcloud/cvm/step_share_image.go index 821555bf..e4f8350e 100644 --- a/builder/tencentcloud/cvm/step_share_image.go +++ b/builder/tencentcloud/cvm/step_share_image.go @@ -6,19 +6,23 @@ package cvm import ( "context" "fmt" + "strconv" "strings" "github.com/hashicorp/packer-plugin-sdk/multistep" + cam "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam/v20190116" "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common" cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + organization "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization/v20210331" ) type stepShareImage struct { - ShareAccounts []string + ShareAccounts []string + IsShareOrgMembers bool } func (s *stepShareImage) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction { - if len(s.ShareAccounts) == 0 { + if len(s.ShareAccounts) == 0 && !s.IsShareOrgMembers { return multistep.ActionContinue } @@ -30,10 +34,23 @@ func (s *stepShareImage) Run(ctx context.Context, state multistep.StateBag) mult req := cvm.NewModifyImageSharePermissionRequest() req.ImageId = imageId req.Permission = common.StringPtr("SHARE") - accounts := make([]*string, 0, len(s.ShareAccounts)) + accounts := []*string{} for _, account := range s.ShareAccounts { accounts = append(accounts, common.StringPtr(account)) } + + if s.IsShareOrgMembers { + accountList, err := s.getOrgAccounts(ctx, state) + if err != nil { + return Halt(state, err, "Failed to get org accounts") + } + accounts = append(accounts, accountList...) + } + + if len(accounts) == 0 { + return multistep.ActionContinue + } + req.AccountIds = accounts err := Retry(ctx, func(ctx context.Context) error { _, e := client.ModifyImageSharePermission(req) @@ -48,6 +65,81 @@ func (s *stepShareImage) Run(ctx context.Context, state multistep.StateBag) mult return multistep.ActionContinue } +func (s *stepShareImage) getOrgAccounts(ctx context.Context, state multistep.StateBag) ([]*string, error) { + + currentAccount, err := s.getUserId(ctx, state) + if err != nil { + return nil, err + } + + req := organization.NewDescribeOrganizationMembersRequest() + resp := organization.NewDescribeOrganizationMembersResponse() + + var limit uint64 = 50 + var offset uint64 = 0 + + req.Limit = &limit + req.Offset = &offset + + accounts := []*string{} + for { + client := state.Get("org_client").(*organization.Client) + err := Retry(ctx, func(ctx context.Context) error { + var e error + resp, e = client.DescribeOrganizationMembers(req) + return e + }) + if err != nil { + return nil, nil + } + if resp.Response == nil { + return nil, nil + } + items := resp.Response.Items + for _, v := range items { + if v.MemberUin != nil { + if strconv.FormatInt(*v.MemberUin, 10) == currentAccount { + continue + } + accounts = append(accounts, common.StringPtr(strconv.Itoa(int(*v.MemberUin)))) + } + } + + if len(items) < int(limit) { + break + } + + offset += limit + } + + return accounts, nil +} + +func (s *stepShareImage) getUserId(ctx context.Context, state multistep.StateBag) (string, error) { + req := cam.NewGetUserAppIdRequest() + resp := cam.NewGetUserAppIdResponse() + + client := state.Get("cam_client").(*cam.Client) + err := Retry(ctx, func(ctx context.Context) error { + var e error + resp, e = client.GetUserAppId(req) + return e + }) + if err != nil { + return "", err + } + + if resp.Response == nil { + return "", nil + } + + if resp.Response.Uin != nil { + return *resp.Response.Uin, nil + } + + return "", nil +} + func (s *stepShareImage) Cleanup(state multistep.StateBag) { _, cancelled := state.GetOk(multistep.StateCancelled) _, halted := state.GetOk(multistep.StateHalted) diff --git a/builder/tencentcloud/cvm/transport.go b/builder/tencentcloud/cvm/transport.go new file mode 100644 index 00000000..b98ce37c --- /dev/null +++ b/builder/tencentcloud/cvm/transport.go @@ -0,0 +1,104 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package cvm + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "time" + + "github.com/hashicorp/packer-plugin-tencentcloud/version" +) + +type LogRoundTripper struct{} + +func (me *LogRoundTripper) RoundTrip(request *http.Request) (response *http.Response, errRet error) { + + var inBytes, outBytes []byte + + var start = time.Now() + + defer func() { me.log(inBytes, outBytes, errRet, start) }() + + bodyReader, errRet := request.GetBody() + if errRet != nil { + return + } + + var headName = "X-TC-Action" + var reqClientFormat = version.PluginVersion + request.Header.Set("X-TC-RequestClient", reqClientFormat.String()) + inBytes = []byte(fmt.Sprintf("%s, request: ", request.Header[headName])) + requestBody, errRet := ioutil.ReadAll(bodyReader) + if errRet != nil { + return + } + + inBytes = append(inBytes, requestBody...) + headName = "X-TC-Region" + appendMessage := []byte(fmt.Sprintf( + ", (host %+v, region:%+v)", + request.Header["Host"], + request.Header[headName], + )) + + inBytes = append(inBytes, appendMessage...) + response, errRet = http.DefaultTransport.RoundTrip(request) + if errRet != nil { + return + } + + outBytes, errRet = ioutil.ReadAll(response.Body) + if errRet != nil { + return + } + + response.Body = ioutil.NopCloser(bytes.NewBuffer(outBytes)) + return +} + +func (me *LogRoundTripper) log(in []byte, out []byte, err error, start time.Time) { + var buf bytes.Buffer + buf.WriteString("######") + tag := "[DEBUG]" + if err != nil { + tag = "[CRITICAL]" + } + + buf.WriteString(tag) + if len(in) > 0 { + buf.WriteString("tencentcloud-sdk-go: ") + buf.Write(in) + } + + if len(out) > 0 { + buf.WriteString("; response:") + err := json.Compact(&buf, out) + if err != nil { + out := bytes.Replace(out, + []byte("\n"), + []byte(""), + -1) + out = bytes.Replace(out, + []byte(" "), + []byte(""), + -1) + buf.Write(out) + } + } + + if err != nil { + buf.WriteString("; error:") + buf.WriteString(err.Error()) + } + + costFormat := fmt.Sprintf(",cost %s", time.Since(start).String()) + buf.WriteString(costFormat) + + log.Println(buf.String()) +} diff --git a/datasource/tencentcloud/examples/image.pkr.hcl b/datasource/tencentcloud/examples/image.pkr.hcl new file mode 100644 index 00000000..2ff53488 --- /dev/null +++ b/datasource/tencentcloud/examples/image.pkr.hcl @@ -0,0 +1,29 @@ +data "tencentcloud-image" "test-image" { + filters = { + image-type = "PRIVATE_IMAGE" + } + most_recent = true + region = "ap-guangzhou" +} + +locals { + id = data.tencentcloud-image.test-image.id + name = data.tencentcloud-image.test-image.name +} + +source "null" "basic-example" { + communicator = "none" +} + +build { + sources = [ + "source.null.basic-example" + ] + + provisioner "shell-local" { + inline = [ + "echo id: ${local.id}", + "echo name: ${local.name}", + ] + } +} \ No newline at end of file diff --git a/datasource/tencentcloud/image/data.go b/datasource/tencentcloud/image/data.go new file mode 100644 index 00000000..bdec647c --- /dev/null +++ b/datasource/tencentcloud/image/data.go @@ -0,0 +1,182 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config,Image + +package image + +import ( + "context" + "fmt" + + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-sdk/common" + "github.com/hashicorp/packer-plugin-sdk/hcl2helper" + packersdk "github.com/hashicorp/packer-plugin-sdk/packer" + "github.com/hashicorp/packer-plugin-sdk/template/config" + "github.com/hashicorp/packer-plugin-sdk/template/interpolate" + buildCvm "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm" + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" + "github.com/zclconf/go-cty/cty" +) + +type ImageFilterOptions struct { + // Filters used to select an image. Any filter described in the documentation for + // [DescribeImages](https://www.tencentcloud.com/document/product/213/33272) can be used. + Filters map[string]string `mapstructure:"filters"` + // Image family used to select an image. Uses the + // [DescribeImageFromFamily](https://www.tencentcloud.com/document/product/213/64971) API. + // Mutually exclusive with `filters`, and `most_recent` will have no effect. + ImageFamily string `mapstructure:"image_family"` + // Selects the most recently created image when multiple results are returned. Note that + // public images don't have a creation date, so this flag is only really useful for private + // images. + MostRecent bool `mapstructure:"most_recent"` +} + +type Config struct { + common.PackerConfig `mapstructure:",squash"` + buildCvm.TencentCloudAccessConfig `mapstructure:",squash"` + ImageFilterOptions `mapstructure:",squash"` + ctx interpolate.Context +} + +type Datasource struct { + config Config +} + +func (d *Datasource) ConfigSpec() hcldec.ObjectSpec { + return d.config.FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Configure(raws ...interface{}) error { + err := config.Decode(&d.config, nil, raws...) + if err != nil { + return err + } + + var errs *packersdk.MultiError + errs = packersdk.MultiErrorAppend(errs, d.config.TencentCloudAccessConfig.Prepare(&d.config.ctx)...) + + if len(d.config.Filters) == 0 && d.config.ImageFamily == "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`filters` or `image_family` must be specified")) + } + + if len(d.config.Filters) > 0 && d.config.ImageFamily != "" { + errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`filters` and `image_family` are mutually exclusive")) + } + + if errs != nil && len(errs.Errors) > 0 { + return errs + } + return nil +} + +type DatasourceOutput struct { + // The image ID + ID string `mapstructure:"id"` + // The image name + Name string `mapstructure:"name"` +} + +func (d *Datasource) OutputSpec() hcldec.ObjectSpec { + return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec() +} + +func (d *Datasource) Execute() (cty.Value, error) { + var image *cvm.Image + var err error + + if len(d.config.Filters) > 0 { + image, err = d.ResolveImageByFilters() + } else { + image, err = d.ResolveImageByImageFamily() + } + + if err != nil { + return cty.NullVal(cty.EmptyObject), err + } + + output := DatasourceOutput{ + ID: *image.ImageId, + Name: *image.ImageName, + } + return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil +} + +func (d *Datasource) ResolveImageByFilters() (*cvm.Image, error) { + clientMap, err := d.config.Client() + if err != nil { + return nil, err + } + + cvmClient := clientMap["cvm_client"].(*cvm.Client) + + req := cvm.NewDescribeImagesRequest() + + var filters []*cvm.Filter + for k, v := range d.config.Filters { + k := k + v := v + filters = append(filters, &cvm.Filter{ + Name: &k, + Values: []*string{&v}, + }) + } + req.Filters = filters + + ctx := context.TODO() + var resp *cvm.DescribeImagesResponse + err = buildCvm.Retry(ctx, func(ctx context.Context) error { + var e error + resp, e = cvmClient.DescribeImages(req) + return e + }) + if err != nil { + return nil, err + } + + if *resp.Response.TotalCount == 0 { + return nil, fmt.Errorf("No image found using the specified filters") + } + + if *resp.Response.TotalCount > 1 && !d.config.MostRecent { + return nil, fmt.Errorf("Your image query returned more than result. Please try a more specific search, or set `most_recent` to `true`.") + } + + if d.config.MostRecent { + return mostRecentImage(resp.Response.ImageSet), nil + } else { + return resp.Response.ImageSet[0], nil + } +} + +func (d *Datasource) ResolveImageByImageFamily() (*cvm.Image, error) { + clientMap, err := d.config.Client() + if err != nil { + return nil, err + } + + cvmClient := clientMap["cvm_client"].(*cvm.Client) + + var resp *cvm.DescribeImageFromFamilyResponse + req := cvm.NewDescribeImageFromFamilyRequest() + req.ImageFamily = &d.config.ImageFamily + + ctx := context.TODO() + err = buildCvm.Retry(ctx, func(ctx context.Context) error { + var e error + resp, e = cvmClient.DescribeImageFromFamily(req) + return e + }) + + if err != nil { + return nil, err + } + + if resp.Response.Image == nil { + return nil, fmt.Errorf("No image found using the specified image family") + } + + return resp.Response.Image, nil +} diff --git a/datasource/tencentcloud/image/data.hcl2spec.go b/datasource/tencentcloud/image/data.hcl2spec.go new file mode 100644 index 00000000..f870f0de --- /dev/null +++ b/datasource/tencentcloud/image/data.hcl2spec.go @@ -0,0 +1,99 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package image + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + SecretId *string `mapstructure:"secret_id" required:"true" cty:"secret_id" hcl:"secret_id"` + SecretKey *string `mapstructure:"secret_key" required:"true" cty:"secret_key" hcl:"secret_key"` + Region *string `mapstructure:"region" required:"true" cty:"region" hcl:"region"` + CvmEndpoint *string `mapstructure:"cvm_endpoint" required:"false" cty:"cvm_endpoint" hcl:"cvm_endpoint"` + VpcEndpoint *string `mapstructure:"vpc_endpoint" required:"false" cty:"vpc_endpoint" hcl:"vpc_endpoint"` + TagEndpoint *string `mapstructure:"tag_endpoint" required:"false" cty:"tag_endpoint" hcl:"tag_endpoint"` + OrgEndpoint *string `mapstructure:"org_endpoint" required:"false" cty:"org_endpoint" hcl:"org_endpoint"` + SecurityToken *string `mapstructure:"security_token" required:"false" cty:"security_token" hcl:"security_token"` + AssumeRole *cvm.FlatTencentCloudAccessRole `mapstructure:"assume_role" required:"false" cty:"assume_role" hcl:"assume_role"` + Profile *string `mapstructure:"profile" required:"false" cty:"profile" hcl:"profile"` + SharedCredentialsDir *string `mapstructure:"shared_credentials_dir" required:"false" cty:"shared_credentials_dir" hcl:"shared_credentials_dir"` + Filters map[string]string `mapstructure:"filters" cty:"filters" hcl:"filters"` + ImageFamily *string `mapstructure:"image_family" cty:"image_family" hcl:"image_family"` + MostRecent *bool `mapstructure:"most_recent" cty:"most_recent" hcl:"most_recent"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "secret_id": &hcldec.AttrSpec{Name: "secret_id", Type: cty.String, Required: false}, + "secret_key": &hcldec.AttrSpec{Name: "secret_key", Type: cty.String, Required: false}, + "region": &hcldec.AttrSpec{Name: "region", Type: cty.String, Required: false}, + "cvm_endpoint": &hcldec.AttrSpec{Name: "cvm_endpoint", Type: cty.String, Required: false}, + "vpc_endpoint": &hcldec.AttrSpec{Name: "vpc_endpoint", Type: cty.String, Required: false}, + "tag_endpoint": &hcldec.AttrSpec{Name: "tag_endpoint", Type: cty.String, Required: false}, + "org_endpoint": &hcldec.AttrSpec{Name: "org_endpoint", Type: cty.String, Required: false}, + "security_token": &hcldec.AttrSpec{Name: "security_token", Type: cty.String, Required: false}, + "assume_role": &hcldec.BlockSpec{TypeName: "assume_role", Nested: hcldec.ObjectSpec((*cvm.FlatTencentCloudAccessRole)(nil).HCL2Spec())}, + "profile": &hcldec.AttrSpec{Name: "profile", Type: cty.String, Required: false}, + "shared_credentials_dir": &hcldec.AttrSpec{Name: "shared_credentials_dir", Type: cty.String, Required: false}, + "filters": &hcldec.AttrSpec{Name: "filters", Type: cty.Map(cty.String), Required: false}, + "image_family": &hcldec.AttrSpec{Name: "image_family", Type: cty.String, Required: false}, + "most_recent": &hcldec.AttrSpec{Name: "most_recent", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatDatasourceOutput struct { + ID *string `mapstructure:"id" cty:"id" hcl:"id"` + Name *string `mapstructure:"name" cty:"name" hcl:"name"` +} + +// FlatMapstructure returns a new FlatDatasourceOutput. +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*DatasourceOutput) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatDatasourceOutput) +} + +// HCL2Spec returns the hcl spec of a DatasourceOutput. +// This spec is used by HCL to read the fields of DatasourceOutput. +// The decoded values from this spec will then be applied to a FlatDatasourceOutput. +func (*FlatDatasourceOutput) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "id": &hcldec.AttrSpec{Name: "id", Type: cty.String, Required: false}, + "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, + } + return s +} diff --git a/datasource/tencentcloud/image/data_acc_test.go b/datasource/tencentcloud/image/data_acc_test.go new file mode 100644 index 00000000..58c1b6d8 --- /dev/null +++ b/datasource/tencentcloud/image/data_acc_test.go @@ -0,0 +1,66 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package image + +import ( + _ "embed" + "fmt" + "io/ioutil" + "os" + "os/exec" + "regexp" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/acctest" +) + +//go:embed test-fixtures/image.pkr.hcl +var testImageDatasource string + +// Run with: PACKER_ACC=1 go test -count 1 -v ./datasource/image/data_acc_test.go -timeout=120m +func TestAccImageDatasource(t *testing.T) { + testCase := &acctest.PluginTestCase{ + Name: "image_datasource_basic_test", + Setup: func() error { + return nil + }, + Teardown: func() error { + return nil + }, + Template: testImageDatasource, + Type: "image-my-datasource", + Check: func(buildCommand *exec.Cmd, logfile string) error { + if buildCommand.ProcessState != nil { + if buildCommand.ProcessState.ExitCode() != 0 { + return fmt.Errorf("Bad exit code. Logfile: %s", logfile) + } + } + + logs, err := os.Open(logfile) + if err != nil { + return fmt.Errorf("Unable find %s", logfile) + } + defer logs.Close() + + logsBytes, err := ioutil.ReadAll(logs) + if err != nil { + return fmt.Errorf("Unable to read %s", logfile) + } + logsString := string(logsBytes) + + idLog := "null.basic-example: id: img-kvtbik4g" + nameLog := "null.basic-example: name: image-family-test" + + if matched, _ := regexp.MatchString(idLog+".*", logsString); !matched { + t.Fatalf("logs doesn't contain expected ID value %q", logsString) + } + if matched, _ := regexp.MatchString(nameLog+".*", logsString); !matched { + t.Fatalf("logs doesn't contain expected name value %q", logsString) + } + + return nil + }, + } + acctest.TestPlugin(t, testCase) +} diff --git a/datasource/tencentcloud/image/data_test.go b/datasource/tencentcloud/image/data_test.go new file mode 100644 index 00000000..e1a90d5f --- /dev/null +++ b/datasource/tencentcloud/image/data_test.go @@ -0,0 +1,89 @@ +package image + +import ( + "testing" + + cvm "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm" +) + +var tencentCloudAccessConfig = cvm.TencentCloudAccessConfig{ + Region: "na-ashburn", + SecretId: "secret", + SecretKey: "key", +} + +func TestDatasourceConfigure_NoOptionsSpecified(t *testing.T) { + ds := Datasource{ + config: Config{ + TencentCloudAccessConfig: tencentCloudAccessConfig, + ImageFilterOptions: ImageFilterOptions{ + Filters: map[string]string{}, + ImageFamily: "", + MostRecent: false, + }, + }, + } + + if err := ds.Configure(); err == nil { + t.Fatal("Should fail since at least one option must be specified") + } else { + t.Log(err) + } +} + +func TestDatasourceConfigure_BothFiltersAndImageFamilySpecified(t *testing.T) { + ds := Datasource{ + config: Config{ + TencentCloudAccessConfig: tencentCloudAccessConfig, + ImageFilterOptions: ImageFilterOptions{ + Filters: map[string]string{ + "foo": "bar", + }, + ImageFamily: "foo", + MostRecent: false, + }, + }, + } + + if err := ds.Configure(); err == nil { + t.Fatal("Should fail since options are mutually exclusive") + } else { + t.Log(err) + } +} + +func TestDatasourceConfigure_FiltersSpecified(t *testing.T) { + ds := Datasource{ + config: Config{ + TencentCloudAccessConfig: tencentCloudAccessConfig, + ImageFilterOptions: ImageFilterOptions{ + Filters: map[string]string{ + "foo": "bar", + }, + ImageFamily: "", + MostRecent: false, + }, + }, + } + + if err := ds.Configure(); err != nil { + t.Fatal("Should not fail") + } +} + +func TestDatasourceConfigure_ImageFamilySpecified(t *testing.T) { + ds := Datasource{ + config: Config{ + TencentCloudAccessConfig: tencentCloudAccessConfig, + ImageFilterOptions: ImageFilterOptions{ + Filters: map[string]string{}, + ImageFamily: "foo", + MostRecent: false, + }, + }, + } + + if err := ds.Configure(); err != nil { + t.Fatal("Should not fail") + } +} diff --git a/datasource/tencentcloud/image/sorting.go b/datasource/tencentcloud/image/sorting.go new file mode 100644 index 00000000..7a6824c7 --- /dev/null +++ b/datasource/tencentcloud/image/sorting.go @@ -0,0 +1,30 @@ +package image + +import ( + "sort" + "time" + + cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312" +) + +type imageSort []*cvm.Image + +func (a imageSort) Len() int { return len(a) } +func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a imageSort) Less(i, j int) bool { + // Public images don't have a creation time + if a[i].CreatedTime == nil || a[j].CreatedTime == nil { + return false + } + + itime, _ := time.Parse(time.RFC3339, *a[i].CreatedTime) + jtime, _ := time.Parse(time.RFC3339, *a[j].CreatedTime) + return itime.Before(jtime) +} + +func mostRecentImage(images []*cvm.Image) *cvm.Image { + sortedImages := images + sort.Sort(imageSort(sortedImages)) + + return sortedImages[len(sortedImages)-1] +} diff --git a/datasource/tencentcloud/image/test-fixtures/image.pkr.hcl b/datasource/tencentcloud/image/test-fixtures/image.pkr.hcl new file mode 100644 index 00000000..09e876d3 --- /dev/null +++ b/datasource/tencentcloud/image/test-fixtures/image.pkr.hcl @@ -0,0 +1,30 @@ +data "tencentcloud-image" "test-image" { + filters = { + image-type = "PRIVATE_IMAGE" + image-name = "image-family-test" + } + most_recent = true + region = "ap-guangzhou" +} + +locals { + id = data.tencentcloud-image.test-image.id + name = data.tencentcloud-image.test-image.name +} + +source "null" "basic-example" { + communicator = "none" +} + +build { + sources = [ + "source.null.basic-example" + ] + + provisioner "shell-local" { + inline = [ + "echo id: ${local.id}", + "echo name: ${local.name}", + ] + } +} \ No newline at end of file diff --git a/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-not-required.mdx b/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-not-required.mdx index 67405b86..8c15fe1d 100644 --- a/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-not-required.mdx +++ b/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-not-required.mdx @@ -6,6 +6,12 @@ - `vpc_endpoint` (string) - The endpoint you want to reach the cloud endpoint, if tce cloud you should set a tce vpc endpoint. +- `tag_endpoint` (string) - The endpoint you want to reach the cloud endpoint, + if tce cloud you should set a tce tag endpoint. + +- `org_endpoint` (string) - The endpoint you want to reach the cloud endpoint, + if tce cloud you should set a tce organization endpoint. + - `security_token` (string) - STS access token, can be set through template or by exporting as environment variable such as `export TENCENTCLOUD_SECURITY_TOKEN=value`. diff --git a/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-required.mdx b/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-required.mdx index 3f93b9a8..80aff723 100644 --- a/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-required.mdx +++ b/docs-partials/builder/tencentcloud/cvm/TencentCloudAccessConfig-required.mdx @@ -10,8 +10,4 @@ reference [Region and Zone](https://intl.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://intl.cloud.tencent.com/document/product/213/6091) - for parameter taking. - diff --git a/docs-partials/builder/tencentcloud/cvm/TencentCloudImageConfig-not-required.mdx b/docs-partials/builder/tencentcloud/cvm/TencentCloudImageConfig-not-required.mdx index b1c3baf0..8859424a 100644 --- a/docs-partials/builder/tencentcloud/cvm/TencentCloudImageConfig-not-required.mdx +++ b/docs-partials/builder/tencentcloud/cvm/TencentCloudImageConfig-not-required.mdx @@ -15,4 +15,16 @@ - `image_tags` (map[string]string) - Key/value pair tags that will be applied to the resulting image. +- `snapshot_tags` (map[string]string) - Key/value pair tags that will be applied to snapshot. + +- `skip_create_image` (bool) - Skip creating an image. When set to true, you don't need to enter target image information, share, copy, etc. The default value is false. + +- `is_share_org_members` (bool) - After creating the image, + whether to share it with other accounts in the organization + where the current account is located. + The image can be copied to a maximum of 50 accounts, + with ImageShareAccounts being the priority. + +- `image_family` (string) - Image family. Example value: business-daily-update. + diff --git a/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-not-required.mdx b/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-not-required.mdx index 7d1fabb9..c98e844a 100644 --- a/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-not-required.mdx +++ b/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-not-required.mdx @@ -4,10 +4,16 @@ Default value is `false`. - `source_image_id` (string) - The base image id of Image you want to create + You can also specify `source_image_family`. If both `source_image` and `source_image_family` are specified, `source_image` takes precedence. your customized image from. - `source_image_name` (string) - The base image name of Image you want to create your - customized image from.Conflict with SourceImageId. + customized image from.Conflict with SourceImageId and SourceImageName. + +- `source_image_family` (string) - The source image family to use to create the new image from. + The image family always returns its latest image that is not deprecated. + Conflict with SourceImageId and SourceImageName. It takes effect when SourceImageId and SourceImageName are empty. + Example value: business-daily-update. - `instance_charge_type` (string) - Charge type of cvm, values can be `POSTPAID_BY_HOUR` (default) `SPOTPAID` @@ -28,7 +34,7 @@ type for all data disks, and each data disk size will use the origin value in source image. The data disks allow for the following argument: - - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM` and `CLOUD_SSD`. + - `disk_type` - Type of the data disk. Valid choices: `CLOUD_BASIC`, `CLOUD_PREMIUM`, `CLOUD_SSD`, `CLOUD_BSSD`, `CLOUD_HSSD` and `CLOUD_TSSD`. - `disk_size` - Size of the data disk. - `disk_snapshot_id` - Id of the snapshot for a data disk. @@ -74,6 +80,8 @@ [`dynamic_block`](/packer/docs/templates/hcl_templates/expressions#dynamic-blocks) will allow you to create those programatically. +- `cdc_id` (string) - Support for local dedicated cluster CDC + - `ssh_private_ip` (bool) - SSH Private Ip diff --git a/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-required.mdx b/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-required.mdx index 7bc83eb7..dd2eaef3 100644 --- a/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-required.mdx +++ b/docs-partials/builder/tencentcloud/cvm/TencentCloudRunConfig-required.mdx @@ -4,4 +4,8 @@ You should reference [Instance Type](https://intl.cloud.tencent.com/document/product/213/11518) for parameter taking. +- `zone` (string) - The zone where your cvm will be launch. You should + reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091) + for parameter taking. + diff --git a/go.mod b/go.mod index 4004da58..ca56c5d5 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,21 @@ require ( github.com/hashicorp/hcl/v2 v2.19.1 github.com/hashicorp/packer-plugin-sdk v0.5.2 github.com/pkg/errors v0.9.1 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.799 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.799 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1200 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1072 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.797 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.799 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.1175 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1072 github.com/zclconf/go-cty v1.13.3 ) +require ( + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1200 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization v1.0.1200 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect +) + require ( cloud.google.com/go v0.105.0 // indirect cloud.google.com/go/compute v1.12.1 // indirect @@ -36,7 +44,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.9 // indirect - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.3.0 github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect github.com/googleapis/gax-go/v2 v2.6.0 // indirect github.com/hashicorp/consul/api v1.25.1 // indirect @@ -69,7 +77,7 @@ require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.17 // indirect github.com/mitchellh/go-fs v0.0.0-20180402235330-b7b9ca407fff // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/iochan v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2d636fbc..a321d879 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,15 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.105.0 h1:DNtEKRBAAzeS4KyIory52wWHuClNaXJ5x1F7xa4q+5Y= cloud.google.com/go v0.105.0/go.mod h1:PrLgOJNe5nfE9UMxKxgXj4mD3voiP+YQ6gdt6KMFOKM= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= cloud.google.com/go/compute v1.12.1 h1:gKVJMEyqV5c/UnpzjjQbo3Rjvvqpr9B1DFSbJC4OXr0= cloud.google.com/go/compute v1.12.1/go.mod h1:e8yNOBcBONZU1vJKCvCoDw/4JQsA0dpM4x/6PIIOocU= -cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs= cloud.google.com/go/compute/metadata v0.1.1 h1:/sxEbyrm6cw+XOUw1YxBHlatV71z4vpnmO7z2IZ0h3I= cloud.google.com/go/compute/metadata v0.1.1/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/iam v0.6.0 h1:nsqQC88kT5Iwlm4MeNGTpfMWddp6NB/UOLFTH6m1QfQ= cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= -cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew= cloud.google.com/go/longrunning v0.1.1 h1:y50CXG4j0+qvEukslYFBCrzaXX0qpFbBzc3PchSu/LE= cloud.google.com/go/storage v1.27.0 h1:YOO045NZI9RKfCj1c5A/ZtuuENUc8OAW+gHdGnDgyMQ= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= github.com/Azure/go-ntlmssp v0.0.0-20180810175552-4a21cbd618b4/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c h1:/IBSNwUN8+eKzUzbJPqhK839ygXJ82sde8x3ogr6R28= github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= @@ -73,7 +68,6 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo= github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -124,10 +118,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.2.0 h1:y8Yozv7SZtlU//QXbezB6QkpuE6jMD2/gfzk4AftXjs= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go/v2 v2.6.0 h1:SXk3ABtQYDT/OH8jAyvEOQ58mgawq5C4o/4/89qN2ZU= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= github.com/hashicorp/consul/api v1.25.1 h1:CqrdhYzc8XZuPnhIYZWH45toM0LB9ZeYr/gvpLVI3PE= github.com/hashicorp/consul/api v1.25.1/go.mod h1:iiLVwR/htV7mas/sy0O+XSuEnrdBUUydemjxcUrAt4g= github.com/hashicorp/consul/sdk v0.14.1 h1:ZiwE2bKb+zro68sWzZ1SgHF3kRMBZ94TwOCFRF4ylPs= @@ -147,7 +139,6 @@ github.com/hashicorp/go-getter/v2 v2.2.1/go.mod h1:EcJx6oZE8hmGuRR1l38QrfnyiujQb github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -213,7 +204,6 @@ github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -260,12 +250,13 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nywilken/go-cty v1.13.3 h1:03U99oXf3j3g9xgqAE3YGpixCjM8Mg09KZ0Ji9LzX0o= @@ -307,43 +298,36 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.366/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.367 h1:wZpJtVV05zBriiyAMZtHF7wSgBFUdDiXdnzD/Ecj7Ds= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.367/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.624/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.779/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.797 h1:jDSfrthql0LxaWOkEoQk/bSYRvM/k2+ukjsE6VfStEQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1200 h1:vONBPPPZR83iIJGnrIBjIrFj1Lba8p3tBmwmveabLlo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cam v1.0.1200/go.mod h1:g1HW2Y5P+yi9XH2dL136gEMKogKcNRLoZ4hq9767D48= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.797/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.799 h1:jAMelFh7c+sBrR2kzdNB2zfmkhsEXLIR9YFQcBuTnzI= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.799/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.366 h1:NJm4RjeL2btX3alWLQvyzObmlDtGC0pCFCoeqWw2Veg= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.366/go.mod h1:x9QV7qu6FpnSdVyGQoirhjKsPd1dEpWnr9RL75DpgJ4= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.624/go.mod h1:+TXSVyeKwt1IhZRqKPbTREteBcP+K07Q846/ilNzLWA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.799 h1:FnXNkHQhPX7sNvxKNYyMB6PGpbMCce6bfXkzRwGHS74= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.799/go.mod h1:bNuzbq27CiymhqONoqE1CnhK6aJJjWWcZG8J3ragVfs= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1072/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1175 h1:w0z4dtrinCY3R4aHnw9vcq80XWEIEKXv6c3p779ueRo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1175/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1200 h1:n6elge0PuoOtHt67BhlQka5Y7ChPbCtp23zYDFw56V0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1200/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.31 h1:PKa4c2BLYbW5LUOWGNXt20+rV9L8JnLqBXZjnOXsHKQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.31/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1072 h1:BO6eEqw2CxeP73AnOfkq99mJAPccP+w3exmVuBDMvAc= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.0.1072/go.mod h1:Ot4h9BuqIbyOPotq8cIT3vCCMDdn6LXkc37jU2teHnQ= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.1.31 h1:M6v6WE88puzkxap8QgVSHM3u2Pe80E8uwps8U08FOOk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm v1.1.31/go.mod h1:oMQNF1IsVtrOHdBONFRCWz0T5zqmxzM6JDBPl8ub6EM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization v1.0.1200 h1:hYOvCfgPKpH2OS6+6ZOT+h21CfduIbGfz7RE+Ey1H14= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/organization v1.0.1200/go.mod h1:qYzdOsPWtOJ18uQ/4QupBTF98ariELEH4dx93GiBeuY= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.797 h1:Z9rTZBoR4arEXA9gYLu8AQnMInG1scb+WnlIWczLH2A= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sts v1.0.797/go.mod h1:IugQh1ZI86ZeEUBYf+u/REwTeKZcneP449FPU8BbLxA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.366 h1:7wbTvCCJ41Hx9KWO9pcmvOFWFS1A9iPs0jtQJLwe++U= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.366/go.mod h1:TcIZ64TWquVpU7SmDHScoRUkx4P3Jm/lWq4BYs6IEN8= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.779 h1:4NpjQiFgnIH662ydP7AecllyrhH+CVoGlzQ9V7RfD08= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.779/go.mod h1:kYBG2jgpjL7CuhYM+K1fkEtbWvNXrtt7NSLwXVCqmKA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.799 h1:6M8TGTEvrLAjxaKl53RyDIktCmF8kPuL0swJeKsbR/E= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.799/go.mod h1:jq1PLPim6gB9soBqQ/H6fRAI/NYlj/Qtn8JZfOK+eWw= -github.com/tencentcloudstack/terraform-provider-tencentcloud v1.81.47 h1:6e9miVImuKP6g2HDDyNNTGrh/UnCmgkqJ+CInE6kZ7s= -github.com/tencentcloudstack/terraform-provider-tencentcloud v1.81.47/go.mod h1:YuYFm1AJbAELr9L799QJdLkZVkBaeFHokCeBG4J02Lc= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.1175 h1:Yw7azOzlQqh6/Zyk5JCLH69LJ7nE2jkilGXU+3vIqUM= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag v1.0.1175/go.mod h1:8CNgZPCw4+fKMA/cAsAcdkQUdz6B7xicnFtsLxJWkOI= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1072 h1:qG5L/VZcw4PC+OSMjxM1BNXlOfJ9vvDfToItgmCxfLE= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/vpc v1.0.1072/go.mod h1:fynFW8ciFiCFLVyIKyQKENQ6g+Tm7mXw687lFgPmU/Y= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= @@ -351,15 +335,8 @@ github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxW github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/yangwenmai/ratelimit v0.0.0-20180104140304-44221c2292e1 h1:q6c//IMJug6THoqsseZ+Z/zq53HQvADPh5a66E9hb+I= -github.com/yangwenmai/ratelimit v0.0.0-20180104140304-44221c2292e1/go.mod h1:Rl8MvKI/yVRGN91gMEZAIf/92QtFQBSG/QFRHWQZtmo= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190222235706-ffb98f73852f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -367,9 +344,6 @@ golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 h1:O8uGbHCqlTp2P6QJSLmCojM4mN6UemYv8K+dCnmHmu0= -golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -397,7 +371,6 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -458,7 +431,6 @@ golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3j golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.101.0 h1:lJPPeEBIRxGpGLwnBTam1NPEM8Z2BmmXEd3z812pjwM= google.golang.org/api v0.101.0/go.mod h1:CjxAAWWt3A3VrUE2IGDY2bgK5qhoG/OkyWVlYcP05MY= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= @@ -468,7 +440,6 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c h1:QgY/XxIAIeccR+Ca/rDdKubLIU9rcJ3xfy1DC/Wd2Oo= google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c/go.mod h1:CGI5F/G+E5bKwmfYo09AXuVN4dD894kIKUFmVbP2/Fo= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -476,7 +447,6 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -490,13 +460,12 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -504,7 +473,6 @@ gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 212f8fc5..3267a4e7 100644 --- a/main.go +++ b/main.go @@ -10,12 +10,14 @@ import ( "github.com/hashicorp/packer-plugin-sdk/plugin" "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm" + "github.com/hashicorp/packer-plugin-tencentcloud/datasource/tencentcloud/image" "github.com/hashicorp/packer-plugin-tencentcloud/version" ) func main() { pps := plugin.NewSet() pps.RegisterBuilder("cvm", new(cvm.Builder)) + pps.RegisterDatasource("image", new(image.Datasource)) pps.SetVersion(version.PluginVersion) err := pps.Run() if err != nil {