Skip to content

Commit

Permalink
Merge pull request #68 from digitalocean/asb/issues/67
Browse files Browse the repository at this point in the history
Support configuring droplet_agent (fixes #67).
  • Loading branch information
andrewsomething authored Aug 11, 2022
2 parents 4228d35 + 97488a9 commit c2c4125
Show file tree
Hide file tree
Showing 7 changed files with 410 additions and 69 deletions.
6 changes: 6 additions & 0 deletions builder/digitalocean/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ type Config struct {
// Set to true to enable monitoring for the droplet
// being created. This defaults to false, or not enabled.
Monitoring bool `mapstructure:"monitoring" required:"false"`
// A boolean indicating whether to install the DigitalOcean agent used for
// providing access to the Droplet web console in the control panel. By
// default, the agent is installed on new Droplets but installation errors
// (i.e. OS not supported) are ignored. To prevent it from being installed,
// set to false. To make installation errors fatal, explicitly set it to true.
DropletAgent *bool `mapstructure:"droplet_agent" required:"false"`
// Set to true to enable ipv6 for the droplet being
// created. This defaults to false, or not enabled.
IPv6 bool `mapstructure:"ipv6" required:"false"`
Expand Down
2 changes: 2 additions & 0 deletions builder/digitalocean/config.hcl2spec.go

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

83 changes: 46 additions & 37 deletions builder/digitalocean/step_create_droplet.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,51 @@ func (s *stepCreateDroplet) Run(ctx context.Context, state multistep.StateBag) m
ui := state.Get("ui").(packersdk.Ui)
c := state.Get("config").(*Config)

// Store the source image ID and
// other miscellaneous info for HCP Packer
state.Put("source_image_id", c.Image)
state.Put("droplet_size", c.Size)
state.Put("droplet_name", c.DropletName)
state.Put("build_region", c.Region)

// Create the droplet based on configuration
ui.Say("Creating droplet...")
dropletCreateReq, err := s.buildDropletCreateRequest(state)
if err != nil {
state.Put("error", err)
return multistep.ActionHalt
}

log.Printf("[DEBUG] Droplet create parameters: %s", godo.Stringify(dropletCreateReq))

droplet, _, err := client.Droplets.Create(context.TODO(), dropletCreateReq)
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

// We use this in cleanup
s.dropletId = droplet.ID

// Store the droplet id for later
state.Put("droplet_id", droplet.ID)
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", droplet.ID)

return multistep.ActionContinue
}

func (s *stepCreateDroplet) buildDropletCreateRequest(state multistep.StateBag) (*godo.DropletCreateRequest, error) {
c := state.Get("config").(*Config)

sshKeys := []godo.DropletCreateSSHKey{}
sshKeyId, hasSSHkey := state.GetOk("ssh_key_id")
sshKeyID, hasSSHkey := state.GetOk("ssh_key_id")
if hasSSHkey {
sshKeys = append(sshKeys, godo.DropletCreateSSHKey{
ID: sshKeyId.(int),
ID: sshKeyID.(int),
})
}
if c.SSHKeyID != 0 {
Expand All @@ -35,63 +75,32 @@ func (s *stepCreateDroplet) Run(ctx context.Context, state multistep.StateBag) m
})
}

// Create the droplet based on configuration
ui.Say("Creating droplet...")

userData := c.UserData
if c.UserDataFile != "" {
contents, err := ioutil.ReadFile(c.UserDataFile)
if err != nil {
state.Put("error", fmt.Errorf("Problem reading user data file: %s", err))
return multistep.ActionHalt
return nil, fmt.Errorf("Problem reading user data file: %s", err)
}

userData = string(contents)
}

createImage := getImageType(c.Image)

// Store the source image ID and
// other miscellaneous info for HCP Packer
state.Put("source_image_id", c.Image)
state.Put("droplet_size", c.Size)
state.Put("droplet_name", c.DropletName)
state.Put("build_region", c.Region)

