Skip to content

Commit

Permalink
Fix Tags; Add Firewall ID, User Data; Other MISC Updates (#223)
Browse files Browse the repository at this point in the history
  • Loading branch information
zliang-akamai authored Apr 24, 2024
1 parent 8ed6e49 commit 43aa1d6
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 38 deletions.
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"`
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

0 comments on commit 43aa1d6

Please sign in to comment.