Skip to content

Commit

Permalink
Add linode-image data source
Browse files Browse the repository at this point in the history
  • Loading branch information
zliang-akamai committed Mar 20, 2024
1 parent 638f175 commit 3dcd769
Show file tree
Hide file tree
Showing 8 changed files with 556 additions and 6 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go-test-multiplatform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
go-version: ${{ needs.get-go-version.outputs.go-version }}
- run: |
echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}"
go test -race -count 1 ./builder/linode/... -timeout=3m -v
go test -race -count 1 ./... -timeout=3m -v
windows-go-tests:
needs:
Expand All @@ -55,7 +55,7 @@ jobs:
# Running unit tests directly with `go test` due to gofmt/gofumpt issues in Windows
- run: |
echo "Testing with Go ${{ needs.get-go-version.outputs.go-version }}"
go test -race -count 1 ./builder/linode/... -timeout=3m -v
go test -race -count 1 ./... -timeout=3m -v
linux-go-tests:
needs:
Expand Down
108 changes: 108 additions & 0 deletions .web-docs/components/data-source/image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
Type: `linode-image`

The Linode Image data source matches or filters the ID or label of both public images on
Linode and private images in your account using regular expression (regex) or an exact
match.

You can get the latest list of available public images on Linode via the
[Linode Image List API](https://www.linode.com/docs/api/images/#images-list).

## Examples

```hcl
data "linode-image" "latest_ubuntu" {
id_regex = "linode/ubuntu.*"
latest = true
}
source "linode" "example" {
image = linode-image.latest_ubuntu.id
image_description = "My Private Image"
image_label = "my-packaer-private-linode-image-test"
instance_label = "temporary-linode-image"
instance_type = "g6-nanode-1"
region = "us-mia"
ssh_username = "root"
}
build {
sources = ["source.linode.example"]
}
```

```hcl
data "linode-image" "latest_ubuntu_lts" {
label_regex = "Ubuntu [0-9]+\\.[0-9]+ LTS"
latest = true
}
```

```hcl
data "linode-image" "ubuntu22_lts" {
id = "linode/ubuntu22.04"
latest = true
}
```

## Configuration Reference:

<!-- Code generated from the comments of the Config struct in datasource/image/data.go; DO NOT EDIT MANUALLY -->

- `label` (string) - Matching the label of an image by exact label

- `label_regex` (string) - Matching the label of an image by a regular expression

- `id` (string) - Matching the ID of an image by exact ID

- `id_regex` (string) - Matching the ID of an image by a regular expression

- `latest` (bool) - Whether to use the latest created image when there are multiple matches

<!-- End of code generated from the comments of the Config struct in datasource/image/data.go; -->

<!-- Code generated from the comments of the LinodeCommon struct in helper/common.go; DO NOT EDIT MANUALLY -->

- `linode_token` (string) - The Linode API token. This can also be specified in LINODE_TOKEN environment variable

<!-- End of code generated from the comments of the LinodeCommon struct in helper/common.go; -->


## Output:

<!-- Code generated from the comments of the DatasourceOutput struct in datasource/image/data.go; DO NOT EDIT MANUALLY -->

- `id` (string) - The unique ID of this Image.

- `capabilities` ([]string) - A list containing the following possible capabilities of this Image:
- cloud-init: This Image supports cloud-init with Metadata. Only applies to public Images.

- `created` (string) - When this Image was created.

- `created_by` (string) - The name of the User who created this Image, or “linode” for public Images.

- `deprecated` (bool) - Whether or not this Image is deprecated. Will only be true for deprecated public Images.

- `description` (string) - A detailed description of this Image.

- `eol` (string) - The date of the public Image’s planned end of life. `null` for private Images.

- `expiry` (string) - Expiry date of the image.
Only Images created automatically from a deleted Linode (type=automatic) will expire.

- `is_public` (bool) - True if the Image is a public distribution image.
False if Image is private Account-specific Image.

- `label` (string) - A short description of the Image.

- `size` (int) - The minimum size this Image needs to deploy. Size is in MB.

- `type` (string) - Enum: `manual` `automatic`
How the Image was created.
"Manual" Images can be created at any time.
"Automatic" Images are created automatically from a deleted Linode.

- `updated` (string) - When this Image was last updated.

- `vendor` (string) - The upstream distribution vendor. `null` for private Images.

<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/image/data.go; -->
8 changes: 4 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ NAME=linode
BINARY=packer-plugin-${NAME}
PLUGIN_FQN="$(shell grep -E '^module' <go.mod | sed -E 's/module *//')"
COUNT?=1
UNIT_TEST_TARGET?=$(shell go list ./builder/...)
TEST?=$(shell go list ./...)
HASHICORP_PACKER_PLUGIN_SDK_VERSION?=$(shell go list -m github.com/hashicorp/packer-plugin-sdk | cut -d " " -f2)
PACKER_SDC_REPO ?= github.com/hashicorp/packer-plugin-sdk/cmd/packer-sdc
.DEFAULT_GOAL = dev
Expand All @@ -21,7 +21,7 @@ build: fmtcheck
@go build -o ${BINARY}

.PHONY: test
test: fmtcheck unit-test int-test
test: fmtcheck acctest

.PHONY: install-packer-sdc
install-packer-sdc: ## Install packer sofware development command
Expand All @@ -33,15 +33,15 @@ plugin-check: install-packer-sdc build

.PHONY: unit-test
unit-test: dev
@go test -count $(COUNT) -v $(UNIT_TEST_TARGET) -timeout=10m
@go test -race -count $(COUNT) -v $(TEST) -timeout=10m

# int-test is an alias of acctest
.PHONY: int-test
int-test: acctest

.PHONY: acctest
acctest: dev
@PACKER_ACC=1 go test -count $(COUNT) ./... -v -timeout=100m
@PACKER_ACC=1 go test -race -count $(COUNT) -v $(TEST) -timeout=100m

.PHONY: generate
generate: install-packer-sdc
Expand Down
190 changes: 190 additions & 0 deletions datasource/image/data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package image

//go:generate packer-sdc struct-markdown
//go:generate packer-sdc mapstructure-to-hcl2 -type DatasourceOutput,Config
import (
"context"
"errors"
"os"
"time"

"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/linode/linodego"
"github.com/linode/packer-plugin-linode/helper"
"github.com/zclconf/go-cty/cty"
)

type Datasource struct {
config Config
}

type Config struct {
common.PackerConfig `mapstructure:",squash"`
helper.LinodeCommon `mapstructure:",squash"`

// Matching the label of an image by exact label
Label string `mapstructure:"label"`

// Matching the label of an image by a regular expression
LabelRegex string `mapstructure:"label_regex"`

// Matching the ID of an image by exact ID
ID string `mapstructure:"id"`

// Matching the ID of an image by a regular expression
IDRegex string `mapstructure:"id_regex"`

// Whether to use the latest created image when there are multiple matches
Latest bool `mapstructure:"latest"`
}

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

if d.config.PersonalAccessToken == "" {
token := os.Getenv("LINODE_TOKEN")
if token == "" {
errs = packersdk.MultiErrorAppend(errs, errors.New(
"A Linode API token is required. You can specify it in an "+
"environment variable LINODE_TOKEN or set linode_token "+
"attribute in the datasource block.",
))
}
d.config.PersonalAccessToken = token
}

if errs != nil && len(errs.Errors) > 0 {
return errs
}

return nil
}

