Skip to content

Commit 878376e

Browse files
committed
Merge branch 'main' of github.com:coder/coder into spike/1359_no_tfexec
2 parents 8052b50 + e3a1cd3 commit 878376e

File tree

93 files changed

+2138
-937
lines changed

Some content is hidden

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

93 files changed

+2138
-937
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"sdktrace",
6464
"Signup",
6565
"sourcemapped",
66+
"Srcs",
6667
"stretchr",
6768
"TCGETS",
6869
"tcpip",

cli/bump.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,55 +12,55 @@ import (
1212
)
1313

1414
const (
15-
bumpDescriptionLong = `To extend the autostop deadline for a workspace.
16-
If no unit is specified in the duration, we assume minutes.`
17-
defaultBumpDuration = 90 * time.Minute
15+
bumpDescriptionLong = `To extend the autostop deadline for a workspace.`
1816
)
1917

2018
func bump() *cobra.Command {
2119
bumpCmd := &cobra.Command{
2220
Args: cobra.RangeArgs(1, 2),
2321
Annotations: workspaceCommand,
24-
Use: "bump <workspace-name> [duration]",
22+
Use: "bump <workspace-name> <duration>",
2523
Short: "Extend the autostop deadline for a workspace.",
2624
Long: bumpDescriptionLong,
2725
Example: "coder bump my-workspace 90m",
2826
RunE: func(cmd *cobra.Command, args []string) error {
29-
bumpDuration := defaultBumpDuration
30-
if len(args) > 1 {
31-
d, err := tryParseDuration(args[1])
32-
if err != nil {
33-
return err
34-
}
35-
bumpDuration = d
36-
}
37-
38-
if bumpDuration < time.Minute {
39-
return xerrors.New("minimum bump duration is 1 minute")
27+
bumpDuration, err := tryParseDuration(args[1])
28+
if err != nil {
29+
return err
4030
}
4131

4232
client, err := createClient(cmd)
4333
if err != nil {
4434
return xerrors.Errorf("create client: %w", err)
4535
}
36+
4637
workspace, err := namedWorkspace(cmd, client, args[0])
4738
if err != nil {
4839
return xerrors.Errorf("get workspace: %w", err)
4940
}
5041

51-
if workspace.LatestBuild.Deadline.IsZero() {
52-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "no deadline set\n")
42+
newDeadline := time.Now().Add(bumpDuration)
43+
44+
if newDeadline.Before(workspace.LatestBuild.Deadline) {
45+
_, _ = fmt.Fprintf(
46+
cmd.OutOrStdout(),
47+
"The proposed deadline is %s before the current deadline.\n",
48+
workspace.LatestBuild.Deadline.Sub(newDeadline).Round(time.Minute),
49+
)
5350
return nil
5451
}
5552

56-
newDeadline := workspace.LatestBuild.Deadline.Add(bumpDuration)
5753
if err := client.PutExtendWorkspace(cmd.Context(), workspace.ID, codersdk.PutExtendWorkspaceRequest{
5854
Deadline: newDeadline,
5955
}); err != nil {
6056
return err
6157
}
6258

63-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Workspace %q will now stop at %s\n", workspace.Name, newDeadline.Format(time.RFC3339))
59+
_, _ = fmt.Fprintf(
60+
cmd.OutOrStdout(),
61+
"Workspace %q will now stop at %s\n", workspace.Name,
62+
newDeadline.Format(time.RFC822),
63+
)
6464

6565
return nil
6666
},

cli/bump_test.go

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -17,47 +17,6 @@ import (
1717
func TestBump(t *testing.T) {
1818
t.Parallel()
1919

20-
t.Run("BumpOKDefault", func(t *testing.T) {
21-
t.Parallel()
22-
23-
// Given: we have a workspace
24-
var (
25-
err error
26-
ctx = context.Background()
27-
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
28-
user = coderdtest.CreateFirstUser(t, client)
29-
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
30-
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
31-
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
32-
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
33-
cmdArgs = []string{"bump", workspace.Name}
34-
stdoutBuf = &bytes.Buffer{}
35-
)
36-
37-
// Given: we wait for the workspace to be built
38-
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
39-
workspace, err = client.Workspace(ctx, workspace.ID)
40-
require.NoError(t, err)
41-
expectedDeadline := workspace.LatestBuild.Deadline.Add(90 * time.Minute)
42-
43-
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
44-
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
45-
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
46-
47-
cmd, root := clitest.New(t, cmdArgs...)
48-
clitest.SetupConfig(t, client, root)
49-
cmd.SetOut(stdoutBuf)
50-
51-
// When: we execute `coder bump <workspace>`
52-
err = cmd.ExecuteContext(ctx)
53-
require.NoError(t, err, "unexpected error")
54-
55-
// Then: the deadline of the latest build is updated
56-
updated, err := client.Workspace(ctx, workspace.ID)
57-
require.NoError(t, err)
58-
require.WithinDuration(t, expectedDeadline, updated.LatestBuild.Deadline, time.Minute)
59-
})
60-
6120
t.Run("BumpSpecificDuration", func(t *testing.T) {
6221
t.Parallel()
6322

@@ -71,15 +30,15 @@ func TestBump(t *testing.T) {
7130
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
7231
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
7332
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
74-
cmdArgs = []string{"bump", workspace.Name, "30"}
33+
cmdArgs = []string{"bump", workspace.Name, "10h"}
7534
stdoutBuf = &bytes.Buffer{}
7635
)
7736

7837
// Given: we wait for the workspace to be built
7938
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
8039
workspace, err = client.Workspace(ctx, workspace.ID)
8140
require.NoError(t, err)
82-
expectedDeadline := workspace.LatestBuild.Deadline.Add(30 * time.Minute)
41+
expectedDeadline := time.Now().Add(10 * time.Hour)
8342

8443
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
8544
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
@@ -150,7 +109,7 @@ func TestBump(t *testing.T) {
150109
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID, func(cwr *codersdk.CreateWorkspaceRequest) {
151110
cwr.TTLMillis = nil
152111
})
153-
cmdArgs = []string{"bump", workspace.Name}
112+
cmdArgs = []string{"bump", workspace.Name, "1h"}
154113
stdoutBuf = &bytes.Buffer{}
155114
)
156115
// Unset the workspace TTL
@@ -180,51 +139,11 @@ func TestBump(t *testing.T) {
180139

181140
// When: we execute `coder bump workspace``
182141
err = cmd.ExecuteContext(ctx)
183-
require.NoError(t, err)
142+
require.Error(t, err)
184143

185144
// Then: nothing happens and the deadline remains unset
186145
updated, err := client.Workspace(ctx, workspace.ID)
187146
require.NoError(t, err)
188147
require.Zero(t, updated.LatestBuild.Deadline)
189148
})
190-
191-
t.Run("BumpMinimumDuration", func(t *testing.T) {
192-
t.Parallel()
193-
194-
// Given: we have a workspace with no deadline set
195-
var (
196-
err error
197-
ctx = context.Background()
198-
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
199-
user = coderdtest.CreateFirstUser(t, client)
200-
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
201-
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
202-
project = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
203-
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, project.ID)
204-
cmdArgs = []string{"bump", workspace.Name, "59s"}
205-
stdoutBuf = &bytes.Buffer{}
206-
)
207-
208-
// Given: we wait for the workspace to build
209-
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
210-
workspace, err = client.Workspace(ctx, workspace.ID)
211-
require.NoError(t, err)
212-
213-
// Assert test invariant: workspace build has a deadline set equal to now plus ttl
214-
initDeadline := time.Now().Add(time.Duration(*workspace.TTLMillis) * time.Millisecond)
215-
require.WithinDuration(t, initDeadline, workspace.LatestBuild.Deadline, time.Minute)
216-
217-
cmd, root := clitest.New(t, cmdArgs...)
218-
clitest.SetupConfig(t, client, root)
219-
cmd.SetOut(stdoutBuf)
220-
221-
// When: we execute `coder bump workspace 59s`
222-
err = cmd.ExecuteContext(ctx)
223-
require.ErrorContains(t, err, "minimum bump duration is 1 minute")
224-
225-
// Then: an error is reported and the deadline remains as before
226-
updated, err := client.Workspace(ctx, workspace.ID)
227-
require.NoError(t, err)
228-
require.WithinDuration(t, workspace.LatestBuild.Deadline, updated.LatestBuild.Deadline, time.Minute)
229-
})
230149
}

cli/create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func create() *cobra.Command {
4949
workspaceName, err = cliui.Prompt(cmd, cliui.PromptOptions{
5050
Text: "Specify a name for your workspace:",
5151
Validate: func(workspaceName string) error {
52-
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceByOwnerAndNameParams{})
52+
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
5353
if err == nil {
5454
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
5555
}
@@ -61,7 +61,7 @@ func create() *cobra.Command {
6161
}
6262
}
6363

64-
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceByOwnerAndNameParams{})
64+
_, err = client.WorkspaceByOwnerAndName(cmd.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
6565
if err == nil {
6666
return xerrors.Errorf("A workspace already exists named %q!", workspaceName)
6767
}

cli/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, identifier stri
214214
return codersdk.Workspace{}, xerrors.Errorf("invalid workspace name: %q", identifier)
215215
}
216216

217-
return client.WorkspaceByOwnerAndName(cmd.Context(), owner, name, codersdk.WorkspaceByOwnerAndNameParams{})
217+
return client.WorkspaceByOwnerAndName(cmd.Context(), owner, name, codersdk.WorkspaceOptions{})
218218
}
219219

220220
// createConfig consumes the global configuration flag to produce a config root.

cli/server.go

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"golang.org/x/mod/semver"
3434
"golang.org/x/oauth2"
3535
xgithub "golang.org/x/oauth2/github"
36+
"golang.org/x/sync/errgroup"
3637
"golang.org/x/xerrors"
3738
"google.golang.org/api/idtoken"
3839
"google.golang.org/api/option"
@@ -169,8 +170,9 @@ func server() *cobra.Command {
169170
}
170171

171172
var (
172-
tunnelErrChan <-chan error
173173
ctxTunnel, closeTunnel = context.WithCancel(cmd.Context())
174+
devTunnel = (*devtunnel.Tunnel)(nil)
175+
devTunnelErrChan = make(<-chan error, 1)
174176
)
175177
defer closeTunnel()
176178

@@ -195,14 +197,37 @@ func server() *cobra.Command {
195197
}
196198
}
197199
if err == nil {
198-
accessURL, tunnelErrChan, err = devtunnel.New(ctxTunnel, localURL)
200+
devTunnel, devTunnelErrChan, err = devtunnel.New(ctxTunnel, logger.Named("devtunnel"))
199201
if err != nil {
200202
return xerrors.Errorf("create tunnel: %w", err)
201203
}
204+
accessURL = devTunnel.URL
202205
}
203206
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
204207
}
205208

209+
// Warn the user if the access URL appears to be a loopback address.
210+
isLocal, err := isLocalURL(cmd.Context(), accessURL)
211+
if isLocal || err != nil {
212+
var reason string
213+
if isLocal {
214+
reason = "appears to be a loopback address"
215+
} else {
216+
reason = "could not be resolved"
217+
}
218+
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), cliui.Styles.Wrap.Render(
219+
cliui.Styles.Warn.Render("Warning:")+" The current access URL:")+"\n\n")
220+
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), " "+cliui.Styles.Field.Render(accessURL)+"\n\n")
221+
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), cliui.Styles.Wrap.Render(
222+
reason+". Provisioned workspaces are unlikely to be able to "+
223+
"connect to Coder. Please consider changing your "+
224+
"access URL using the --access-url option, or directly "+
225+
"specifying access URLs on templates.",
226+
)+"\n\n")
227+
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "For more information, see "+
228+
"https://github.com/coder/coder/issues/1528\n\n")
229+
}
230+
206231
validator, err := idtoken.NewValidator(cmd.Context(), option.WithoutAuthentication())
207232
if err != nil {
208233
return err
@@ -327,7 +352,27 @@ func server() *cobra.Command {
327352
return shutdownConnsCtx
328353
},
329354
}
330-
errCh <- server.Serve(listener)
355+
356+
wg := errgroup.Group{}
357+
wg.Go(func() error {
358+
// Make sure to close the tunnel listener if we exit so the
359+
// errgroup doesn't wait forever!
360+
if dev && tunnel {
361+
defer devTunnel.Listener.Close()
362+
}
363+
364+
return server.Serve(listener)
365+
})
366+
367+
if dev && tunnel {
368+
wg.Go(func() error {
369+
defer listener.Close()
370+
371+
return server.Serve(devTunnel.Listener)
372+
})
373+
}
374+
375+
errCh <- wg.Wait()
331376
}()
332377

