diff --git a/.github/workflows/go-test-multiplatform.yml b/.github/workflows/go-test-multiplatform.yml index 098be93e..af7346b1 100644 --- a/.github/workflows/go-test-multiplatform.yml +++ b/.github/workflows/go-test-multiplatform.yml @@ -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: @@ -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: diff --git a/.web-docs/components/data-source/image/README.md b/.web-docs/components/data-source/image/README.md new file mode 100644 index 00000000..90505b43 --- /dev/null +++ b/.web-docs/components/data-source/image/README.md @@ -0,0 +1,109 @@ +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 = data.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: + + + +- `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 + + + + + +- `linode_token` (string) - The Linode API token required for provision Linode resources. + This can also be specified in LINODE_TOKEN environment variable. + + + + +## Output: + + + +- `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. + + diff --git a/Makefile b/Makefile index 9cc02873..ff21cd7a 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ NAME=linode BINARY=packer-plugin-${NAME} PLUGIN_FQN="$(shell grep -E '^module' 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), + } + + if image.EOL != nil { + output.EOL = image.EOL.Format(time.RFC3339) + } + + if image.Expiry != nil { + output.Expiry = image.Expiry.Format(time.RFC3339) + } + + return output +} diff --git a/datasource/image/data.hcl2spec.go b/datasource/image/data.hcl2spec.go new file mode 100644 index 00000000..1b2192d2 --- /dev/null +++ b/datasource/image/data.hcl2spec.go @@ -0,0 +1,106 @@ +// Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. + +package image + +import ( + "github.com/hashicorp/hcl/v2/hcldec" + "github.com/zclconf/go-cty/cty" +) + +// FlatConfig is an auto-generated flat version of Config. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatConfig struct { + PackerBuildName *string `mapstructure:"packer_build_name" cty:"packer_build_name" hcl:"packer_build_name"` + PackerBuilderType *string `mapstructure:"packer_builder_type" cty:"packer_builder_type" hcl:"packer_builder_type"` + PackerCoreVersion *string `mapstructure:"packer_core_version" cty:"packer_core_version" hcl:"packer_core_version"` + PackerDebug *bool `mapstructure:"packer_debug" cty:"packer_debug" hcl:"packer_debug"` + PackerForce *bool `mapstructure:"packer_force" cty:"packer_force" hcl:"packer_force"` + PackerOnError *string `mapstructure:"packer_on_error" cty:"packer_on_error" hcl:"packer_on_error"` + PackerUserVars map[string]string `mapstructure:"packer_user_variables" cty:"packer_user_variables" hcl:"packer_user_variables"` + PackerSensitiveVars []string `mapstructure:"packer_sensitive_variables" cty:"packer_sensitive_variables" hcl:"packer_sensitive_variables"` + PersonalAccessToken *string `mapstructure:"linode_token" cty:"linode_token" hcl:"linode_token"` + Label *string `mapstructure:"label" cty:"label" hcl:"label"` + LabelRegex *string `mapstructure:"label_regex" cty:"label_regex" hcl:"label_regex"` + ID *string `mapstructure:"id" cty:"id" hcl:"id"` + IDRegex *string `mapstructure:"id_regex" cty:"id_regex" hcl:"id_regex"` + Latest *bool `mapstructure:"latest" cty:"latest" hcl:"latest"` +} + +// FlatMapstructure returns a new FlatConfig. +// FlatConfig is an auto-generated flat version of Config. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*Config) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatConfig) +} + +// HCL2Spec returns the hcl spec of a Config. +// This spec is used by HCL to read the fields of Config. +// The decoded values from this spec will then be applied to a FlatConfig. +func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "packer_build_name": &hcldec.AttrSpec{Name: "packer_build_name", Type: cty.String, Required: false}, + "packer_builder_type": &hcldec.AttrSpec{Name: "packer_builder_type", Type: cty.String, Required: false}, + "packer_core_version": &hcldec.AttrSpec{Name: "packer_core_version", Type: cty.String, Required: false}, + "packer_debug": &hcldec.AttrSpec{Name: "packer_debug", Type: cty.Bool, Required: false}, + "packer_force": &hcldec.AttrSpec{Name: "packer_force", Type: cty.Bool, Required: false}, + "packer_on_error": &hcldec.AttrSpec{Name: "packer_on_error", Type: cty.String, Required: false}, + "packer_user_variables": &hcldec.AttrSpec{Name: "packer_user_variables", Type: cty.Map(cty.String), Required: false}, + "packer_sensitive_variables": &hcldec.AttrSpec{Name: "packer_sensitive_variables", Type: cty.List(cty.String), Required: false}, + "linode_token": &hcldec.AttrSpec{Name: "linode_token", Type: cty.String, Required: false}, + "label": &hcldec.AttrSpec{Name: "label", Type: cty.String, Required: false}, + "label_regex": &hcldec.AttrSpec{Name: "label_regex", Type: cty.String, Required: false}, + "id": &hcldec.AttrSpec{Name: "id", Type: cty.String, Required: false}, + "id_regex": &hcldec.AttrSpec{Name: "id_regex", Type: cty.String, Required: false}, + "latest": &hcldec.AttrSpec{Name: "latest", Type: cty.Bool, Required: false}, + } + return s +} + +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents of a field with a `mapstructure:,squash` tag are bubbled up. +type FlatDatasourceOutput struct { + ID *string `mapstructure:"id" cty:"id" hcl:"id"` + Capabilities []string `mapstructure:"capabilities" cty:"capabilities" hcl:"capabilities"` + Created *string `mapstructure:"created" cty:"created" hcl:"created"` + CreatedBy *string `mapstructure:"created_by" cty:"created_by" hcl:"created_by"` + Deprecated *bool `mapstructure:"deprecated" cty:"deprecated" hcl:"deprecated"` + Description *string `mapstructure:"description" cty:"description" hcl:"description"` + EOL *string `mapstructure:"eol" cty:"eol" hcl:"eol"` + Expiry *string `mapstructure:"expiry" cty:"expiry" hcl:"expiry"` + IsPublic *bool `mapstructure:"is_public" cty:"is_public" hcl:"is_public"` + Label *string `mapstructure:"label" cty:"label" hcl:"label"` + Size *int `mapstructure:"size" cty:"size" hcl:"size"` + Type *string `mapstructure:"type" cty:"type" hcl:"type"` + Updated *string `mapstructure:"updated" cty:"updated" hcl:"updated"` + Vendor *string `mapstructure:"vendor" cty:"vendor" hcl:"vendor"` +} + +// FlatMapstructure returns a new FlatDatasourceOutput. +// FlatDatasourceOutput is an auto-generated flat version of DatasourceOutput. +// Where the contents a fields with a `mapstructure:,squash` tag are bubbled up. +func (*DatasourceOutput) FlatMapstructure() interface{ HCL2Spec() map[string]hcldec.Spec } { + return new(FlatDatasourceOutput) +} + +// HCL2Spec returns the hcl spec of a DatasourceOutput. +// This spec is used by HCL to read the fields of DatasourceOutput. +// The decoded values from this spec will then be applied to a FlatDatasourceOutput. +func (*FlatDatasourceOutput) HCL2Spec() map[string]hcldec.Spec { + s := map[string]hcldec.Spec{ + "id": &hcldec.AttrSpec{Name: "id", Type: cty.String, Required: false}, + "capabilities": &hcldec.AttrSpec{Name: "capabilities", Type: cty.List(cty.String), Required: false}, + "created": &hcldec.AttrSpec{Name: "created", Type: cty.String, Required: false}, + "created_by": &hcldec.AttrSpec{Name: "created_by", Type: cty.String, Required: false}, + "deprecated": &hcldec.AttrSpec{Name: "deprecated", Type: cty.Bool, Required: false}, + "description": &hcldec.AttrSpec{Name: "description", Type: cty.String, Required: false}, + "eol": &hcldec.AttrSpec{Name: "eol", Type: cty.String, Required: false}, + "expiry": &hcldec.AttrSpec{Name: "expiry", Type: cty.String, Required: false}, + "is_public": &hcldec.AttrSpec{Name: "is_public", Type: cty.Bool, Required: false}, + "label": &hcldec.AttrSpec{Name: "label", Type: cty.String, Required: false}, + "size": &hcldec.AttrSpec{Name: "size", Type: cty.Number, Required: false}, + "type": &hcldec.AttrSpec{Name: "type", Type: cty.String, Required: false}, + "updated": &hcldec.AttrSpec{Name: "updated", Type: cty.String, Required: false}, + "vendor": &hcldec.AttrSpec{Name: "vendor", Type: cty.String, Required: false}, + } + return s +} diff --git a/datasource/image/data_acc_test.go b/datasource/image/data_acc_test.go new file mode 100644 index 00000000..14aae3b9 --- /dev/null +++ b/datasource/image/data_acc_test.go @@ -0,0 +1,37 @@ +package image + +import ( + "testing" + + "github.com/hashicorp/packer-plugin-sdk/acctest" + "github.com/linode/packer-plugin-linode/helper/acceptance" +) + +func TestImageDataSourceAcc_basic(t *testing.T) { + if skip := acceptance.TestAccPreCheck(t); skip == true { + return + } + acctest.TestPlugin(t, &acctest.PluginTestCase{ + Name: "test-linode-image-data-source-basic", + Type: "linode", + Template: testImageDataSourceAccBasic, + }) +} + +const testImageDataSourceAccBasic = ` +data "linode-image" "latest_ubuntu" { + id_regex = "linode/ubuntu.*" + latest = true +} + +source "linode" "example" { + image = data.linode-image.latest_ubuntu.id + instance_type = "g6-nanode-1" + region = "us-mia" + ssh_username = "root" +} + +build { + sources = ["source.linode.example"] +} +` diff --git a/datasource/image/data_test.go b/datasource/image/data_test.go new file mode 100644 index 00000000..950b85a4 --- /dev/null +++ b/datasource/image/data_test.go @@ -0,0 +1,51 @@ +package image + +import ( + "testing" + + "github.com/linode/packer-plugin-linode/helper" +) + +func TestImageDatasourceConfigure_MissingToken(t *testing.T) { + t.Setenv(helper.TokenEnvVar, "") + + datasource := Datasource{ + config: Config{}, + } + if err := datasource.Configure(nil); err == nil { + t.Fatalf( + "Should error if both environment variable %q "+ + "and linode_token config are unset", + helper.TokenEnvVar, + ) + } +} + +func TestImageDatasourceConfigure_EnvToken(t *testing.T) { + t.Setenv(helper.TokenEnvVar, "IAMATOKEN") + + datasource := Datasource{ + config: Config{}, + } + if err := datasource.Configure(nil); err != nil { + t.Fatalf( + "Should not error if environment variable %q is set.", + helper.TokenEnvVar, + ) + } +} + +func TestImageDatasourceConfigure_ConfigToken(t *testing.T) { + t.Setenv(helper.TokenEnvVar, "") + + datasource := Datasource{ + config: Config{ + LinodeCommon: helper.LinodeCommon{ + PersonalAccessToken: "IAMATOKEN", + }, + }, + } + if err := datasource.Configure(nil); err != nil { + t.Fatalf("Should not error if linode_token is configured.") + } +} diff --git a/datasource/image/filter.go b/datasource/image/filter.go new file mode 100644 index 00000000..823ff4d4 --- /dev/null +++ b/datasource/image/filter.go @@ -0,0 +1,77 @@ +package image + +import ( + "errors" + "regexp" + "sort" + + "github.com/linode/linodego" +) + +type ImageFilter func(linodego.Image) bool + +func filterImages(images []linodego.Image, filter ImageFilter) []linodego.Image { + result := make([]linodego.Image, 0) + + for _, image := range images { + if filter(image) { + result = append(result, image) + } + } + + return result +} + +func filterImagesByID(images []linodego.Image, id string) []linodego.Image { + idFilter := func(image linodego.Image) bool { + return image.ID == id + } + return filterImages(images, idFilter) +} + +func filterImagesByIDRegex(images []linodego.Image, idRegex string) []linodego.Image { + r := regexp.MustCompile(idRegex) + idRegexFilter := func(image linodego.Image) bool { + return r.MatchString(image.ID) + } + return filterImages(images, idRegexFilter) +} + +func filterImagesByLabelRegex(images []linodego.Image, labelRegex string) []linodego.Image { + r := regexp.MustCompile(labelRegex) + labelRegexFilter := func(image linodego.Image) bool { + return r.MatchString(image.Label) + } + return filterImages(images, labelRegexFilter) +} + +func filterImageResults(images []linodego.Image, config Config) (linodego.Image, error) { + if config.LabelRegex != "" { + images = filterImagesByLabelRegex(images, config.LabelRegex) + } + if config.ID != "" { + images = filterImagesByID(images, config.ID) + } + if config.IDRegex != "" { + images = filterImagesByIDRegex(images, config.IDRegex) + } + if len(images) > 1 { + + if config.Latest { + sort.Slice(images, func(i, j int) bool { + return images[i].Created.After(*images[j].Created) + }) + return images[0], nil + } + + return linodego.Image{}, errors.New( + "Multiple images found. Please try a more specific search, " + + "or set latest to true in the data source config block.", + ) + } + if len(images) == 0 { + return linodego.Image{}, errors.New("No image found.") + } + + return images[0], nil +} diff --git a/datasource/image/filter_test.go b/datasource/image/filter_test.go new file mode 100644 index 00000000..bb929d15 --- /dev/null +++ b/datasource/image/filter_test.go @@ -0,0 +1,80 @@ +package image + +import ( + "testing" + + "github.com/linode/linodego" +) + +func TestImageDatasourceFilter_IDExactFilter(t *testing.T) { + targetID := "test_image" + + images := []linodego.Image{ + {ID: "some_other_image"}, + {ID: targetID}, + } + + config := Config{ID: targetID} + + image, err := filterImageResults(images, config) + if err != nil { + t.Fatalf("error filtering by exact image ID: %v", err) + } + if image.ID != targetID { + t.Fatalf( + "incorrect image with ID '%q' got selected, image "+ + "with ID '%q' should be selected instead", + image.ID, targetID, + ) + } +} + +func TestImageDatasourceFilter_IDRegexFilter(t *testing.T) { + targetPartialID := "test_image" + targetIDRegex := targetPartialID + "*" + targetID := targetPartialID + "1.0" + + images := []linodego.Image{ + {ID: "some_other_image1.0"}, + {ID: targetID}, + } + + config := Config{IDRegex: targetIDRegex} + + image, err := filterImageResults(images, config) + if err != nil { + t.Fatalf("error filtering by regex image ID: %v", err) + } + if image.ID != targetID { + t.Fatalf( + "incorrect image with ID '%q' got selected, image "+ + "with ID '%q' should be selected instead", + image.ID, targetID, + ) + } +} + +func TestImageDatasourceFilter_LabelRegexFilter(t *testing.T) { + targetPartialLabel := "test_image" + targetLabelRegex := targetPartialLabel + "*" + targetLabel := targetPartialLabel + "1.0" + + images := []linodego.Image{ + {Label: "some_other_image1.0"}, + {Label: targetLabel}, + } + + config := Config{LabelRegex: targetLabelRegex} + + image, err := filterImageResults(images, config) + if err != nil { + t.Fatalf("error filtering by regex image label: %v", err) + } + if image.Label != targetLabel { + t.Fatalf( + "incorrect image with label '%q' got selected, image "+ + "with label '%q' should be selected instead", + image.Label, targetLabel, + ) + } +} diff --git a/docs/datasources/image.mdx b/docs/datasources/image.mdx new file mode 100644 index 00000000..058bd31d --- /dev/null +++ b/docs/datasources/image.mdx @@ -0,0 +1,64 @@ +--- +description: | + The Linode Image data source for Packer is for matching and filtering images on Linode. +page_title: Linode Image - Data Source +nav_title: Linode Image +--- + +# Linode Image Data Source + +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 = data.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: + +@include 'datasource/image/Config-not-required.mdx' +@include 'helper/LinodeCommon-not-required.mdx' + +## Output: + +@include 'datasource/image/DatasourceOutput.mdx' + diff --git a/helper/acceptance/utils.go b/helper/acceptance/utils.go new file mode 100644 index 00000000..2559720f --- /dev/null +++ b/helper/acceptance/utils.go @@ -0,0 +1,23 @@ +package acceptance + +import ( + "os" + "testing" + + "github.com/hashicorp/packer-plugin-sdk/acctest" + "github.com/linode/packer-plugin-linode/helper" +) + +func TestAccPreCheck(t *testing.T) bool { + if os.Getenv(acctest.TestEnvVar) == "" { + t.Skipf("Acceptance tests skipped unless env '%s' set", + acctest.TestEnvVar) + return true + } + + if v := os.Getenv(helper.TokenEnvVar); v == "" { + t.Fatalf("%q must be set for acceptance tests", helper.TokenEnvVar) + return true + } + return false +} diff --git a/helper/client.go b/helper/client.go index ad4ee7dd..429169dd 100644 --- a/helper/client.go +++ b/helper/client.go @@ -9,6 +9,8 @@ import ( "golang.org/x/oauth2" ) +const TokenEnvVar = "LINODE_TOKEN" + func NewLinodeClient(token string) linodego.Client { tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) diff --git a/helper/common.go b/helper/common.go index efb4eabf..bbc13db0 100644 --- a/helper/common.go +++ b/helper/common.go @@ -3,6 +3,7 @@ package helper // The common configuration options related to Linode services type LinodeCommon struct { - // The Linode API token. This can also be specified in LINODE_TOKEN environment variable + // The Linode API token required for provision Linode resources. + // This can also be specified in LINODE_TOKEN environment variable. PersonalAccessToken string `mapstructure:"linode_token"` } diff --git a/main.go b/main.go index ea424d76..4de489ca 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "os" "github.com/linode/packer-plugin-linode/builder/linode" + "github.com/linode/packer-plugin-linode/datasource/image" "github.com/linode/packer-plugin-linode/version" "github.com/hashicorp/packer-plugin-sdk/plugin" @@ -12,6 +13,7 @@ import ( func main() { pps := plugin.NewSet() + pps.RegisterDatasource("image", new(image.Datasource)) pps.RegisterBuilder(plugin.DEFAULT_NAME, new(linode.Builder)) pps.SetVersion(version.PluginVersion) err := pps.Run()