diff --git a/end-to-end/cloud_init_test.go b/end-to-end/cloud_init_test.go new file mode 100644 index 000000000..5862beecc --- /dev/null +++ b/end-to-end/cloud_init_test.go @@ -0,0 +1,116 @@ +package endtoend_test + +import ( + "context" + "os" + "path/filepath" + "strings" + "testing" + "time" + + landscapeapi "github.com/canonical/landscape-hostagent-api" + "github.com/canonical/ubuntu-pro-for-wsl/common/golden" + "github.com/stretchr/testify/require" + wsl "github.com/ubuntu/gowsl" +) + +func TestCloudInitIntegration(t *testing.T) { + currentFuncName := t.Name() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + testSetup(t) + defer logWindowsAgentOnError(t) + + landscape := NewLandscape(t, ctx) + writeUbuntuProRegistry(t, "LandscapeConfig", landscape.ClientConfig) + + go landscape.Serve() + defer landscape.LogOnError(t) + defer landscape.Stop() + + hostname, err := os.Hostname() + require.NoError(t, err, "Setup: could not test machine's hostname") + + proToken := os.Getenv(proTokenEnv) + require.NotEmptyf(t, proToken, "Setup: environment variable %q should contain a valid pro token, but is empty", proTokenEnv) + writeUbuntuProRegistry(t, "UbuntuProToken", proToken) + + cleanup := startAgent(t, ctx, currentFuncName) + defer cleanup() + + info := landscape.RequireReceivedInfo(t, proToken, nil, hostname) + + out, err := os.ReadFile(filepath.Join(golden.TestFixturePath(t), "user-data.yaml")) + require.NoError(t, err, "Setup: could not read cloud-init file") + cloudInitUserData := string(out) + + err = landscape.service.SendCommand(ctx, info.UID, &landscapeapi.Command{ + Cmd: &landscapeapi.Command_Install_{ + Install: &landscapeapi.Command_Install{ + Id: strings.Split(referenceDistroAppx, ".")[1], // CanonicalGroupLimited.[UbuntuPreview] + Cloudinit: &cloudInitUserData, + }, + }, + }) + require.NoError(t, err, "Setup: could not send install command") + + distro := wsl.NewDistro(ctx, referenceDistro) + + //nolint:errcheck // Nothing we can do about it + defer distro.Unregister() + + require.Eventually(t, func() bool { + ok, err := distro.IsRegistered() + if err != nil { + t.Logf("could not determine if distro is registered: %v", err) + return false + } + return ok + }, time.Minute, time.Second, "Distro should have been registered") + + defer logWslProServiceOnError(t, ctx, distro) + + runCommand(t, ctx, time.Minute, distro, "cloud-init status --wait") + + require.Eventually(t, func() bool { + attached, err := distroIsProAttached(t, ctx, distro) + if err != nil { + t.Logf("could not determine if distro is attached: %v", err) + return false + } + return attached + }, 10*time.Second, time.Second, "distro should have been Pro attached") + + userName := runCommand(t, ctx, 10*time.Second, distro, "whoami") + require.Equal(t, "testuser", userName, "cloud-init should have configured the default user") + + landscape.RequireReceivedInfo(t, proToken, []wsl.Distro{distro}, hostname) + landscape.RequireUninstallCommand(t, ctx, distro, info) +} + +//nolint:revive // t always goes before ctx +func runCommand(t *testing.T, ctx context.Context, timeout time.Duration, distro wsl.Distro, comand string) string { + t.Helper() + + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + out, err := distro.Command(ctx, comand).CombinedOutput() + if err == nil { + return string(out) + } + + // We check the context to see if it was a timeout. + // This makes the error message easier to understand. + + select { + case <-ctx.Done(): + require.Fail(t, "Timed out waiting for cloud-init to finish") + default: + } + + require.NoError(t, err, "could not determine if cloud-init is done: %s. Output: %s", out, out) + return "" +} diff --git a/end-to-end/landscape_utils_test.go b/end-to-end/landscape_utils_test.go index 0331bdcec..759357348 100644 --- a/end-to-end/landscape_utils_test.go +++ b/end-to-end/landscape_utils_test.go @@ -109,7 +109,7 @@ func (l landscape) Stop() { } // RequireReceivedInfo checks that a connection to Landscape was made and the proper information was sent. -func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistro gowsl.Distro, wantHostname string) landscapemockservice.HostInfo { +func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistros []gowsl.Distro, wantHostname string) landscapemockservice.HostInfo { t.Helper() require.Eventually(t, func() bool { @@ -122,9 +122,12 @@ func (l landscape) RequireReceivedInfo(t *testing.T, wantToken string, wantDistr // Validate token require.Equal(t, wantToken, info.Token, "Landscape did not receive the right pro token") - // Validate distro - require.Len(t, info.Instances, 1, "Landscape did not receive the right number of distros") - require.Equal(t, wantDistro.Name(), info.Instances[0].ID, "Landscape did not receive the right distro name from the agent") + // Validate distros + gotDistros := make([]string, 0) + for _, instance := range info.Instances { + gotDistros = append(gotDistros, instance.ID) + } + require.ElementsMatch(t, wantDistros, gotDistros, "Landscape did not receive the right distros") // Validate hostname require.Equal(t, wantHostname, info.Hostname, "Landscape did not receive the right hostname from the agent") diff --git a/end-to-end/manual_token_input_test.go b/end-to-end/manual_token_input_test.go index 48a3b29f3..73b0c0b96 100644 --- a/end-to-end/manual_token_input_test.go +++ b/end-to-end/manual_token_input_test.go @@ -63,7 +63,7 @@ func TestManualTokenInput(t *testing.T) { defer logWslProServiceOnError(t, ctx, d) - out, err := d.Command(ctx, "exit 0").CombinedOutput() + out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput() require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) // ... or after registration, but never both. @@ -94,7 +94,7 @@ func TestManualTokenInput(t *testing.T) { return attached }, maxTimeout, time.Second, "distro should have been Pro attached") - info := landscape.RequireReceivedInfo(t, os.Getenv(proTokenEnv), d, hostname) + info := landscape.RequireReceivedInfo(t, os.Getenv(proTokenEnv), []wsl.Distro{d}, hostname) landscape.RequireUninstallCommand(t, ctx, d, info) }) } diff --git a/end-to-end/organization_token_test.go b/end-to-end/organization_token_test.go index fc0f26022..d41504d47 100644 --- a/end-to-end/organization_token_test.go +++ b/end-to-end/organization_token_test.go @@ -65,7 +65,7 @@ func TestOrganizationProvidedToken(t *testing.T) { defer logWslProServiceOnError(t, ctx, d) - out, err := d.Command(ctx, "exit 0").CombinedOutput() + out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput() require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) if tc.whenToken == afterDistroRegistration { @@ -99,7 +99,7 @@ func TestOrganizationProvidedToken(t *testing.T) { return attached }, maxTimeout, time.Second, "distro should have been Pro attached") - info := landscape.RequireReceivedInfo(t, proToken, d, hostname) + info := landscape.RequireReceivedInfo(t, proToken, []wsl.Distro{d}, hostname) landscape.RequireUninstallCommand(t, ctx, d, info) }) } diff --git a/end-to-end/purchase_test.go b/end-to-end/purchase_test.go index b6d599750..3a2edf8aa 100644 --- a/end-to-end/purchase_test.go +++ b/end-to-end/purchase_test.go @@ -121,7 +121,7 @@ func TestPurchase(t *testing.T) { defer logWslProServiceOnError(t, ctx, d) - out, err := d.Command(ctx, "exit 0").CombinedOutput() + out, err := d.Command(ctx, "cloud-init status --wait").CombinedOutput() require.NoErrorf(t, err, "Setup: could not wake distro up: %v. %s", err, out) // ... or after registration, but never both. @@ -153,7 +153,7 @@ func TestPurchase(t *testing.T) { return attached }, maxTimeout, time.Second, "distro should have been Pro attached") - landscape.RequireReceivedInfo(t, token, d, hostname) + landscape.RequireReceivedInfo(t, token, []wsl.Distro{d}, hostname) // Skipping because we know it to be broken // See https://warthogs.atlassian.net/browse/UDENG-1810 // diff --git a/end-to-end/testdata/TestCloudInitIntegration/user-data.yaml b/end-to-end/testdata/TestCloudInitIntegration/user-data.yaml new file mode 100644 index 000000000..2857b3486 --- /dev/null +++ b/end-to-end/testdata/TestCloudInitIntegration/user-data.yaml @@ -0,0 +1,13 @@ +#cloud-config +users: + - name: testuser + gecos: Test User + sudo: ALL=(ALL) NOPASSWD:ALL + groups: [adm,dialout,cdrom,floppy,sudo,audio,dip,video,plugdev,users,netdev] + shell: /bin/bash +write_files: +- path: /etc/wsl.conf + append: true + content: | + [user] + default=testuser diff --git a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart index 8615bd1d7..3a509ddc7 100644 --- a/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart +++ b/gui/packages/ubuntupro/end_to_end/end_to_end_test.dart @@ -25,6 +25,7 @@ void main(List args) { const testCases = { 'TestOrganizationProvidedToken': testOrganizationProvidedToken, + 'TestCloudInitIntegration': testOrganizationProvidedToken, 'TestManualTokenInput': testManualTokenInput, 'TestPurchase': testPurchase, };