333378
config := createConfig(cmd)
@@ -393,7 +438,7 @@ func server() *cobra.Command {
393438
case <-cmd.Context().Done():
394439
coderAPI.Close()
395440
return cmd.Context().Err()
396-
case err := <-tunnelErrChan:
441+
case err := <-devTunnelErrChan:
397442
if err != nil {
398443
return err
399444
}
@@ -456,7 +501,7 @@ func server() *cobra.Command {
456501
if dev && tunnel {
457502
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for dev tunnel to close...\n")
458503
closeTunnel()
459-
<-tunnelErrChan
504+
<-devTunnelErrChan
460505
}
461506

462507
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for WebSocket connections to close...\n")
@@ -473,8 +518,12 @@ func server() *cobra.Command {
473518
cliflag.StringVarP(root.Flags(), &promAddress, "prometheus-address", "", "CODER_PROMETHEUS_ADDRESS", "127.0.0.1:2112", "The address to serve prometheus metrics.")
474519
cliflag.BoolVarP(root.Flags(), &pprofEnabled, "pprof-enable", "", "CODER_PPROF_ENABLE", false, "Enable serving pprof metrics on the address defined by --pprof-address.")
475520
cliflag.StringVarP(root.Flags(), &pprofAddress, "pprof-address", "", "CODER_PPROF_ADDRESS", "127.0.0.1:6060", "The address to serve pprof.")
476-
// systemd uses the CACHE_DIRECTORY environment variable!
477-
cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CACHE_DIRECTORY", filepath.Join(os.TempDir(), "coder-cache"), "Specifies a directory to cache binaries for provision operations.")
521+
defaultCacheDir := filepath.Join(os.TempDir(), "coder-cache")
522+
if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" {
523+
// For compatibility with systemd.
524+
defaultCacheDir = dir
525+
}
526+
cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CODER_CACHE_DIRECTORY", defaultCacheDir, "Specifies a directory to cache binaries for provision operations. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.")
478527
cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering")
479528
cliflag.StringVarP(root.Flags(), &devUserEmail, "dev-admin-email", "", "CODER_DEV_ADMIN_EMAIL", "admin@coder.com", "Specifies the admin email to be used in dev mode (--dev)")
480529
cliflag.StringVarP(root.Flags(), &devUserPassword, "dev-admin-password", "", "CODER_DEV_ADMIN_PASSWORD", "", "Specifies the admin password to be used in dev mode (--dev) instead of a randomly generated one")
@@ -799,3 +848,24 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler,
799848

800849
return func() { _ = srv.Close() }
801850
}
851+
852+
// isLocalURL returns true if the hostname of the provided URL appears to
853+
// resolve to a loopback address.
854+
func isLocalURL(ctx context.Context, urlString string) (bool, error) {
855+
parsedURL, err := url.Parse(urlString)
856+
if err != nil {
857+
return false, err
858+
}
859+
resolver := &net.Resolver{}
860+
ips, err := resolver.LookupIPAddr(ctx, parsedURL.Hostname())
861+
if err != nil {
862+
return false, err
863+
}
864+
865+
for _, ip := range ips {
866+
if ip.IP.IsLoopback() {
867+
return true, nil
868+
}
869+
}
870+
return false, nil
871+
}

0 commit comments

Comments
 (0)