type DatasourceOutput struct {
// The unique ID of this Image.
ID string `mapstructure:"id"`

// A list containing the following possible capabilities of this Image:
// - cloud-init: This Image supports cloud-init with Metadata. Only applies to public Images.
Capabilities []string `mapstructure:"capabilities"`

// When this Image was created.
Created string `mapstructure:"created"`

// The name of the User who created this Image, or “linode” for public Images.
CreatedBy string `mapstructure:"created_by"`

// Whether or not this Image is deprecated. Will only be true for deprecated public Images.
Deprecated bool `mapstructure:"deprecated"`

// A detailed description of this Image.
Description string `mapstructure:"description"`

// The date of the public Image’s planned end of life. `null` for private Images.
EOL string `mapstructure:"eol"`

// Expiry date of the image.
// Only Images created automatically from a deleted Linode (type=automatic) will expire.
Expiry string `mapstructure:"expiry"`

// True if the Image is a public distribution image.
// False if Image is private Account-specific Image.
IsPublic bool `mapstructure:"is_public"`

// A short description of the Image.
Label string `mapstructure:"label"`

// The minimum size this Image needs to deploy. Size is in MB.
Size int `mapstructure:"size"`

// Enum: `manual` `automatic`
// How the Image was created.
// "Manual" Images can be created at any time.
// "Automatic" Images are created automatically from a deleted Linode.
Type string `mapstructure:"type"`

// When this Image was last updated.
Updated string `mapstructure:"updated"`

// The upstream distribution vendor. `null` for private Images.
Vendor string `mapstructure:"vendor"`
}

