|
6 | 6 | package client |
7 | 7 |
|
8 | 8 | import ( |
| 9 | + "context" |
9 | 10 | "errors" |
10 | 11 | "fmt" |
| 12 | + "path/filepath" |
| 13 | + "strings" |
| 14 | + "time" |
| 15 | + |
| 16 | + "github.com/cenkalti/backoff/v7" |
| 17 | + "github.com/pkg/sftp" |
11 | 18 |
|
12 | 19 | "github.com/DataDog/datadog-agent/test/e2e-framework/components" |
13 | 20 | oscomp "github.com/DataDog/datadog-agent/test/e2e-framework/components/os" |
14 | 21 | ) |
15 | 22 |
|
16 | 23 | const ( |
17 | 24 | cacheBucketURL = "s3://agent-e2e-s3-bucket" |
| 25 | + |
| 26 | + // awsCliInstallRetries/awsS3CopyRetries account for a flaky AWS network on the test host. |
| 27 | + awsCliInstallRetries = 3 |
| 28 | + awsS3CopyRetries = 3 |
| 29 | + awsRetryInterval = 5 * time.Second |
| 30 | + |
| 31 | + awsCliRemoteInstallLogPath = `C:\Windows\Temp\awscli-install.log` |
| 32 | + |
| 33 | + // msiExitSuccessRebootRequired is the successful msiexec exit code returned when /norestart |
| 34 | + // defers a pending reboot. See https://learn.microsoft.com/en-us/windows/win32/msi/error-codes |
| 35 | + msiExitSuccessRebootRequired = 3010 |
18 | 36 | ) |
19 | 37 |
|
20 | 38 | type unimplementedHostCache struct{} |
@@ -65,15 +83,43 @@ func (c *windowsAWSCLI) ensureInstalled() error { |
65 | 83 | if _, err := c.sshExecutor.Execute("& \"c:\\Program Files\\Amazon\\AWSCLIV2\\aws.exe\" --version"); err == nil { |
66 | 84 | return nil |
67 | 85 | } |
68 | | - _, err := c.sshExecutor.Execute("Start-Process msiexec.exe -Wait -ArgumentList \"/i https://awscli.amazonaws.com/AWSCLIV2.msi /qn /norestart /L*V ./awscli-install.log\" ") |
| 86 | + _, err := backoff.Retry(context.Background(), func() (any, error) { |
| 87 | + // Start-Process's own exit code only reflects whether it launched msiexec, not whether the |
| 88 | + // install succeeded, so capture the process with -PassThru and check its ExitCode explicitly. |
| 89 | + _, err := c.sshExecutor.Execute(fmt.Sprintf( |
| 90 | + `$p = Start-Process msiexec.exe -Wait -PassThru -ArgumentList "/i https://awscli.amazonaws.com/AWSCLIV2.msi /qn /norestart /L*V %s"; if ($p.ExitCode -ne 0 -and $p.ExitCode -ne %d) { throw "msiexec exited with code $($p.ExitCode)" }`, |
| 91 | + awsCliRemoteInstallLogPath, msiExitSuccessRebootRequired)) |
| 92 | + return nil, err |
| 93 | + }, backoff.WithBackOff(backoff.NewConstantBackOff(awsRetryInterval)), backoff.WithMaxTries(awsCliInstallRetries)) |
| 94 | + c.collectInstallLog() |
69 | 95 | return err |
70 | 96 | } |
71 | 97 |
|
| 98 | +// collectInstallLog best-effort copies the AWS CLI msiexec log to the test's artifacts folder, |
| 99 | +// mirroring the log collection test/new-e2e/tests/windows/common/msi.go does for Agent MSI installs. |
| 100 | +func (c *windowsAWSCLI) collectInstallLog() { |
| 101 | + sftpClient, err := sftp.NewClient(c.sshExecutor.client, sftp.UseConcurrentWrites(true)) |
| 102 | + if err != nil { |
| 103 | + c.sshExecutor.context.Logf("failed to collect AWS CLI install log: %v", err) |
| 104 | + return |
| 105 | + } |
| 106 | + defer sftpClient.Close() |
| 107 | + |
| 108 | + remotePath := strings.ReplaceAll(awsCliRemoteInstallLogPath, "\\", "/") |
| 109 | + localPath := filepath.Join(c.sshExecutor.context.SessionOutputDir(), "awscli-install.log") |
| 110 | + if err := downloadFile(sftpClient, remotePath, localPath); err != nil { |
| 111 | + c.sshExecutor.context.Logf("failed to collect AWS CLI install log: %v", err) |
| 112 | + } |
| 113 | +} |
| 114 | + |
72 | 115 | func (c *windowsAWSCLI) download(path string, destPath string) error { |
73 | 116 | if err := c.ensureInstalled(); err != nil { |
74 | 117 | return err |
75 | 118 | } |
76 | | - _, err := c.sshExecutor.Execute(fmt.Sprintf("& \"c:\\Program Files\\Amazon\\AWSCLIV2\\aws.exe\" s3 cp \"%s\" \"%s\"", path, destPath)) |
| 119 | + _, err := backoff.Retry(context.Background(), func() (any, error) { |
| 120 | + _, err := c.sshExecutor.Execute(fmt.Sprintf("& \"c:\\Program Files\\Amazon\\AWSCLIV2\\aws.exe\" s3 cp \"%s\" \"%s\"", path, destPath)) |
| 121 | + return nil, err |
| 122 | + }, backoff.WithBackOff(backoff.NewConstantBackOff(awsRetryInterval)), backoff.WithMaxTries(awsS3CopyRetries)) |
77 | 123 | return err |
78 | 124 | } |
79 | 125 |
|
|
0 commit comments