Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Tags; Add Firewall ID, User Data; Other MISC Updates #223

Merged
merged 3 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .web-docs/components/builder/linode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ can also be supplied to override the typical auto-generated key:

- `cloud_init` (bool) - Whether the newly created image supports cloud-init.

- `firewall_id` (int) - The ID of the Firewall to attach this Linode to upon creation.

- `metadata` ((Metadata)[#metadata]) - An object containing user-defined data relevant
to the creation of Linodes.

#### Interface

This section outlines the fields configurable for a single interface object.
Expand All @@ -123,6 +128,12 @@ VPC-specific fields:

- `nat_1_1` (string) - The public IPv4 address assigned to this Linode to be 1:1 NATed with the VPC IPv4 address.

#### Metadata

This section outlines the fields configurable for a single metadata object.

- `user_data` (string) - Base64-encoded (cloud-config)[https://www.linode.com/docs/products/compute/compute-instances/guides/metadata-cloud-config/] data.

## Examples

### Basic Example
Expand Down Expand Up @@ -200,7 +211,9 @@ source "linode" "example" {
region = "us-east"
ssh_username = "root"
private_ip = true
firewall_id = 12345

instance_tags = ["abc", "foo=bar"]
authorized_users = ["your_authorized_username"]
authorized_keys = ["ssh-rsa AAAA_valid_public_ssh_key_123456785== user@their-computer"]
stackscript_id = 1177256
Expand All @@ -220,6 +233,20 @@ source "linode" "example" {
nat_1_1 = "any"
}
}

metadata {
user_data = base64encode(<<EOF
#cloud-config

write_files:
- path: /root/helloworld.txt
content: |
Hello, world!
owner: 'root:root'
permissions: '0644'
EOF
)
}
}

build {
Expand Down
9 changes: 2 additions & 7 deletions builder/linode/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ type Builder struct {

func (b *Builder) ConfigSpec() hcldec.ObjectSpec { return b.config.FlatMapstructure().HCL2Spec() }

func (b *Builder) Prepare(raws ...interface{}) ([]string, []string, error) {
func (b *Builder) Prepare(raws ...any) ([]string, []string, error) {
warnings, errs := b.config.Prepare(raws...)
if errs != nil {
return nil, warnings, errs
Expand All @@ -42,11 +42,6 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)

client := helper.NewLinodeClient(b.config.PersonalAccessToken)

if err != nil {
ui.Error(err.Error())
return nil, err
}

state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
state.Put("hook", hook)
Expand Down Expand Up @@ -96,7 +91,7 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
ImageLabel: image.Label,
ImageID: image.ID,
Driver: &client,
StateData: map[string]interface{}{
StateData: map[string]any{
"generated_data": state.Get("generated_data"),
"source_image": b.config.Image,
"region": b.config.Region,
Expand Down
65 changes: 61 additions & 4 deletions builder/linode/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
)

func testConfig() map[string]interface{} {
return map[string]interface{}{
func testConfig() map[string]any {
return map[string]any{
"linode_token": "bar",
"region": "us-ord",
"instance_type": "g6-nanode-1",
Expand All @@ -20,7 +20,7 @@ func testConfig() map[string]interface{} {
}

func TestBuilder_ImplementsBuilder(t *testing.T) {
var raw interface{}
var raw any
raw = &Builder{}
if _, ok := raw.(packersdk.Builder); !ok {
t.Fatalf("Builder should be a builder")
Expand All @@ -29,7 +29,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) {

func TestBuilder_Prepare_BadType(t *testing.T) {
b := &Builder{}
c := map[string]interface{}{
c := map[string]any{
"linode_token": []string{},
}

Expand Down Expand Up @@ -547,3 +547,60 @@ func TestBuilderPrepare_CloudInit(t *testing.T) {
t.Errorf("found %s, expected %t", b.config.Region, expected)
}
}

func TestBuilderPrepare_MetadataTagsFirewallID(t *testing.T) {
var b Builder
config := testConfig()

// Test optional
delete(config, "firewall_id")
delete(config, "metadata")
delete(config, "instance_tags")

_, warnings, err := b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

expectedFirewallID := 123
config["firewall_id"] = expectedFirewallID

expectedUserData := "foo"
expectedMetadata := Metadata{
UserData: expectedUserData,
}
config["metadata"] = map[string]string{
"user_data": expectedUserData,
}

expectedTags := []string{
"foo",
"bar=baz",
":!@#$%^&*()_+-=[]\\{}|;'\",./<>?`~",
}
config["instance_tags"] = expectedTags

b = Builder{}
_, warnings, err = b.Prepare(config)
if len(warnings) > 0 {
t.Fatalf("bad: %#v", warnings)
}
if err != nil {
t.Fatalf("should not have error: %s", err)
}

if !reflect.DeepEqual(b.config.FirewallID, expectedFirewallID) {
t.Errorf("got %v, expected %v", b.config.FirewallID, expectedFirewallID)
}

if !reflect.DeepEqual(b.config.Metadata, expectedMetadata) {
t.Errorf("got %v, expected %v", b.config.Metadata, expectedMetadata)
}

if !reflect.DeepEqual(b.config.Tags, expectedTags) {
t.Errorf("got %v, expected %v", b.config.Tags, expectedTags)
}
}
36 changes: 21 additions & 15 deletions builder/linode/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,Interface,InterfaceIPv4
//go:generate packer-sdc mapstructure-to-hcl2 -type Config,Interface,InterfaceIPv4,Metadata

package linode

Expand All @@ -24,6 +24,10 @@ type InterfaceIPv4 struct {
NAT1To1 *string `mapstructure:"nat_1_1"`
}

type Metadata struct {
UserData string `mapstructure:"user_data"`
}

type Interface struct {
Purpose string `mapstructure:"purpose"`
Label string `mapstructure:"label"`
Expand All @@ -40,24 +44,26 @@ type Config struct {
ctx interpolate.Context
Comm communicator.Config `mapstructure:",squash"`

Interfaces []Interface `mapstructure:"interface"`
Interfaces []Interface `mapstructure:"interface" required:"false"`
lgarber-akamai marked this conversation as resolved.
Show resolved Hide resolved
Region string `mapstructure:"region"`
AuthorizedKeys []string `mapstructure:"authorized_keys"`
AuthorizedUsers []string `mapstructure:"authorized_users"`
AuthorizedKeys []string `mapstructure:"authorized_keys" required:"false"`
AuthorizedUsers []string `mapstructure:"authorized_users" required:"false"`
InstanceType string `mapstructure:"instance_type"`
Label string `mapstructure:"instance_label"`
Tags []string `mapstructure:"instance_tags"`
Label string `mapstructure:"instance_label" required:"false"`
Tags []string `mapstructure:"instance_tags" required:"false"`
Image string `mapstructure:"image"`
SwapSize int `mapstructure:"swap_size"`
PrivateIP bool `mapstructure:"private_ip"`
RootPass string `mapstructure:"root_pass"`
ImageLabel string `mapstructure:"image_label"`
Description string `mapstructure:"image_description"`
SwapSize int `mapstructure:"swap_size" required:"false"`
PrivateIP bool `mapstructure:"private_ip" required:"false"`
RootPass string `mapstructure:"root_pass" required:"false"`
ImageLabel string `mapstructure:"image_label" required:"false"`
Description string `mapstructure:"image_description" required:"false"`
StateTimeout time.Duration `mapstructure:"state_timeout" required:"false"`
StackScriptData map[string]string `mapstructure:"stackscript_data"`
StackScriptID int `mapstructure:"stackscript_id"`
StackScriptData map[string]string `mapstructure:"stackscript_data" required:"false"`
StackScriptID int `mapstructure:"stackscript_id" required:"false"`
ImageCreateTimeout time.Duration `mapstructure:"image_create_timeout" required:"false"`
CloudInit bool `mapstructure:"cloud_init" required:"false"`
Metadata Metadata `mapstructure:"metadata" required:"false"`
FirewallID int `mapstructure:"firewall_id" required:"false"`
}

func createRandomRootPassword() (string, error) {
Expand All @@ -70,7 +76,7 @@ func createRandomRootPassword() (string, error) {
return rootPass, nil
}

func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
func (c *Config) Prepare(raws ...any) ([]string, error) {
if err := config.Decode(c, &config.DecodeOpts{
Interpolate: true,
InterpolateContext: &c.ctx,
Expand Down Expand Up @@ -157,7 +163,7 @@ func (c *Config) Prepare(raws ...interface{}) ([]string, error) {
if c.Tags == nil {
c.Tags = make([]string, 0)
}
tagRe := regexp.MustCompile("^[[:alnum:]:_-]{1,255}$")
tagRe := regexp.MustCompile("^[[:print:]]{3,50}$")

for _, t := range c.Tags {
if !tagRe.MatchString(t) {
Expand Down
51 changes: 39 additions & 12 deletions builder/linode/config.hcl2spec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions builder/linode/step_create_linode.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ func flattenConfigInterface(i Interface) linodego.InstanceConfigInterfaceCreateO
}
}

func flattenMetadata(m Metadata) *linodego.InstanceMetadataOptions {
if m.UserData == "" {
return nil
}

return &linodego.InstanceMetadataOptions{
UserData: m.UserData,
}
}

func (s *stepCreateLinode) Run(ctx context.Context, state multistep.StateBag) multistep.StepAction {
c := state.Get("config").(*Config)
ui := state.Get("ui").(packersdk.Ui)
Expand Down Expand Up @@ -65,6 +75,9 @@ func (s *stepCreateLinode) Run(ctx context.Context, state multistep.StateBag) mu
Label: c.Label,
Image: c.Image,
SwapSize: &c.SwapSize,
Tags: c.Tags,
FirewallID: c.FirewallID,
Metadata: flattenMetadata(c.Metadata),
}

if pubKey := string(c.Comm.SSHPublicKey); pubKey != "" {
Expand Down
Loading