func (d *Datasource) OutputSpec() hcldec.ObjectSpec {
return (&DatasourceOutput{}).FlatMapstructure().HCL2Spec()
}

func (d *Datasource) Execute() (cty.Value, error) {
client := helper.NewLinodeClient(d.config.PersonalAccessToken)

filters := linodego.Filter{}

// Label is API filterable
if d.config.Label != "" {
filters.AddField(linodego.Eq, "label", d.config.Label)
}

// we only want available images for the obvious reason
filters.AddField(linodego.Eq, "status", "available")

filterString, err := filters.MarshalJSON()
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

images, err := client.ListImages(
context.Background(),
linodego.NewListOptions(0, string(filterString)),
)
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

// filtering non-API filterable attributes
image, err := filterImageResults(images, d.config)
if err != nil {
return cty.NullVal(cty.EmptyObject), err
}

return hcl2helper.HCL2ValueFromConfig(getOutput(image), d.OutputSpec()), nil
}

func getOutput(image linodego.Image) DatasourceOutput {
output := DatasourceOutput{
ID: image.ID,
Capabilities: image.Capabilities,
CreatedBy: image.CreatedBy,
Deprecated: image.Deprecated,
Description: image.Description,
IsPublic: image.IsPublic,
Label: image.Label,
Size: image.Size,
Type: image.Type,
Vendor: image.Vendor,
Created: image.Created.Format(time.RFC3339),
Updated: image.Updated.Format(time.RFC3339),

Check failure on line 178 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Windows Go tests

image.Updated undefined (type linodego.Image has no field or method Updated)

Check failure on line 178 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Darwin Go tests

image.Updated undefined (type linodego.Image has no field or method Updated)

Check failure on line 178 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.Updated undefined (type linodego.Image has no field or method Updated)

Check failure on line 178 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.Updated undefined (type linodego.Image has no field or method Updated)

Check failure on line 178 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Linux Go tests

image.Updated undefined (type linodego.Image has no field or method Updated)
}

if image.EOL != nil {

Check failure on line 181 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Windows Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 181 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Darwin Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 181 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 181 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 181 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Linux Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)
output.EOL = image.EOL.Format(time.RFC3339)

Check failure on line 182 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Windows Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 182 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Darwin Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)

Check failure on line 182 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.EOL undefined (type linodego.Image has no field or method EOL) (typecheck)

Check failure on line 182 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Lint check

image.EOL undefined (type linodego.Image has no field or method EOL)) (typecheck)

Check failure on line 182 in datasource/image/data.go

View workflow job for this annotation

GitHub Actions / Linux Go tests

image.EOL undefined (type linodego.Image has no field or method EOL)
}

if image.Expiry != nil {
output.Expiry = image.Expiry.Format(time.RFC3339)
}

return output
}
Loading

0 comments on commit 3dcd769

Please sign in to comment.