Skip to content

Commit 4eadcf7

Browse files
authored
Merge branch 'main' into matifali/template-push-create
2 parents 66f9eb0 + 0a6e644 commit 4eadcf7

File tree

75 files changed

+2798
-322
lines changed

Some content is hidden

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

75 files changed

+2798
-322
lines changed

.github/workflows/ci.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ jobs:
4747
docs:
4848
- "docs/**"
4949
- "README.md"
50-
# For testing:
51-
# - ".github/**"
50+
- "examples/templates/**"
51+
- "examples/web-server/**"
52+
- "examples/monitoring/**"
53+
- "examples/lima/**"
5254
go:
5355
- "**.sql"
5456
- "**.go"
@@ -231,7 +233,7 @@ jobs:
231233

232234
- uses: hashicorp/setup-terraform@v2
233235
with:
234-
terraform_version: 1.1.9
236+
terraform_version: 1.5.1
235237
terraform_wrapper: false
236238

237239
- name: Test with Mock Database
@@ -296,7 +298,7 @@ jobs:
296298

297299
- uses: hashicorp/setup-terraform@v2
298300
with:
299-
terraform_version: 1.1.9
301+
terraform_version: 1.5.1
300302
terraform_wrapper: false
301303

302304
- name: Test with PostgreSQL Database
@@ -338,6 +340,11 @@ jobs:
338340

339341
- uses: ./.github/actions/setup-go
340342

343+
- uses: hashicorp/setup-terraform@v2
344+
with:
345+
terraform_version: 1.5.1
346+
terraform_wrapper: false
347+
341348
- name: Run Tests
342349
run: |
343350
gotestsum --junitfile="gotests.xml" -- -race ./...
@@ -474,7 +481,7 @@ jobs:
474481

475482
- uses: hashicorp/setup-terraform@v2
476483
with:
477-
terraform_version: 1.1.9
484+
terraform_version: 1.5.1
478485
terraform_wrapper: false
479486

480487
- name: Build

.github/workflows/pr-cleanup.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
id: pr_number
2020
run: |
2121
if [ -z "${{ github.event.pull_request.number }}" ]; then
22-
echo "PR_NUMBER=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
22+
echo "PR_NUMBER=${{ github.event.pull_request.number }}" >> $GITHUB_OUTPUT
2323
else
2424
echo "PR_NUMBER=${{ github.event.inputs.pr_number }}" >> $GITHUB_OUTPUT
2525
fi

.github/workflows/pr-deploy.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ concurrency:
2222

