Skip to content

Commit

Permalink
Add support to use user data when launching instance (#33)
Browse files Browse the repository at this point in the history
Hi team! In this PR, I added support for using User-Data to
automatically provision instances. It is possible to pass it directly or
using the script/cloud-init file.
  • Loading branch information
sternik authored Jul 27, 2023
1 parent 4769f74 commit f3fba9b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 0 deletions.
4 changes: 4 additions & 0 deletions builder/exoscale/builder_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package exoscale

import (
"encoding/base64"
"math/rand"
"net"
"os"
Expand Down Expand Up @@ -34,6 +35,9 @@ var (
testTemplateID = new(testSuite).randomID()
testTemplateName = "packer-plugin-test-" + new(testSuite).randomString(6)
testTemplateZones = []string{"ch-gva-2", "ch-dk-2"}
testUserData = "echo test > /etc/test.txt"
testUserDataBase64 = base64.StdEncoding.EncodeToString([]byte(testUserData))
testUserDataFile = "userdata.txt"

testSeededRand = rand.New(rand.NewSource(time.Now().UnixNano()))
)
Expand Down
11 changes: 11 additions & 0 deletions builder/exoscale/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package exoscale

import (
"fmt"
"os"
"reflect"

"github.com/hashicorp/hcl/v2/hcldec"
Expand Down Expand Up @@ -50,6 +51,8 @@ type Config struct {
TemplateMaintainer string `mapstructure:"template_maintainer"`
TemplateVersion string `mapstructure:"template_version"`
TemplateBuild string `mapstructure:"template_build"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
// Deprecated
TemplateZone string `mapstructure:"template_zone"`

Expand Down Expand Up @@ -102,6 +105,14 @@ func NewConfig(raws ...interface{}) (*Config, []string, error) {
}
}

if config.UserData != "" && config.UserDataFile != "" {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("Only one of user_data or user_data_file can be specified."))
} else if config.UserDataFile != "" {
if _, err := os.Stat(config.UserDataFile); err != nil {
errs = packer.MultiErrorAppend(errs, fmt.Errorf("user_data_file not found: %s", config.UserDataFile))
}
}

if es := config.Comm.Prepare(&config.ctx); len(es) > 0 {
errs = packer.MultiErrorAppend(errs, es...)
}
Expand Down
37 changes: 37 additions & 0 deletions builder/exoscale/config_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package exoscale

import "os"

var (
testConfigAPIKey = "EXOabcdef0123456789abcdef01"
testConfigAPISecret = "ABCDEFGHIJKLMNOPRQSTUVWXYZ0123456789abcdefg"
Expand All @@ -9,6 +11,8 @@ var (
testConfigTemplateZones = []string{"ch-gva-2", "ch-dk-2"}
testConfigTemplateName = "test-packer"
testConfigSSHUsername = "ubuntu"
testConfigUserData = "sed -i -E 's/#?PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config"
testConfigUserDataFile = "disable_ssh_password_auth.sh"
// Deprecated
testConfigTemplateZone = "ch-dk-2"
)
Expand All @@ -27,6 +31,7 @@ func (ts *testSuite) TestNewConfig() {
"template_name": testConfigTemplateName,
"template_zones": testConfigTemplateZones,
"ssh_username": testConfigSSHUsername,
"user_data": testConfigUserData,
}}...)
ts.Require().NoError(err)
ts.Require().NotNil(config)
Expand All @@ -39,6 +44,38 @@ func (ts *testSuite) TestNewConfig() {
ts.Require().Equal(testConfigSnapshotDownloadPath, config.SnapshotDownloadPath)
ts.Require().Equal(testConfigTemplateZones[0], config.InstanceZone)
ts.Require().Equal(defaultTemplateBootMode, config.TemplateBootMode)
ts.Require().Equal(testConfigUserData, config.UserData)

_, _, err = NewConfig([]interface{}{map[string]interface{}{
// Minimal configuration
"api_key": testConfigAPIKey,
"api_secret": testConfigAPISecret,
"instance_template": testConfigInstanceTemplate,
"template_name": testConfigTemplateName,
"template_zones": testConfigTemplateZones,
"ssh_username": testConfigSSHUsername,
"user_data": testConfigUserData,
"user_data_file": testConfigUserDataFile,
}}...)
ts.Require().ErrorContains(err, "Only one of user_data or user_data_file can be specified.")

tmpFile, err := os.CreateTemp(os.TempDir(), testConfigUserDataFile)
ts.Require().NoError(err, "unable to create temporary file")
ts.Require().NoError(tmpFile.Close())
ts.Require().FileExists(tmpFile.Name())

config, _, err = NewConfig([]interface{}{map[string]interface{}{
// Minimal configuration
"api_key": testConfigAPIKey,
"api_secret": testConfigAPISecret,
"instance_template": testConfigInstanceTemplate,
"template_name": testConfigTemplateName,
"template_zones": testConfigTemplateZones,
"ssh_username": testConfigSSHUsername,
"user_data_file": tmpFile.Name(),
}}...)
ts.Require().NoError(err)
ts.Require().Equal(tmpFile.Name(), config.UserDataFile)
}

func (ts *testSuite) TestNewConfigDeprecated() {
Expand Down
22 changes: 22 additions & 0 deletions builder/exoscale/step_create_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package exoscale

import (
"context"
"encoding/base64"
"fmt"
"os"

egoscale "github.com/exoscale/egoscale/v2"
exoapi "github.com/exoscale/egoscale/v2/api"
Expand Down Expand Up @@ -104,6 +106,26 @@ func (s *stepCreateInstance) Run(ctx context.Context, state multistep.StateBag)
instance.SecurityGroupIDs = &securityGroupIDs
}

userData := s.builder.config.UserData
if s.builder.config.UserDataFile != "" {
contents, err := os.ReadFile(s.builder.config.UserDataFile)
if err != nil {
ui.Error(fmt.Sprintf("Unable to read user data file: %v", err))
return multistep.ActionHalt
}

userData = string(contents)
}

if userData != "" {
// Test if it is encoded already, and if not, encode it
if _, err := base64.StdEncoding.DecodeString(userData); err != nil {
userData = base64.StdEncoding.EncodeToString([]byte(userData))
}

instance.UserData = &userData
}

instance, err = s.builder.exo.CreateInstance(ctx, s.builder.config.InstanceZone, instance)
if err != nil {
ui.Error(fmt.Sprintf("Unable to create compute instance: %v", err))
Expand Down
10 changes: 10 additions & 0 deletions builder/exoscale/step_create_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package exoscale

import (
"context"
"os"

"github.com/hashicorp/packer-plugin-sdk/multistep"
"github.com/hashicorp/packer-plugin-sdk/packer"
Expand All @@ -28,6 +29,14 @@ func (ts *testSuite) TestStepCreateInstance_Run() {
instancePrivateNetworkAttached bool
)

tmpUserDataFile, err := os.CreateTemp(os.TempDir(), testUserDataFile)
ts.Require().NoError(err, "unable to create temporary userdata file")
ts.Require().FileExists(tmpUserDataFile.Name())
_, err = tmpUserDataFile.WriteString(testUserData)
ts.Require().NoError(err)
ts.Require().NoError(tmpUserDataFile.Close())
testConfig.UserDataFile = tmpUserDataFile.Name()

testInstance := &egoscale.Instance{
ID: &testInstanceID,
Name: &testInstanceName,
Expand Down Expand Up @@ -99,6 +108,7 @@ func (ts *testSuite) TestStepCreateInstance_Run() {
SSHKey: &testConfig.InstanceSSHKey,
SecurityGroupIDs: &[]string{testInstanceSecurityGroupID},
TemplateID: &testTemplateID,
UserData: &testUserDataBase64,
},
args.Get(2))
instanceCreated = true
Expand Down
8 changes: 8 additions & 0 deletions docs/builders/exoscale.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ other OS, we recommend using the [QEMU][packerqemu] plugin combined with the
- `template_disable_sshkey` (boolean) - Whether the template should disable
SSH key installation during Compute instance creation. Defaults to `false`.

- `user_data` (string) - User data to apply when launching the instance. Note
that you need to be careful about escaping characters due to the templates
being JSON. See [documentation][cloudinit] to learn more about user data.

- `user_data_file` (string) - The path to a file that will be used for the user
data when launching the instance.

In addition to plugin-specific configuration parameters, you can also adjust
the [SSH communicator][packerssh] settings to configure how Packer will log
into the Compute instance.
Expand Down Expand Up @@ -119,3 +126,4 @@ build {
[packerssh]: https://www.packer.io/docs/communicators/ssh/
[zones]: https://www.exoscale.com/datacenters/
[packerqemu]: https://www.packer.io/plugins/builders/qemu
[cloudinit]: https://community.exoscale.com/documentation/compute/cloud-init/

0 comments on commit f3fba9b

Please sign in to comment.