Skip to content

Commit 368296e

Browse files
author
mikatong
committed
add datasource image
1 parent d605d92 commit 368296e

File tree

18 files changed

+605
-38
lines changed

18 files changed

+605
-38
lines changed

.web-docs/components/builder/cvm/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can
2424
reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
2525
for parameter taking.
2626

27-
- `zone` (string) - The zone where your cvm will be launch. You should
28-
reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
29-
for parameter taking.
30-
3127
<!-- End of code generated from the comments of the TencentCloudAccessConfig struct in builder/tencentcloud/cvm/access_config.go; -->
3228

3329

@@ -37,6 +33,10 @@ a [communicator](/packer/docs/templates/legacy_json_templates/communicator) can
3733
You should reference [Instance Type](https://intl.cloud.tencent.com/document/product/213/11518)
3834
for parameter taking.
3935

36+
- `zone` (string) - The zone where your cvm will be launch. You should
37+
reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
38+
for parameter taking.
39+
4040
<!-- End of code generated from the comments of the TencentCloudRunConfig struct in builder/tencentcloud/cvm/run_config.go; -->
4141

4242

builder/tencentcloud/cvm/access_config.go

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
package cvm
88

99
import (
10-
"context"
1110
"fmt"
1211
"os"
1312
"strconv"
@@ -76,10 +75,6 @@ type TencentCloudAccessConfig struct {
7675
// reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
7776
// for parameter taking.
7877
Region string `mapstructure:"region" required:"true"`
79-
// The zone where your cvm will be launch. You should
80-
// reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
81-
// for parameter taking.
82-
Zone string `mapstructure:"zone" required:"true"`
8378
// The endpoint you want to reach the cloud endpoint,
8479
// if tce cloud you should set a tce cvm endpoint.
8580
CvmEndpoint string `mapstructure:"cvm_endpoint" required:"false"`
@@ -131,17 +126,12 @@ func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
131126
err error
132127
cvm_client *cvm.Client
133128
vpc_client *vpc.Client
134-
resp *cvm.DescribeZonesResponse
135129
)
136130

137131
if err = cf.validateRegion(); err != nil {
138132
return nil, nil, err
139133
}
140134

141-
if cf.Zone == "" {
142-
return nil, nil, fmt.Errorf("parameter zone must be set")
143-
}
144-
145135
if cvm_client, err = NewCvmClient(cf); err != nil {
146136
return nil, nil, err
147137
}
@@ -150,23 +140,7 @@ func (cf *TencentCloudAccessConfig) Client() (*cvm.Client, *vpc.Client, error) {
150140
return nil, nil, err
151141
}
152142

153-
ctx := context.TODO()
154-
err = Retry(ctx, func(ctx context.Context) error {
155-
var e error
156-
resp, e = cvm_client.DescribeZones(nil)
157-
return e
158-
})
159-
if err != nil {
160-
return nil, nil, err
161-
}
162-
163-
for _, zone := range resp.Response.ZoneSet {
164-
if cf.Zone == *zone.Zone {
165-
return cvm_client, vpc_client, nil
166-
}
167-
}
168-
169-
return nil, nil, fmt.Errorf("unknown zone: %s", cf.Zone)
143+
return cvm_client, vpc_client, nil
170144
}
171145

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

builder/tencentcloud/cvm/builder.hcl2spec.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

builder/tencentcloud/cvm/common.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,30 @@ func GetImageByName(ctx context.Context, client *cvm.Client, imageName string) (
109109
return nil, nil
110110
}
111111

112+
// GetImages
113+
func GetImages(ctx context.Context, client *cvm.Client, filters []*cvm.Filter) ([]*cvm.Image, error) {
114+
req := cvm.NewDescribeImagesRequest()
115+
if len(filters) > 0 {
116+
req.Filters = filters
117+
}
118+
119+
var resp *cvm.DescribeImagesResponse
120+
err := Retry(ctx, func(ctx context.Context) error {
121+
var e error
122+
resp, e = client.DescribeImages(req)
123+
return e
124+
})
125+
if err != nil {
126+
return nil, err
127+
}
128+
129+
if *resp.Response.TotalCount > 0 {
130+
return resp.Response.ImageSet, nil
131+
}
132+
133+
return nil, nil
134+
}
135+
112136
// NewCvmClient returns a new cvm client
113137
func NewCvmClient(cf *TencentCloudAccessConfig) (client *cvm.Client, err error) {
114138
apiV3Conn, err := packerConfigClient(cf)

builder/tencentcloud/cvm/run_config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ type TencentCloudRunConfig struct {
109109
// Communicator settings
110110
Comm communicator.Config `mapstructure:",squash"`
111111
SSHPrivateIp bool `mapstructure:"ssh_private_ip"`
112+
// The zone where your cvm will be launch. You should
113+
// reference [Region and Zone](https://intl.cloud.tencent.com/document/product/213/6091)
114+
// for parameter taking.
115+
Zone string `mapstructure:"zone" required:"true"`
112116
}
113117

114118
var ValidCBSType = []string{
@@ -125,6 +129,10 @@ func (cf *TencentCloudRunConfig) Prepare(ctx *interpolate.Context) []error {
125129
}
126130

127131
errs := cf.Comm.Prepare(ctx)
132+
if cf.Zone == "" {
133+
errs = append(errs, errors.New("zone must be specified"))
134+
}
135+
128136
if cf.SourceImageId == "" && cf.SourceImageName == "" {
129137
errs = append(errs, errors.New("source_image_id or source_image_name must be specified"))
130138
}

builder/tencentcloud/cvm/step_copy_image.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ func (s *stepCopyImage) Run(ctx context.Context, state multistep.StateBag) multi
5858
cf := &TencentCloudAccessConfig{
5959
SecretId: config.SecretId,
6060
SecretKey: config.SecretKey,
61-
Zone: config.Zone,
6261
CvmEndpoint: config.CvmEndpoint,
6362
SecurityToken: config.SecurityToken,
6463
AssumeRole: TencentCloudAccessRole{
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
data "tencentcloud-image" "test-image" {
2+
filters = {
3+
image-type = "PRIVATE_IMAGE"
4+
}
5+
most_recent = true
6+
region = "ap-guangzhou"
7+
}
8+
9+
locals {
10+
id = data.tencentcloud-image.test-image.id
11+
name = data.tencentcloud-image.test-image.name
12+
}
13+
14+
source "null" "basic-example" {
15+
communicator = "none"
16+
}
17+
18+
build {
19+
sources = [
20+
"source.null.basic-example"
21+
]
22+
23+
provisioner "shell-local" {
24+
inline = [
25+
"echo id: ${local.id}",
26+
"echo name: ${local.name}",
27+
]
28+
}
29+
}

datasource/tencentcloud/image/data.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config,Image
5+
6+
package image
7+
8+
import (
9+
"context"
10+
"fmt"
11+
12+
"github.com/hashicorp/hcl/v2/hcldec"
13+
"github.com/hashicorp/packer-plugin-sdk/common"
14+
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
15+
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
16+
"github.com/hashicorp/packer-plugin-sdk/template/config"
17+
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
18+
buildCvm "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm"
19+
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
20+
"github.com/zclconf/go-cty/cty"
21+
)
22+
23+
type ImageFilterOptions struct {
24+
// Filters used to select an image. Any filter described in the documentation for
25+
// [DescribeImages](https://www.tencentcloud.com/document/product/213/33272) can be used.
26+
Filters map[string]string `mapstructure:"filters"`
27+
// Image family used to select an image. Uses the
28+
// [DescribeImageFromFamily](https://www.tencentcloud.com/document/product/213/64971) API.
29+
// Mutually exclusive with `filters`, and `most_recent` will have no effect.
30+
ImageFamily string `mapstructure:"image_family"`
31+
// Selects the most recently created image when multiple results are returned. Note that
32+
// public images don't have a creation date, so this flag is only really useful for private
33+
// images.
34+
MostRecent bool `mapstructure:"most_recent"`
35+
}
36+
37+
type Config struct {
38+
common.PackerConfig `mapstructure:",squash"`
39+
buildCvm.TencentCloudAccessConfig `mapstructure:",squash"`
40+
ImageFilterOptions `mapstructure:",squash"`
41+
ctx interpolate.Context
42+
}
43+
44+
type Datasource struct {
45+
config Config
46+
}
47+
48+
func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
49+
return d.config.FlatMapstructure().HCL2Spec()
50+
}
51+
52+
func (d *Datasource) Configure(raws ...interface{}) error {
53+
err := config.Decode(&d.config, nil, raws...)
54+
if err != nil {
55+
return err
56+
}
57+
58+
var errs *packersdk.MultiError
59+
errs = packersdk.MultiErrorAppend(errs, d.config.TencentCloudAccessConfig.Prepare(&d.config.ctx)...)
60+
61+
if len(d.config.Filters) == 0 && d.config.ImageFamily == "" {
62+
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`filters` or `image_family` must be specified"))
63+
}
64+
65+
if len(d.config.Filters) > 0 && d.config.ImageFamily != "" {
66+
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`filters` and `image_family` are mutually exclusive"))
67+
}
68+
69+
if errs != nil && len(errs.Errors) > 0 {
70+
return errs
71+
}
72+
return nil
73+
}
74+
75+
type DatasourceOutput struct {
76+
// The image ID
77+
ID string `mapstructure:"id"`
78+
// The image name
79+
Name string `mapstructure:"name"`
80+
}
81+
82+
func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
83+
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
84+
}
85+
86+
func (d *Datasource) Execute() (cty.Value, error) {
87+
var image *cvm.Image
88+
var err error
89+
90+
if len(d.config.Filters) > 0 {
91+
image, err = d.ResolveImageByFilters()
92+
} else {
93+
image, err = d.ResolveImageByImageFamily()
94+
}
95+
96+
if err != nil {
97+
return cty.NullVal(cty.EmptyObject), err
98+
}
99+
100+
output := DatasourceOutput{
101+
ID: *image.ImageId,
102+
Name: *image.ImageName,
103+
}
104+
return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
105+
}
106+
107+
func (d *Datasource) ResolveImageByFilters() (*cvm.Image, error) {
108+
client, _, err := d.config.Client()
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
req := cvm.NewDescribeImagesRequest()
114+
115+
var filters []*cvm.Filter
116+
for k, v := range d.config.Filters {
117+
k := k
118+
v := v
119+
filters = append(filters, &cvm.Filter{
120+
Name: &k,
121+
Values: []*string{&v},
122+
})
123+
}
124+
req.Filters = filters
125+
126+
ctx := context.TODO()
127+
var resp *cvm.DescribeImagesResponse
128+
err = buildCvm.Retry(ctx, func(ctx context.Context) error {
129+
var e error
130+
resp, e = client.DescribeImages(req)
131+
return e
132+
})
133+
if err != nil {
134+
return nil, err
135+
}
136+
137+
if *resp.Response.TotalCount == 0 {
138+
return nil, fmt.Errorf("No image found using the specified filters")
139+
}
140+
141+
if *resp.Response.TotalCount > 1 && !d.config.MostRecent {
142+
return nil, fmt.Errorf("Your image query returned more than result. Please try a more specific search, or set `most_recent` to `true`.")
143+
}
144+
145+
if d.config.MostRecent {
146+
return mostRecentImage(resp.Response.ImageSet), nil
147+
} else {
148+
return resp.Response.ImageSet[0], nil
149+
}
150+
}
151+
152+
func (d *Datasource) ResolveImageByImageFamily() (*cvm.Image, error) {
153+
client, _, err := d.config.Client()
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
var resp *cvm.DescribeImageFromFamilyResponse
159+
req := cvm.NewDescribeImageFromFamilyRequest()
160+
req.ImageFamily = &d.config.ImageFamily
161+
162+
ctx := context.TODO()
163+
err = buildCvm.Retry(ctx, func(ctx context.Context) error {
164+
var e error
165+
resp, e = client.DescribeImageFromFamily(req)
166+
return e
167+
})
168+
169+
if err != nil {
170+
return nil, err
171+
}
172+
173+
if resp.Response.Image == nil {
174+
return nil, fmt.Errorf("No image found using the specified image family")
175+
}
176+
177+
return resp.Response.Image, nil
178+
}

0 commit comments

Comments
 (0)