Skip to content

Commit 641d3cf

Browse files
committed
Add a "tencentcloud-image" data source for querying images
Fixes hashicorp#68
1 parent 1446542 commit 641d3cf

File tree

8 files changed

+361
-0
lines changed

8 files changed

+361
-0
lines changed

datasource/tencentcloud/image/data.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
//go:generate packer-sdc struct-markdown
5+
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,DatasourceOutput
6+
package image
7+
8+
import (
9+
"fmt"
10+
"github.com/hashicorp/hcl/v2/hcldec"
11+
"github.com/hashicorp/packer-plugin-sdk/common"
12+
"github.com/hashicorp/packer-plugin-sdk/hcl2helper"
13+
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
14+
"github.com/hashicorp/packer-plugin-sdk/template/config"
15+
builder "github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm"
16+
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
17+
"github.com/zclconf/go-cty/cty"
18+
)
19+
20+
type ImageFilterOptions struct {
21+
// Filters used to select an image. Any filter described in the documentation for
22+
//[DescribeImages](https://www.tencentcloud.com/document/product/213/33272) can be used.
23+
Filters map[string]string `mapstructure:"filters"`
24+
// Selects the most recently created image when multiple results are returned. Note that
25+
// public images don't have a creation date, so this flag is only really useful for private
26+
// images.
27+
MostRecent bool `mapstructure:"most_recent"`
28+
}
29+
30+
type Config struct {
31+
common.PackerConfig `mapstructure:",squash"`
32+
builder.TencentCloudAccessConfig `mapstructure:",squash"`
33+
ImageFilterOptions `mapstructure:",squash"`
34+
}
35+
36+
type Datasource struct {
37+
config Config
38+
}
39+
40+
type DatasourceOutput struct {
41+
// The image ID
42+
ID string `mapstructure:"id"`
43+
// The image name
44+
Name string `mapstructure:"name"`
45+
}
46+
47+
func (d *Datasource) ConfigSpec() hcldec.ObjectSpec {
48+
return d.config.FlatMapstructure().HCL2Spec()
49+
}
50+
51+
func (d *Datasource) Configure(raws ...interface{}) error {
52+
err := config.Decode(&d.config, nil, raws...)
53+
if err != nil {
54+
return err
55+
}
56+
57+
var errs *packersdk.MultiError
58+
errs = packersdk.MultiErrorAppend(errs, d.config.TencentCloudAccessConfig.Prepare()...)
59+
60+
if len(d.config.Filters) == 0 {
61+
errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("`filters` must be specified"))
62+
}
63+
64+
if errs != nil && len(errs.Errors) > 0 {
65+
return errs
66+
}
67+
return nil
68+
}
69+
70+
func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
71+
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
72+
}
73+
74+
func (d *Datasource) Execute() (cty.Value, error) {
75+
client, _, err := d.config.Client()
76+
if err != nil {
77+
return cty.NullVal(cty.EmptyObject), err
78+
}
79+
80+
req := cvm.NewDescribeImagesRequest()
81+
82+
var filters []*cvm.Filter
83+
for k, v := range d.config.Filters {
84+
k := k
85+
v := v
86+
filters = append(filters, &cvm.Filter{
87+
Name: &k,
88+
Values: []*string{&v},
89+
})
90+
}
91+
req.Filters = filters
92+
93+
resp, err := client.DescribeImages(req)
94+
if err != nil {
95+
return cty.NullVal(cty.EmptyObject), err
96+
}
97+
98+
if *resp.Response.TotalCount == 0 {
99+
return cty.NullVal(cty.EmptyObject), nil
100+
}
101+
102+
if *resp.Response.TotalCount > 1 && !d.config.MostRecent {
103+
return cty.NullVal(cty.EmptyObject), fmt.Errorf("Your image query returned more than result. Please try a more specific search, or set `most_recent` to `true`.")
104+
}
105+
106+
var image *cvm.Image
107+
108+
if d.config.MostRecent {
109+
image = mostRecentImage(resp.Response.ImageSet)
110+
} else {
111+
image = resp.Response.ImageSet[0]
112+
}
113+
114+
output := DatasourceOutput{
115+
ID: *image.ImageId,
116+
Name: *image.ImageName,
117+
}
118+
return hcl2helper.HCL2ValueFromConfig(output, d.OutputSpec()), nil
119+
}

datasource/tencentcloud/image/data.hcl2spec.go

