|
| 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