2323
jobs:
2424
pr_commented:
25-
if: ${{ github.event.issue.pull_request }} && contains(github.event.comment.body, '/deploy-pr') && github.event.comment.author_association == 'MEMBER' || github.event_name == 'workflow_dispatch'
25+
if: github.event_name == 'issue_comment' && contains(github.event.comment.body, '/deploy-pr') && github.event.comment.author_association == 'MEMBER' || github.event_name == 'workflow_dispatch'
2626
outputs:
2727
PR_NUMBER: ${{ steps.pr_number.outputs.PR_NUMBER }}
2828
PR_TITLE: ${{ steps.pr_number.outputs.PR_TITLE }}
@@ -37,12 +37,12 @@ jobs:
3737
id: pr_number
3838
run: |
3939
set -euxo pipefail
40-
if [[ ${{ github.event_name }} == 'workflow_dispatch' ]]; then
40+
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
4141
PR_NUMBER=${{ github.event.inputs.pr_number }}
42-
PR_TITLE=$(curl -sSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER | jq -r '.title')
42+
PR_TITLE=$(curl -sSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER" | jq -r '.title')
4343
else
4444
PR_NUMBER=${{ github.event.issue.number }}
45-
PR_TITLE=${{ github.event.issue.title }}
45+
PR_TITLE='${{ github.event.issue.title }}'
4646
fi
4747
echo "PR_URL=https://github.com/coder/coder/pull/$PR_NUMBER" >> $GITHUB_OUTPUT
4848
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT

.github/workflows/stale.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Stale Issue and Branch Cleanup
1+
name: Stale Issue, Banch and Old Workflows Cleanup
22
on:
33
schedule:
44
# Every day at midnight
@@ -10,6 +10,7 @@ jobs:
1010
permissions:
1111
issues: write
1212
pull-requests: write
13+
actions: write
1314
steps:
1415
- uses: actions/stale@v8.0.0
1516
with:
@@ -42,3 +43,14 @@ jobs:
4243
delete_tags: false
4344
# extra_protected_branch_regex: ^(foo|bar)$
4445
exclude_open_pr_branches: true
46+
del_runs:
47+
runs-on: ubuntu-latest
48+
steps:
49+
- name: Delete workflow runs
50+
uses: Mattraks/delete-workflow-runs@v2
51+
with:
52+
token: ${{ github.token }}
53+
repository: ${{ github.repository }}
54+
retain_days: 1
55+
keep_minimum_runs: 1
56+
delete_workflow_pattern: pr-cleanup.yaml

agent/agent.go

Lines changed: 19 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package agent
22

33
import (
4-
"bufio"
54
"bytes"
65
"context"
76
"encoding/binary"
@@ -863,26 +862,30 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) (err er
863862
}
864863
cmd := cmdPty.AsExec()
865864

866-
var writer io.Writer = fileWriter
865+
var stdout, stderr io.Writer = fileWriter, fileWriter
867866
if lifecycle == "startup" {
868-
// Create pipes for startup logs reader and writer
869-
logsReader, logsWriter := io.Pipe()
867+
send, flushAndClose := agentsdk.StartupLogsSender(a.client.PatchStartupLogs, logger)
868+
// If ctx is canceled here (or in a writer below), we may be
869+
// discarding logs, but that's okay because we're shutting down
870+
// anyway. We could consider creating a new context here if we
871+
// want better control over flush during shutdown.
870872
defer func() {
871-
_ = logsReader.Close()
872-
}()
873-
writer = io.MultiWriter(fileWriter, logsWriter)
874-
flushedLogs, err := a.trackScriptLogs(ctx, logsReader)
875-
if err != nil {
876-
return xerrors.Errorf("track %s script logs: %w", lifecycle, err)
877-
}
878-
defer func() {
879-
_ = logsWriter.Close()
880-
<-flushedLogs
873+
if err := flushAndClose(ctx); err != nil {
874+
logger.Warn(ctx, "flush startup logs failed", slog.Error(err))
875+
}
881876
}()
877+
878+
infoW := agentsdk.StartupLogsWriter(ctx, send, codersdk.LogLevelInfo)
879+
defer infoW.Close()
880+
errW := agentsdk.StartupLogsWriter(ctx, send, codersdk.LogLevelError)
881+
defer errW.Close()
882+
883+
stdout = io.MultiWriter(fileWriter, infoW)
884+
stderr = io.MultiWriter(fileWriter, errW)
882885
}
883886

884-
cmd.Stdout = writer
885-
cmd.Stderr = writer
887+
cmd.Stdout = stdout
888+
cmd.Stderr = stderr
886889

887890
start := time.Now()
888891
defer func() {
@@ -913,143 +916,6 @@ func (a *agent) runScript(ctx context.Context, lifecycle, script string) (err er
913916
return nil
914917
}
915918

916-
func (a *agent) trackScriptLogs(ctx context.Context, reader io.ReadCloser) (chan struct{}, error) {
917-
// Synchronous sender, there can only be one outbound send at a time.
918-
//
919-
// It's important that we either flush or drop all logs before returning
920-
// because the startup state is reported after flush.
921-
sendDone := make(chan struct{})
922-
send := make(chan []agentsdk.StartupLog, 1)
923-
go func() {
924-
// Set flushTimeout and backlogLimit so that logs are uploaded
925-
// once every 250ms or when 100 logs have been added to the
926-
// backlog, whichever comes first.
927-
flushTimeout := 250 * time.Millisecond
928-
backlogLimit := 100
929-
930-
flush := time.NewTicker(flushTimeout)
931-
932-
var backlog []agentsdk.StartupLog
933-
defer func() {
934-
flush.Stop()
935-
_ = reader.Close() // Ensure read routine is closed.
936-
if len(backlog) > 0 {
937-
a.logger.Debug(ctx, "track script logs sender exiting, discarding logs", slog.F("discarded_logs_count", len(backlog)))
938-
}
939-
a.logger.Debug(ctx, "track script logs sender exited")
940-
close(sendDone)
941-
}()
942-
943-
done := false
944-
for {
945-
flushed := false
946-
select {
947-
case <-ctx.Done():
948-
return
949-
case <-a.closed:
950-
return
951-
// Close (!ok) can be triggered by the reader closing due to
952-
// EOF or due to agent closing, when this happens we attempt
953-
// a final flush. If the context is canceled this will be a
954-
// no-op.
955-
case logs, ok := <-send:
956-
done = !ok
957-
if ok {
958-
backlog = append(backlog, logs...)
959-
flushed = len(backlog) >= backlogLimit
960-
}
961-
case <-flush.C:
962-
flushed = true
963-
}
964-
965-
if (done || flushed) && len(backlog) > 0 {
966-
flush.Stop() // Lower the chance of a double flush.
967-
968-
// Retry uploading logs until successful or a specific
969-
// error occurs.
970-
for r := retry.New(time.Second, 5*time.Second); r.Wait(ctx); {
971-
err := a.client.PatchStartupLogs(ctx, agentsdk.PatchStartupLogs{
972-
Logs: backlog,
973-
})
974-
if err == nil {
975-
break
976-
}
977-
978-
if errors.Is(err, context.Canceled) {
979-
return
980-
}
981-
var sdkErr *codersdk.Error
982-
if errors.As(err, &sdkErr) {
983-
if sdkErr.StatusCode() == http.StatusRequestEntityTooLarge {
984-
a.logger.Warn(ctx, "startup logs too large, dropping logs")
985-
break
986-
}
987-
}
988-
a.logger.Error(ctx, "upload startup logs failed", slog.Error(err), slog.F("to_send", backlog))
989-
}
990-
if ctx.Err() != nil {
991-
return
992-
}
993-
backlog = nil
994-
995-
// Anchor flush to the last log upload.
996-
flush.Reset(flushTimeout)
997-
}
998-
if done {
999-
return
1000-
}
1001-
}
1002-
}()
1003-
1004-
// Forward read lines to the sender or queue them for when the
1005-
// sender is ready to process them.
1006-
//
1007-
// We only need to track this goroutine since it will ensure that
1008-
// the sender has closed before returning.
1009-
logsDone := make(chan struct{})
1010-
err := a.trackConnGoroutine(func() {
1011-
defer func() {
1012-
close(send)
1013-
<-sendDone
1014-
a.logger.Debug(ctx, "track script logs reader exited")
1015-
close(logsDone)
1016-
}()
1017-
1018-
var queue []agentsdk.StartupLog
1019-
1020-
s := bufio.NewScanner(reader)
1021-
for s.Scan() {
1022-
select {
1023-
case <-ctx.Done():
1024-
return
1025-
case <-a.closed:
1026-
return
1027-
case queue = <-send:
1028-
// Not captured by sender yet, re-use.
1029-
default:
1030-
}
1031-
1032-
queue = append(queue, agentsdk.StartupLog{
1033-
CreatedAt: database.Now(),
1034-
Output: s.Text(),
1035-
})
1036-
send <- queue
1037-
queue = nil
1038-
}
1039-
if err := s.Err(); err != nil {
1040-
a.logger.Warn(ctx, "scan startup logs ended unexpectedly", slog.Error(err))
1041-
}
1042-
})
1043-
if err != nil {
1044-
close(send)
1045-
<-sendDone
1046-
close(logsDone)
1047-
return logsDone, err
1048-
}
1049-
1050-
return logsDone, nil
1051-
}
1052-
1053919
func (a *agent) handleReconnectingPTY(ctx context.Context, logger slog.Logger, msg codersdk.WorkspaceAgentReconnectingPTYInit, conn net.Conn) (retErr error) {
1054920
defer conn.Close()
1055921
a.metrics.connectionsTotal.Add(1)

cli/configssh.go

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,12 @@ func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (r
192192
//nolint:gocyclo
193193
func (r *RootCmd) configSSH() *clibase.Cmd {
194194
var (
195-
sshConfigFile string
196-
sshConfigOpts sshConfigOptions
197-
usePreviousOpts bool
198-
dryRun bool
199-
skipProxyCommand bool
195+
sshConfigFile string
196+
sshConfigOpts sshConfigOptions
197+
usePreviousOpts bool
198+
dryRun bool
199+
skipProxyCommand bool
200+
forceUnixSeparators bool
200201
)
201202
client := new(codersdk.Client)
202203
cmd := &clibase.Cmd{
@@ -236,13 +237,13 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
236237
if err != nil {
237238
return err
238239
}
239-
escapedCoderBinary, err := sshConfigExecEscape(coderBinary)
240+
escapedCoderBinary, err := sshConfigExecEscape(coderBinary, forceUnixSeparators)
240241
if err != nil {
241242
return xerrors.Errorf("escape coder binary for ssh failed: %w", err)
242243
}
243244

244245
root := r.createConfig()
245-
escapedGlobalConfig, err := sshConfigExecEscape(string(root))
246+
escapedGlobalConfig, err := sshConfigExecEscape(string(root), forceUnixSeparators)
246247
if err != nil {
247248
return xerrors.Errorf("escape global config for ssh failed: %w", err)
248249
}
@@ -540,6 +541,19 @@ func (r *RootCmd) configSSH() *clibase.Cmd {
540541
Default: "auto",
541542
Value: clibase.EnumOf(&sshConfigOpts.waitEnum, "yes", "no", "auto"),
542543
},
544+
{
545+
Flag: "force-unix-filepaths",
546+
Env: "CODER_CONFIGSSH_UNIX_FILEPATHS",
547+
Description: "By default, 'config-ssh' uses the os path separator when writing the ssh config. " +
548+
"This might be an issue in Windows machine that use a unix-like shell. " +
549+
"This flag forces the use of unix file paths (the forward slash '/').",
550+
Value: clibase.BoolOf(&forceUnixSeparators),
551+
// On non-windows showing this command is useless because it is a noop.
552+
// Hide vs disable it though so if a command is copied from a Windows
553+
// machine to a unix machine it will still work and not throw an
554+
// "unknown flag" error.
555+
Hidden: hideForceUnixSlashes,
556+
},
543557
cliui.SkipPromptOption(),
544558
}
545559

@@ -727,7 +741,31 @@ func writeWithTempFileAndMove(path string, r io.Reader) (err error) {
727741
// - https://github.com/openssh/openssh-portable/blob/V_9_0_P1/sshconnect.c#L158-L167
728742
// - https://github.com/PowerShell/openssh-portable/blob/v8.1.0.0/sshconnect.c#L231-L293
729743
// - https://github.com/PowerShell/openssh-portable/blob/v8.1.0.0/contrib/win32/win32compat/w32fd.c#L1075-L1100
730-
func sshConfigExecEscape(path string) (string, error) {
744+
//
745+
// Additional Windows-specific notes:
746+
//
747+
// In some situations a Windows user could be using a unix-like shell such as
748+
// git bash. In these situations the coder.exe is using the windows filepath
749+
// separator (\), but the shell wants the unix filepath separator (/).
750+
// Trying to determine if the shell is unix-like is difficult, so this function
751+
// takes the argument 'forceUnixPath' to force the filepath to be unix-like.
752+
//
753+
// On actual unix machines, this is **always** a noop. Even if a windows
754+
// path is provided.
755+
//
756+
// Passing a "false" for forceUnixPath will result in the filepath separator
757+
// untouched from the original input.
758+
// ---
759+
// This is a control flag, and that is ok. It is a control flag
760+
// based on the OS of the user. Making this a different file is excessive.
761+
// nolint:revive
762+
func sshConfigExecEscape(path string, forceUnixPath bool) (string, error) {
763+
if forceUnixPath {
764+
// This is a workaround for #7639, where the filepath separator is
765+
// incorrectly the Windows separator (\) instead of the unix separator (/).
766+
path = filepath.ToSlash(path)
767+
}
768+
731769
// This is unlikely to ever happen, but newlines are allowed on
732770
// certain filesystems, but cannot be used inside ssh config.
733771
if strings.ContainsAny(path, "\n") {

0 commit comments

Comments
 (0)