Skip to content

Commit 940f458

Browse files
committed
Merge remote-tracking branch 'origin/main' into dwahler/provisionerd
2 parents 6100206 + 623fc5b commit 940f458

File tree

170 files changed

+4211
-1722
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

170 files changed

+4211
-1722
lines changed

.github/workflows/coder.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,14 @@ jobs:
661661
DEBUG: pw:api
662662
working-directory: site
663663

664+
- name: Upload Playwright Failed Tests
665+
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
666+
uses: actions/upload-artifact@v3
667+
with:
668+
name: failed-test-videos
669+
path: ./site/test-results/**/*.webm
670+
retention:days: 7
671+
664672
- name: Upload DataDog Trace
665673
if: always() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
666674
env:

.github/workflows/stale.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,5 @@ jobs:
3131
isn't more activity.
3232
# Upped from 30 since we have a big tracker and was hitting the limit.
3333
operations-per-run: 60
34-
close-issue-reason: not_planned
3534
# Start with the oldest issues, always.
3635
ascending: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ dist/
3434
site/out/
3535

3636
*.tfstate
37+
*.tfstate.backup
3738
*.tfplan
3839
*.lock.hcl
3940
.terraform/

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ build: site/out/index.html $(shell find . -not -path './vendor/*' -type f -name
5656
.PHONY: build
5757

5858
# Runs migrations to output a dump of the database.
59-
coderd/database/dump.sql: $(wildcard coderd/database/migrations/*.sql)
59+
coderd/database/dump.sql: coderd/database/dump/main.go $(wildcard coderd/database/migrations/*.sql)
6060
go run coderd/database/dump/main.go
6161

6262
# Generates Go code for querying the database.

agent/agent.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,12 +394,25 @@ func (a *agent) createCommand(ctx context.Context, rawCommand string, env []stri
394394
if err != nil {
395395
return nil, xerrors.Errorf("getting os executable: %w", err)
396396
}
397+
// Set environment variables reliable detection of being inside a
398+
// Coder workspace.
399+
cmd.Env = append(cmd.Env, "CODER=true")
400+
397401
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", username))
398402
// Git on Windows resolves with UNIX-style paths.
399403
// If using backslashes, it's unable to find the executable.
400404
unixExecutablePath := strings.ReplaceAll(executablePath, "\\", "/")
401405
cmd.Env = append(cmd.Env, fmt.Sprintf(`GIT_SSH_COMMAND=%s gitssh --`, unixExecutablePath))
402406

407+
// Set SSH connection environment variables (these are also set by OpenSSH
408+
// and thus expected to be present by SSH clients). Since the agent does
409+
// networking in-memory, trying to provide accurate values here would be
410+
// nonsensical. For now, we hard code these values so that they're present.
411+
srcAddr, srcPort := "0.0.0.0", "0"
412+
dstAddr, dstPort := "0.0.0.0", "0"
413+
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CLIENT=%s %s %s", srcAddr, srcPort, dstPort))
414+
cmd.Env = append(cmd.Env, fmt.Sprintf("SSH_CONNECTION=%s %s %s %s", srcAddr, srcPort, dstAddr, dstPort))
415+
403416
// Load environment variables passed via the agent.
404417
// These should override all variables we manually specify.
405418
for envKey, value := range metadata.EnvironmentVariables {
@@ -437,6 +450,8 @@ func (a *agent) handleSSHSession(session ssh.Session) (retErr error) {
437450
sshPty, windowSize, isPty := session.Pty()
438451
if isPty {
439452
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", sshPty.Term))
453+
454+
// The pty package sets `SSH_TTY` on supported platforms.
440455
ptty, process, err := pty.Start(cmd)
441456
if err != nil {
442457
return xerrors.Errorf("start command: %w", err)
@@ -797,7 +812,9 @@ func (r *reconnectingPTY) Close() {
797812
_ = conn.Close()
798813
}
799814
_ = r.ptty.Close()
815+
r.circularBufferMutex.Lock()
800816
r.circularBuffer.Reset()
817+
r.circularBufferMutex.Unlock()
801818
r.timeout.Stop()
802819
}
803820

agent/agent_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,49 @@ func TestAgent(t *testing.T) {
232232
require.Equal(t, expect, strings.TrimSpace(string(output)))
233233
})
234234

235+
t.Run("Coder env vars", func(t *testing.T) {
236+
t.Parallel()
237+
238+
for _, key := range []string{"CODER"} {
239+
key := key
240+
t.Run(key, func(t *testing.T) {
241+
t.Parallel()
242+
243+
session := setupSSHSession(t, agent.Metadata{})
244+
command := "sh -c 'echo $" + key + "'"
245+
if runtime.GOOS == "windows" {
246+
command = "cmd.exe /c echo %" + key + "%"
247+
}
248+
output, err := session.Output(command)
249+
require.NoError(t, err)
250+
require.NotEmpty(t, strings.TrimSpace(string(output)))
251+
})
252+
}
253+
})
254+
255+
t.Run("SSH connection env vars", func(t *testing.T) {
256+
t.Parallel()
257+
258+
// Note: the SSH_TTY environment variable should only be set for TTYs.
259+
// For some reason this test produces a TTY locally and a non-TTY in CI
260+
// so we don't test for the absence of SSH_TTY.
261+
for _, key := range []string{"SSH_CONNECTION", "SSH_CLIENT"} {
262+
key := key
263+
t.Run(key, func(t *testing.T) {
264+
t.Parallel()
265+
266+
session := setupSSHSession(t, agent.Metadata{})
267+
command := "sh -c 'echo $" + key + "'"
268+
if runtime.GOOS == "windows" {
269+
command = "cmd.exe /c echo %" + key + "%"
270+
}
271+
output, err := session.Output(command)
272+
require.NoError(t, err)
273+
require.NotEmpty(t, strings.TrimSpace(string(output)))
274+
})
275+
}
276+
})
277+
235278
t.Run("StartupScript", func(t *testing.T) {
236279
t.Parallel()
237280
tempPath := filepath.Join(t.TempDir(), "content.txt")

cli/clitest/clitest.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ import (
2121
// New creates a CLI instance with a configuration pointed to a
2222
// temporary testing directory.
2323
func New(t *testing.T, args ...string) (*cobra.Command, config.Root) {
24-
cmd := cli.Root(cli.AGPL())
24+
return NewWithSubcommands(t, cli.AGPL(), args...)
25+
}
26+
27+
func NewWithSubcommands(
28+
t *testing.T, subcommands []*cobra.Command, args ...string,
29+
) (*cobra.Command, config.Root) {
30+
cmd := cli.Root(subcommands)
2531
dir := t.TempDir()
2632
root := config.Root(dir)
2733
cmd.SetArgs(append([]string{"--global-config", dir}, args...))

cli/cliui/agent.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
7979
defer resourceMutex.Unlock()
8080
message := "Don't panic, your workspace is booting up!"
8181
if agent.Status == codersdk.WorkspaceAgentDisconnected {
82-
message = "The workspace agent lost connection! Wait for it to reconnect or run: " + Styles.Code.Render("coder rebuild "+opts.WorkspaceName)
82+
message = "The workspace agent lost connection! Wait for it to reconnect or restart your workspace."
8383
}
8484
// This saves the cursor position, then defers clearing from the cursor
8585
// position to the end of the screen.

cli/configssh.go

Lines changed: 41 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ func configSSH() *cobra.Command {
137137
sshConfigFile string
138138
sshConfigOpts sshConfigOptions
139139
usePreviousOpts bool
140-
coderConfigFile string
141140
dryRun bool
142141
skipProxyCommand bool
143142
wireguard bool
@@ -158,7 +157,7 @@ func configSSH() *cobra.Command {
158157
),
159158
Args: cobra.ExactArgs(0),
160159
RunE: func(cmd *cobra.Command, _ []string) error {
161-
client, err := createClient(cmd)
160+
client, err := CreateClient(cmd)
162161
if err != nil {
163162
return err
164163
}
@@ -198,15 +197,7 @@ func configSSH() *cobra.Command {
198197
// Parse the previous configuration only if config-ssh
199198
// has been run previously.
200199
var lastConfig *sshConfigOptions
201-
var ok bool
202-
var coderConfigRaw []byte
203-
if coderConfigFile, coderConfigRaw, ok = readDeprecatedCoderConfigFile(homedir, coderConfigFile); ok {
204-
// Deprecated: Remove after migration period.
205-
changes = append(changes, fmt.Sprintf("Remove old auto-generated coder config file at %s", coderConfigFile))
206-
// Backwards compate, restore old options.
207-
c := sshConfigParseLastOptions(bytes.NewReader(coderConfigRaw))
208-
lastConfig = &c
209-
} else if section, ok := sshConfigGetCoderSection(configRaw); ok {
200+
if section, ok := sshConfigGetCoderSection(configRaw); ok {
210201
c := sshConfigParseLastOptions(bytes.NewReader(section))
211202
lastConfig = &c
212203
}
@@ -237,6 +228,8 @@ func configSSH() *cobra.Command {
237228
}
238229
// Selecting "no" will use the last config.
239230
sshConfigOpts = *lastConfig
231+
} else {
232+
changes = append(changes, "Use new SSH options")
240233
}
241234
// Only print when prompts are shown.
242235
if yes, _ := cmd.Flags().GetBool("yes"); !yes {
@@ -245,14 +238,6 @@ func configSSH() *cobra.Command {
245238
}
246239

247240
configModified := configRaw
248-
249-
// Check for the presence of the coder Include
250-
// statement is present and add if missing.
251-
// Deprecated: Remove after migration period.
252-
if configModified, ok = removeDeprecatedSSHIncludeStatement(configModified); ok {
253-
changes = append(changes, fmt.Sprintf("Remove %q from %s", "Include coder", sshConfigFile))
254-
}
255-
256241
root := createConfig(cmd)
257242

258243
buf := &bytes.Buffer{}
@@ -295,10 +280,16 @@ func configSSH() *cobra.Command {
295280
"\tLogLevel ERROR",
296281
)
297282
if !skipProxyCommand {
283+
// In SSH configs, strings inside "" are interpreted literally and there
284+
// is no need to e.g. escape backslashes (common on Windows platforms).
285+
// We will escape the quotes, though.
286+
escapedBinaryFile := strings.ReplaceAll(binaryFile, "\"", "\\\"")
298287
if !wireguard {
299-
configOptions = append(configOptions, fmt.Sprintf("\tProxyCommand %q --global-config %q ssh --stdio %s", binaryFile, root, hostname))
288+
//nolint:gocritic // We don't want to use %q here, see above.
289+
configOptions = append(configOptions, fmt.Sprintf("\tProxyCommand \"%s\" --global-config \"%s\" ssh --stdio %s", escapedBinaryFile, root, hostname))
300290
} else {
301-
configOptions = append(configOptions, fmt.Sprintf("\tProxyCommand %q --global-config %q ssh --wireguard --stdio %s", binaryFile, root, hostname))
291+
//nolint:gocritic // We don't want to use %q here, see above.
292+
configOptions = append(configOptions, fmt.Sprintf("\tProxyCommand \"%s\" --global-config \"%s\" ssh --wireguard --stdio %s", escapedBinaryFile, root, hostname))
302293
}
303294
}
304295

@@ -313,17 +304,34 @@ func configSSH() *cobra.Command {
313304
_, _ = buf.Write(after)
314305

315306
if !bytes.Equal(configModified, buf.Bytes()) {
316-
changes = append(changes, fmt.Sprintf("Update coder config section in %s", sshConfigFile))
307+
changes = append(changes, fmt.Sprintf("Update the coder section in %s", sshConfigFile))
317308
configModified = buf.Bytes()
318309
}
319310

320-
if len(changes) > 0 {
321-
dryRunDisclaimer := ""
322-
if dryRun {
323-
dryRunDisclaimer = " (dry-run, no changes will be made)"
311+
if len(changes) == 0 {
312+
_, _ = fmt.Fprintf(out, "No changes to make.\n")
313+
return nil
314+
}
315+
316+
if dryRun {
317+
_, _ = fmt.Fprintf(out, "Dry run, the following changes would be made to your SSH configuration:\n\n * %s\n\n", strings.Join(changes, "\n * "))
318+
319+
color := isTTYOut(cmd)
320+
diff, err := diffBytes(sshConfigFile, configRaw, configModified, color)
321+
if err != nil {
322+
return xerrors.Errorf("diff failed: %w", err)
323+
}
324+
if len(diff) > 0 {
325+
// Write diff to stdout.
326+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s", diff)
324327
}
328+
329+
return nil
330+
}
331+
332+
if len(changes) > 0 {
325333
_, err = cliui.Prompt(cmd, cliui.PromptOptions{
326-
Text: fmt.Sprintf("The following changes will be made to your SSH configuration:\n\n * %s\n\n Continue?%s", strings.Join(changes, "\n * "), dryRunDisclaimer),
334+
Text: fmt.Sprintf("The following changes will be made to your SSH configuration:\n\n * %s\n\n Continue?", strings.Join(changes, "\n * ")),
327335
IsConfirm: true,
328336
})
329337
if err != nil {
@@ -335,47 +343,18 @@ func configSSH() *cobra.Command {
335343
}
336344
}
337345

338-
if dryRun {
339-
color := isTTYOut(cmd)
340-
diffFns := []func() ([]byte, error){
341-
func() ([]byte, error) { return diffBytes(sshConfigFile, configRaw, configModified, color) },
342-
}
343-
if len(coderConfigRaw) > 0 {
344-
// Deprecated: Remove after migration period.
345-
diffFns = append(diffFns, func() ([]byte, error) { return diffBytes(coderConfigFile, coderConfigRaw, nil, color) })
346-
}
347-
348-
for _, diffFn := range diffFns {
349-
diff, err := diffFn()
350-
if err != nil {
351-
return xerrors.Errorf("diff failed: %w", err)
352-
}
353-
if len(diff) > 0 {
354-
// Write diff to stdout.
355-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n%s", diff)
356-
}
357-
}
358-
} else {
359-
if !bytes.Equal(configRaw, configModified) {
360-
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
361-
if err != nil {
362-
return xerrors.Errorf("write ssh config failed: %w", err)
363-
}
364-
}
365-
// Deprecated: Remove after migration period.
366-
if len(coderConfigRaw) > 0 {
367-
err = os.Remove(coderConfigFile)
368-
if err != nil {
369-
return xerrors.Errorf("remove coder config failed: %w", err)
370-
}
346+
if !bytes.Equal(configRaw, configModified) {
347+
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
348+
if err != nil {
349+
return xerrors.Errorf("write ssh config failed: %w", err)
371350
}
372351
}
373352

374353
if len(workspaceConfigs) > 0 {
375354
_, _ = fmt.Fprintln(out, "You should now be able to ssh into your workspace.")
376-
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh coder.%s\n\n", workspaceConfigs[0].Name)
355+
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh coder.%s\n", workspaceConfigs[0].Name)
377356
} else {
378-
_, _ = fmt.Fprint(out, "You don't have any workspaces yet, try creating one with:\n\n\t$ coder create <workspace>\n\n")
357+
_, _ = fmt.Fprint(out, "You don't have any workspaces yet, try creating one with:\n\n\t$ coder create <workspace>\n")
379358
}
380359
return nil
381360
},
@@ -389,10 +368,6 @@ func configSSH() *cobra.Command {
389368
cliflag.BoolVarP(cmd.Flags(), &wireguard, "wireguard", "", "CODER_CONFIG_SSH_WIREGUARD", false, "Whether to use Wireguard for SSH tunneling.")
390369
_ = cmd.Flags().MarkHidden("wireguard")
391370

392-
// Deprecated: Remove after migration period.
393-
cmd.Flags().StringVar(&coderConfigFile, "test.ssh-coder-config-file", sshDefaultCoderConfigFileName, "Specifies the path to an Coder SSH config file. Useful for testing.")
394-
_ = cmd.Flags().MarkHidden("test.ssh-coder-config-file")
395-
396371
cliui.AllowSkipPrompt(cmd)
397372

398373
return cmd

0 commit comments

Comments
 (0)