Skip to content

feat: add datasource image #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 5, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
add datasource image
  • Loading branch information
mikatong committed Jun 4, 2025
commit 368296ebf085584b24dc220095b2c99e3d42c8b5
8 changes: 4 additions & 4 deletions .web-docs/components/builder/cvm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<!-- End of code generated from the comments of the TencentCloudAccessConfig struct in builder/tencentcloud/cvm/access_config.go; -->


Expand All @@ -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.

<!-- End of code generated from the comments of the TencentCloudRunConfig struct in builder/tencentcloud/cvm/run_config.go; -->


Expand Down
28 changes: 1 addition & 27 deletions builder/tencentcloud/cvm/access_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package cvm

import (
"context"
"fmt"
"os"
"strconv"
Expand Down Expand Up @@ -76,10 +75,6 @@ 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"`
Expand Down Expand Up @@ -131,17 +126,12 @@ func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
err error
cvm_client *cvm.Client
vpc_client *vpc.Client
resp *cvm.DescribeZonesResponse
)

if err = cf.validateRegion(); err != nil {
return nil, nil, err
}

if cf.Zone == "" {
return nil, nil, fmt.Errorf("parameter zone must be set")
}

if cvm_client, err = NewCvmClient(cf); err != nil {
return nil, nil, err
}
Expand All @@ -150,23 +140,7 @@ func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
return nil, 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
}

for _, zone := range resp.Response.ZoneSet {
if cf.Zone == *zone.Zone {
return cvm_client, vpc_client, nil
}
}

return nil, nil, fmt.Errorf("unknown zone: %s", cf.Zone)
return cvm_client, vpc_client, nil
}

func (cf *TencentCloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
Expand Down
4 changes: 2 additions & 2 deletions builder/tencentcloud/cvm/builder.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions builder/tencentcloud/cvm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,30 @@ func GetImageByName(ctx context.Context, client *cvm.Client, imageName string) (
return nil, nil
}

// GetImages
func GetImages(ctx context.Context, client *cvm.Client, filters []*cvm.Filter) ([]*cvm.Image, error) {
req := cvm.NewDescribeImagesRequest()
if len(filters) > 0 {
req.Filters = filters
}

var resp *cvm.DescribeImagesResponse
err := Retry(ctx, func(ctx context.Context) error {
var e error
resp, e = client.DescribeImages(req)
return e
})
if err != nil {
return nil, err
}

if *resp.Response.TotalCount > 0 {
return resp.Response.ImageSet, nil
}

return nil, nil
}

// NewCvmClient returns a new cvm client
func NewCvmClient(cf *TencentCloudAccessConfig) (client *cvm.Client, err error) {
apiV3Conn, err := packerConfigClient(cf)
Expand Down
8 changes: 8 additions & 0 deletions builder/tencentcloud/cvm/run_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ type TencentCloudRunConfig struct {
// 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{
Expand All @@ -125,6 +129,10 @@ func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error {
}

errs := cf.Comm.Prepare(ctx)
if cf.Zone == "" {
errs = append(errs, errors.New("zone must be specified"))
}

if cf.SourceImageId == "" && cf.SourceImageName == "" {
errs = append(errs, errors.New("source_image_id or source_image_name must be specified"))
}
Expand Down
1 change: 0 additions & 1 deletion builder/tencentcloud/cvm/step_copy_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,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{
Expand Down
29 changes: 29 additions & 0 deletions datasource/tencentcloud/examples/image.pkr.hcl
Original file line number Diff line number Diff line change
@@ -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}",
]
}
}
178 changes: 178 additions & 0 deletions datasource/tencentcloud/image/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
// 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) {
client, _, err := d.config.Client()
if err != nil {
return nil, err
}

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 = client.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) {
client, _, err := d.config.Client()
if err != nil {
return nil, err
}

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 = client.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
}
Loading
Loading