Skip to content
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

Add a "tencentcloud-image" data source for querying images #139

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ dist/*
packer-plugin-tencentcloud
.docs
crash.log
.idea/
3 changes: 1 addition & 2 deletions builder/tencentcloud/cvm/access_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
"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"
Expand Down Expand Up @@ -174,7 +173,7 @@ func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
return nil, nil, fmt.Errorf("unknown zone: %s", cf.Zone)
}

func (cf *TencentCloudAccessConfig) Prepare(ctx *interpolate.Context) []error {
func (cf *TencentCloudAccessConfig) Prepare() []error {
var errs []error

if err := cf.Config(); err != nil {
Expand Down
8 changes: 4 additions & 4 deletions builder/tencentcloud/cvm/access_config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@ func TestTencentCloudAccessConfig_Prepare(t *testing.T) {
SecretKey: "secret-key",
}

if err := cf.Prepare(nil); err == nil {
if err := cf.Prepare(); err == nil {
t.Fatal("should raise error: region not set")
}

cf.Region = "ap-guangzhou"
if err := cf.Prepare(nil); err != nil {
if err := cf.Prepare(); err != nil {
t.Fatalf("shouldn't raise error: %v", err)
}

cf.Region = "unknown-region"
if err := cf.Prepare(nil); err == nil {
if err := cf.Prepare(); err == nil {
t.Fatal("should raise error: unknown region")
}

cf.skipValidation = true
if err := cf.Prepare(nil); err != nil {
if err := cf.Prepare(); err != nil {
t.Fatalf("shouldn't raise error: %v", err)
}
}
2 changes: 1 addition & 1 deletion builder/tencentcloud/cvm/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {

// Accumulate any errors
var errs *packersdk.MultiError
errs = packersdk.MultiErrorAppend(errs, b.config.TencentCloudAccessConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.TencentCloudAccessConfig.Prepare()...)
errs = packersdk.MultiErrorAppend(errs, b.config.TencentCloudImageConfig.Prepare(&b.config.ctx)...)
errs = packersdk.MultiErrorAppend(errs, b.config.TencentCloudRunConfig.Prepare(&b.config.ctx)...)
if errs != nil && len(errs.Errors) > 0 {
Expand Down
7 changes: 0 additions & 7 deletions builder/tencentcloud/cvm/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"context"
"fmt"
"net/url"
"regexp"
"strings"
"time"

Expand Down Expand Up @@ -143,12 +142,6 @@ func NewVpcClient(cf *TencentCloudAccessConfig) (client *vpc.Client, err error)
return
}

// CheckResourceIdFormat check resource id format
func CheckResourceIdFormat(resource string, id string) bool {
regex := regexp.MustCompile(fmt.Sprintf("%s-[0-9a-z]{8}$", resource))
return regex.MatchString(id)
}

// SSHHost returns a function that can be given to the SSH communicator
func SSHHost(pubilcIp bool) func(multistep.StateBag) (string, error) {
return func(state multistep.StateBag) (string, error) {
Expand Down
4 changes: 0 additions & 4 deletions builder/tencentcloud/cvm/run_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,6 @@ func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error {
errs = append(errs, errors.New("source_image_id or source_image_name must be specified"))
}

if cf.SourceImageId != "" && !CheckResourceIdFormat("img", cf.SourceImageId) {
errs = append(errs, errors.New("source_image_id wrong format"))
}

if cf.InstanceType == "" {
errs = append(errs, errors.New("instance_type must be specified"))
}
Expand Down
160 changes: 160 additions & 0 deletions datasource/tencentcloud/image/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,DatasourceOutput
package image

import (
"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"
builder "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"`
builder.TencentCloudAccessConfig `mapstructure:",squash"`
ImageFilterOptions `mapstructure:",squash"`
}

type Datasource struct {
config Config
}

type DatasourceOutput struct {
// The image ID
ID string `mapstructure:"id"`
// The image name
Name string `mapstructure:"name"`
}

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()...)

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
}

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

resp, err := client.DescribeImages(req)
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
}

req := cvm.NewDescribeImageFromFamilyRequest()
req.ImageFamily = &d.config.ImageFamily

resp, err := client.DescribeImageFromFamily(req)

if err != nil {
return nil, err
} else if resp.Response.Image == nil {
return nil, fmt.Errorf("No image found using the specified image family")
} else {
return resp.Response.Image, nil
}
}
97 changes: 97 additions & 0 deletions datasource/tencentcloud/image/data.hcl2spec.go

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

Loading