diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d1cd44b..fd44c4bea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ ### BUG FIXES * builder/amazon: Fix single `tag` interpolation to allow for templating engine usage. [GH-10224] +* builder/yandex: Fixed using cloud config when using IPv6 [GH-10297] * post-processort/yandex-export: added check of service account id ## 1.6.5 (October 30, 2020) diff --git a/builder/yandex/cloud_init.go b/builder/yandex/cloud_init.go new file mode 100644 index 000000000..f1e63b6ea --- /dev/null +++ b/builder/yandex/cloud_init.go @@ -0,0 +1,61 @@ +package yandex + +import ( + "bytes" + "fmt" + "mime/multipart" + "net/textproto" + "strings" +) + +const ( + defaultContentType = "text/cloud-config" + shellContentType = "text/x-shellscript" +) + +const ( + cloudInitIPv6Config = `#cloud-config +bootcmd: +- [ sh, -c, '/usr/bin/env dhclient -6 -D LL -nw -pf /run/dhclient_ipv6.eth0.pid -lf /var/lib/dhcp/dhclient_ipv6.eth0.leases eth0' ] +` +) + +// MergeCloudUserMetaData allow merge some user-data sections +func MergeCloudUserMetaData(usersData ...string) (string, error) { + buff := new(bytes.Buffer) + data := multipart.NewWriter(buff) + _, err := buff.WriteString(fmt.Sprintf("Content-Type: multipart/mixed; boundary=\"%s\"\r\n", data.Boundary())) + if err != nil { + return "", err + } + _, err = buff.WriteString("MIME-Version: 1.0\r\n\r\n") + if err != nil { + return "", err + } + + for i, userData := range usersData { + w, err := data.CreatePart(textproto.MIMEHeader{ + "Content-Disposition": {fmt.Sprintf("attachment; filename=\"user-data-%d\"", i)}, + "Content-Type": {detectContentType(userData)}, + }) + if err != nil { + return "", err + } + _, err = w.Write([]byte(userData)) + if err != nil { + return "", err + } + } + return buff.String(), nil +} + +func detectContentType(content string) string { + switch { + case strings.HasPrefix(content, "#!"): + return shellContentType + case strings.HasPrefix(content, "#cloud-config"): + return defaultContentType + } + + return defaultContentType +} diff --git a/builder/yandex/cloud_init_test.go b/builder/yandex/cloud_init_test.go new file mode 100644 index 000000000..aa2d5fcb3 --- /dev/null +++ b/builder/yandex/cloud_init_test.go @@ -0,0 +1,43 @@ +package yandex + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + data1 = ` +#cloud-config +bootcmd: + - cmd1 + - cmd2 +` + data2 = ` +#cloud-config +runcmd: + - touch "cmd3" + - cmd4 +` + data3 = `#!/bin/bash +touch /test` +) + +func TestCloudInitMerge(t *testing.T) { + merged, err := MergeCloudUserMetaData( + data1, + data2, + data3, + ) + + require.NoError(t, err) + require.NotEmpty(t, merged) + + require.Contains(t, merged, "cmd1") + require.Contains(t, merged, "cmd2") + require.Contains(t, merged, "\"cmd3\"") + require.Contains(t, merged, "cmd4") + + require.Contains(t, merged, "text/cloud-config") + require.Contains(t, merged, "text/x-shellscript") +} diff --git a/builder/yandex/step_create_instance.go b/builder/yandex/step_create_instance.go index 757b24c2a..0e6a99e89 100644 --- a/builder/yandex/step_create_instance.go +++ b/builder/yandex/step_create_instance.go @@ -205,14 +205,17 @@ func (s *StepCreateInstance) Run(ctx context.Context, state multistep.StateBag) return stepHaltWithError(state, fmt.Errorf("Error preparing instance metadata: %s", err)) } - // TODO make part metadata prepare process if config.UseIPv6 { - // this ugly hack will replace user provided 'user-data' - userData := `#cloud-config -runcmd: -- [ sh, -c, '/sbin/dhclient -6 -D LL -nw -pf /run/dhclient_ipv6.eth0.pid -lf /var/lib/dhcp/dhclient_ipv6.eth0.leases eth0' ] -` - instanceMetadata["user-data"] = userData + ui.Say("Prepare user-data...") + + oldUserData, ok := instanceMetadata["user-data"] + if !ok { + oldUserData = "" + } + instanceMetadata["user-data"], err = MergeCloudUserMetaData(oldUserData, cloudInitIPv6Config) + if err != nil { + return stepHaltWithError(state, fmt.Errorf("Error merge user data configs: %s", err)) + } } req := &compute.CreateInstanceRequest{