Lines changed: 95 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package image
5+
6+
import (
7+
_ "embed"
8+
"fmt"
9+
"io/ioutil"
10+
"os"
11+
"os/exec"
12+
"regexp"
13+
"testing"
14+
15+
"github.com/hashicorp/packer-plugin-sdk/acctest"
16+
)
17+
18+
//go:embed test-fixtures/template.pkr.hcl
19+
var testDatasourceHCL2Basic string
20+
21+
// Run with: PACKER_ACC=1 go test -count 1 -v ./datasource/image/data_acc_test.go -timeout=120m
22+
func TestAccImageDatasource(t *testing.T) {
23+
testCase := &acctest.PluginTestCase{
24+
Name: "image_datasource_basic_test",
25+
Setup: func() error {
26+
return nil
27+
},
28+
Teardown: func() error {
29+
return nil
30+
},
31+
Template: testDatasourceHCL2Basic,
32+
Type: "image-my-datasource",
33+
Check: func(buildCommand *exec.Cmd, logfile string) error {
34+
if buildCommand.ProcessState != nil {
35+
if buildCommand.ProcessState.ExitCode() != 0 {
36+
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
37+
}
38+
}
39+
40+
logs, err := os.Open(logfile)
41+
if err != nil {
42+
return fmt.Errorf("Unable find %s", logfile)
43+
}
44+
defer logs.Close()
45+
46+
logsBytes, err := ioutil.ReadAll(logs)
47+
if err != nil {
48+
return fmt.Errorf("Unable to read %s", logfile)
49+
}
50+
logsString := string(logsBytes)
51+
52+
idLog := "null.basic-example: id: img-39ei7bw5"
53+
nameLog := "null.basic-example: name: Rocky Linux 9.4 64bit"
54+
55+
if matched, _ := regexp.MatchString(idLog+".*", logsString); !matched {
56+
t.Fatalf("logs doesn't contain expected ID value %q", logsString)
57+
}
58+
if matched, _ := regexp.MatchString(nameLog+".*", logsString); !matched {
59+
t.Fatalf("logs doesn't contain expected name value %q", logsString)
60+
}
61+
62+
return nil
63+
},
64+
}
65+
acctest.TestPlugin(t, testCase)
66+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package image
2+
3+
import (
4+
cvm "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/cvm/v20170312"
5+
"sort"
6+
"time"
7+
)
8+
9+
type imageSort []*cvm.Image
10+
11+
func (a imageSort) Len() int { return len(a) }
12+
func (a imageSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
13+
func (a imageSort) Less(i, j int) bool {
14+
// Public images don't have a creation time
15+
if a[i].CreatedTime == nil || a[j].CreatedTime == nil {
16+
return false
17+
}
18+
19+
itime, _ := time.Parse(time.RFC3339, *a[i].CreatedTime)
20+
jtime, _ := time.Parse(time.RFC3339, *a[j].CreatedTime)
21+
return itime.Before(jtime)
22+
}
23+
24+
func mostRecentImage(images []*cvm.Image) *cvm.Image {
25+
sortedImages := images
26+
sort.Sort(imageSort(sortedImages))
27+
28+
return sortedImages[len(sortedImages)-1]
29+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Copyright (c) HashiCorp, Inc.
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
data "tencentcloud-image" "test-image" {
5+
filters = {
6+
image-type = "PUBLIC_IMAGE"
7+
platform = "Rocky Linux"
8+
image-name = "Rocky Linux 9.4 64bit"
9+
}
10+
most_recent = true
11+
}
12+
13+
locals {
14+
id = data.tencentcloud-image.test-image.id
15+
name = data.tencentcloud-image.test-image.name
16+
}
17+
18+
source "null" "basic-example" {
19+
communicator = "none"
20+
}
21+
22+
build {
23+
sources = [
24+
"source.null.basic-example"
25+
]
26+
27+
provisioner "shell-local" {
28+
inline = [
29+
"echo id: ${local.id}",
30+
"echo name: ${local.name}",
31+
]
32+
}
33+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!-- Code generated from the comments of the DatasourceOutput struct in datasource/tencentcloud/image/data.go; DO NOT EDIT MANUALLY -->
2+
3+
- `id` (string) - The image ID
4+
5+
- `name` (string) - The image name
6+
7+
<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/tencentcloud/image/data.go; -->
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!-- Code generated from the comments of the ImageFilterOptions struct in datasource/tencentcloud/image/data.go; DO NOT EDIT MANUALLY -->
2+
3+
- `filters` (map[string]string) - Filters used to select an image. Any filter described in the documentation for
4+
[DescribeImages](https://www.tencentcloud.com/document/product/213/33272) can be used.
5+
6+
- `most_recent` (bool) - Selects the most recently created image when multiple results are returned. Note that
7+
public images don't have a creation date, so this flag is only really useful for private
8+
images.
9+
10+
<!-- End of code generated from the comments of the ImageFilterOptions struct in datasource/tencentcloud/image/data.go; -->

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import (
1010
"github.com/hashicorp/packer-plugin-sdk/plugin"
1111

1212
"github.com/hashicorp/packer-plugin-tencentcloud/builder/tencentcloud/cvm"
13+
"github.com/hashicorp/packer-plugin-tencentcloud/datasource/tencentcloud/image"
1314
"github.com/hashicorp/packer-plugin-tencentcloud/version"
1415
)
1516

1617
func main() {
1718
pps := plugin.NewSet()
1819
pps.RegisterBuilder("cvm", new(cvm.Builder))
20+
pps.RegisterDatasource("image", new(image.Datasource))
1921
pps.SetVersion(version.PluginVersion)
2022
err := pps.Run()
2123
if err != nil {

0 commit comments

Comments
 (0)