dropletCreateReq := &godo.DropletCreateRequest{
return &godo.DropletCreateRequest{
Name: c.DropletName,
Region: c.Region,
Size: c.Size,
Image: createImage,
SSHKeys: sshKeys,
PrivateNetworking: c.PrivateNetworking,
Monitoring: c.Monitoring,
WithDropletAgent: c.DropletAgent,
IPv6: c.IPv6,
UserData: userData,
Tags: c.Tags,
VPCUUID: c.VPCUUID,
}

log.Printf("[DEBUG] Droplet create paramaters: %s", godo.Stringify(dropletCreateReq))

droplet, _, err := client.Droplets.Create(context.TODO(), dropletCreateReq)
if err != nil {
err := fmt.Errorf("Error creating droplet: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

// We use this in cleanup
s.dropletId = droplet.ID

// Store the droplet id for later
state.Put("droplet_id", droplet.ID)
// instance_id is the generic term used so that users can have access to the
// instance id inside of the provisioners, used in step_provision.
state.Put("instance_id", droplet.ID)

return multistep.ActionContinue
}, nil
}

func (s *stepCreateDroplet) Cleanup(state multistep.StateBag) {
Expand Down
197 changes: 197 additions & 0 deletions builder/digitalocean/step_create_droplet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"testing"

"github.com/digitalocean/godo"
"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/stretchr/testify/require"
)

func TestBuilder_GetImageType(t *testing.T) {
Expand All @@ -24,3 +26,198 @@ func TestBuilder_GetImageType(t *testing.T) {
})
}
}

func TestBuilder_buildDropletCreateRequest(t *testing.T) {
imageTypeTests := []struct {
name string
in *Config
out *godo.DropletCreateRequest
addToState map[string]interface{}
}{
{
name: "DropletAgent is false",
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
DropletAgent: godo.Bool(false),
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
WithDropletAgent: godo.Bool(false),
},
},
{
name: "DropletAgent is true",
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
DropletAgent: godo.Bool(true),
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
WithDropletAgent: godo.Bool(true),
},
},
{
name: "DropletAgent is not set",
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
},
},
{
name: "SSHKeyID set in config",
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
SSHKeyID: 12345,
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{{ID: 12345, Fingerprint: ""}},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
},
},
{
name: "SSH key set in both state and config",
addToState: map[string]interface{}{"ssh_key_id": 56789},
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
SSHKeyID: 123456,
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{
{ID: 56789, Fingerprint: ""},
{ID: 123456, Fingerprint: ""},
},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
},
},
{
name: "ssh_key_id set in state",
addToState: map[string]interface{}{"ssh_key_id": 56789},
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "ubuntu-20-04-x64",
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 0, Slug: "ubuntu-20-04-x64"},
SSHKeys: []godo.DropletCreateSSHKey{{ID: 56789, Fingerprint: ""}},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
},
},
{
name: "image as int",
in: &Config{
DropletName: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: "789",
SSHKeyID: 12345,
},
out: &godo.DropletCreateRequest{
Name: "ubuntu-20-04-x64-build",
Region: "nyc3",
Size: "s-1vcpu-1gb",
Image: godo.DropletCreateImage{ID: 789, Slug: ""},
SSHKeys: []godo.DropletCreateSSHKey{{ID: 12345, Fingerprint: ""}},
Backups: false,
IPv6: false,
PrivateNetworking: false,
Monitoring: false,
UserData: "",
VPCUUID: "",
},
},
}

for _, tt := range imageTypeTests {
t.Run(tt.name, func(t *testing.T) {
state := new(multistep.BasicStateBag)
state.Put("config", tt.in)
if tt.addToState != nil {
for k, v := range tt.addToState {
state.Put(k, v)
}
}

step := new(stepCreateDroplet)

req, err := step.buildDropletCreateRequest(state)
require.NoError(t, err)

require.Equal(t, tt.out, req)
})
}
}
6 changes: 6 additions & 0 deletions docs-partials/builder/digitalocean/Config-not-required.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
- `monitoring` (bool) - Set to true to enable monitoring for the droplet
being created. This defaults to false, or not enabled.

- `droplet_agent` (\*bool) - A boolean indicating whether to install the DigitalOcean agent used for
providing access to the Droplet web console in the control panel. By
default, the agent is installed on new Droplets but installation errors
(i.e. OS not supported) are ignored. To prevent it from being installed,
set to false. To make installation errors fatal, explicitly set it to true.

- `ipv6` (bool) - Set to true to enable ipv6 for the droplet being
created. This defaults to false, or not enabled.

Expand Down
Loading

0 comments on commit c2c4125

Please sign in to comment.