Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2f0be80

Browse files
Kira-PilotAbhineetJainjsjoeiomafredrigreyscaled
authored
Add template tooltips/kira pilot (#2308)
* feat: update build url to @username/workspace/builds/buildnumber (#2234) * update build url to @username/workspace/builds/buildnumber * update errors thrown from the API * add unit tests for the new API * add t.parallel * get username and workspace name from params * fix: update icon (#2216) * feat: Show template description in `coder template init` (#2238) * fix: workspace schedule time displays (#2249) Summary: Various time displays weren't quite right. Details: - Display date (not just time) of upcoming workspace stop in workspace page - Fix ttlShutdownAt for various cases + tests - manual to non-manual - unchanged/unmodified - isBefore --> isSameOrBefore - use the delta (off by _ error) - pluralize units in dayjs.add * fix: Remove easter egg mentioning competitor (#2250) This is more confusing than helpful! * feat: Warn on coderd startup if access URL is localhost (#2248) * feat: use custom wireguard reverse proxy for dev tunnel (#1975) * fix: use correct link in create from template button (#2253) * feat: store and display template creator (#2228) * design commit * add owner_id to templates table * add owner information in apis and ui * update minWidth for statItem * rename owner to created_by * missing refactor to created_by * handle errors in fetching created_by names * feat: update language on workspace page (#2220) * fix: ensure config dir exists before reading tunnel config (#2259) * fix(devtunnel): close `http.Server` before wireguard interface (#2263) * fix: ensure `agentResource` is non-nil (#2261) * chore: add hero image to OSS docs homepage (#2241) * fix: Do not write 2 errors to api on template fetch error (#2285) * feat: add tooltips to templates page resolves #2242 Co-authored-by: Abhineet Jain <[email protected]> Co-authored-by: Joe Previte <[email protected]> Co-authored-by: Mathias Fredriksson <[email protected]> Co-authored-by: G r e y <[email protected]> Co-authored-by: Kyle Carberry <[email protected]> Co-authored-by: David Wahler <[email protected]> Co-authored-by: Colin Adler <[email protected]> Co-authored-by: Garrett Delfosse <[email protected]> Co-authored-by: Katie Horne <[email protected]> Co-authored-by: Steven Masley <[email protected]>
1 parent 953c542 commit 2f0be80

Some content is hidden

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

56 files changed

+1186
-356
lines changed

cli/server.go

Lines changed: 71 additions & 5 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

@@ -197,14 +199,37 @@ func server() *cobra.Command {
197199
}
198200
}
199201
if err == nil {
200-
accessURL, tunnelErrChan, err = devtunnel.New(ctxTunnel, localURL)
202+
devTunnel, devTunnelErrChan, err = devtunnel.New(ctxTunnel, logger.Named("devtunnel"))
201203
if err != nil {
202204
return xerrors.Errorf("create tunnel: %w", err)
203205
}
206+
accessURL = devTunnel.URL
204207
}
205208
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
206209
}
207210

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

335380
config := createConfig(cmd)
@@ -395,7 +440,7 @@ func server() *cobra.Command {
395440
case <-cmd.Context().Done():
396441
coderAPI.Close()
397442
return cmd.Context().Err()
398-
case err := <-tunnelErrChan:
443+
case err := <-devTunnelErrChan:
399444
if err != nil {
400445
return err
401446
}
@@ -458,7 +503,7 @@ func server() *cobra.Command {
458503
if dev && tunnel {
459504
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for dev tunnel to close...\n")
460505
closeTunnel()
461-
<-tunnelErrChan
506+
<-devTunnelErrChan
462507
}
463508

464509
_, _ = fmt.Fprintf(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"Waiting for WebSocket connections to close...\n")
@@ -805,3 +850,24 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler,
805850

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

cli/server_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ func TestServer(t *testing.T) {
118118
} else {
119119
t.Error("expected password line output; got no match")
120120
}
121+
122+
// Verify that we warned the user about the default access URL possibly not being what they want.
123+
assert.Contains(t, buf.String(), "coder/coder/issues/1528")
121124
})
122125

123126
// Duplicated test from "Development" above to test setting email/password via env.
@@ -163,6 +166,32 @@ func TestServer(t *testing.T) {
163166
assert.Contains(t, buf.String(), fmt.Sprintf("password: %s", wantPassword), "expected output %q; got no match", wantPassword)
164167
})
165168

169+
t.Run("NoWarningWithRemoteAccessURL", func(t *testing.T) {
170+
t.Parallel()
171+
ctx, cancelFunc := context.WithCancel(context.Background())
172+
defer cancelFunc()
173+
174+
root, cfg := clitest.New(t, "server", "--dev", "--tunnel=false", "--address", ":0", "--access-url", "http://1.2.3.4:3000/")
175+
var buf strings.Builder
176+
errC := make(chan error)
177+
root.SetOutput(&buf)
178+
go func() {
179+
errC <- root.ExecuteContext(ctx)
180+
}()
181+
182+
// Just wait for startup
183+
require.Eventually(t, func() bool {
184+
var err error
185+
_, err = cfg.URL().Read()
186+
return err == nil
187+
}, 15*time.Second, 25*time.Millisecond)
188+
189+
cancelFunc()
190+
require.ErrorIs(t, <-errC, context.Canceled)
191+
192+
assert.NotContains(t, buf.String(), "coder/coder/issues/1528")
193+
})
194+
166195
t.Run("TLSBadVersion", func(t *testing.T) {
167196
t.Parallel()
168197
ctx, cancelFunc := context.WithCancel(context.Background())

cli/templateinit.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,13 @@ func templateInit() *cobra.Command {
2424
exampleNames := []string{}
2525
exampleByName := map[string]examples.Example{}
2626
for _, example := range exampleList {
27-
exampleNames = append(exampleNames, example.Name)
28-
exampleByName[example.Name] = example
27+
name := fmt.Sprintf(
28+
"%s\n%s\n",
29+
cliui.Styles.Bold.Render(example.Name),
30+
cliui.Styles.Wrap.Copy().PaddingLeft(6).Render(example.Description),
31+
)
32+
exampleNames = append(exampleNames, name)
33+
exampleByName[name] = example
2934
}
3035

3136
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Wrap.Render(

cmd/coder/main.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,13 @@ import (
44
"errors"
55
"fmt"
66
"os"
7-
"os/exec"
8-
"path/filepath"
9-
"strings"
107
_ "time/tzdata"
118

129
"github.com/coder/coder/cli"
1310
"github.com/coder/coder/cli/cliui"
1411
)
1512

1613
func main() {
17-
dadjoke()
1814
cmd, err := cli.Root().ExecuteC()
1915
if err != nil {
2016
if errors.Is(err, cliui.Canceled) {
@@ -25,23 +21,3 @@ func main() {
2521
os.Exit(1)
2622
}
2723
}
28-
29-
//nolint
30-
func dadjoke() {
31-
if os.Getenv("EEOFF") != "" || filepath.Base(os.Args[0]) != "gitpod" {
32-
return
33-
}
34-
35-
args := strings.Fields(`run -it --rm git --image=index.docker.io/bitnami/git --command --restart=Never -- git`)
36-
args = append(args, os.Args[1:]...)
37-
cmd := exec.Command("kubectl", args...)
38-
cmd.Stdin = os.Stdin
39-
cmd.Stdout = os.Stdout
40-
cmd.Stderr = os.Stderr
41-
_ = cmd.Start()
42-
err := cmd.Wait()
43-
if exitErr, ok := err.(*exec.ExitError); ok {
44-
os.Exit(exitErr.ExitCode())
45-
}
46-
os.Exit(0)
47-
}

coderd/audit/diff_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ func TestDiff(t *testing.T) {
8888
ActiveVersionID: uuid.UUID{3},
8989
MaxTtl: int64(time.Hour),
9090
MinAutostartInterval: int64(time.Minute),
91+
CreatedBy: uuid.NullUUID{UUID: uuid.UUID{4}, Valid: true},
9192
},
9293
exp: audit.Map{
9394
"id": uuid.UUID{1}.String(),
@@ -97,6 +98,7 @@ func TestDiff(t *testing.T) {
9798
"active_version_id": uuid.UUID{3}.String(),
9899
"max_ttl": int64(3600000000000),
99100
"min_autostart_interval": int64(60000000000),
101+
"created_by": uuid.UUID{4}.String(),
100102
},
101103
},
102104
})

coderd/audit/table.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ var AuditableResources = auditMap(map[any]map[string]Action{
7272
"description": ActionTrack,
7373
"max_ttl": ActionTrack,
7474
"min_autostart_interval": ActionTrack,
75+
"created_by": ActionTrack,
7576
},
7677
&database.TemplateVersion{}: {
7778
"id": ActionTrack,

coderd/coderd.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ func New(options *Options) *API {
270270
r.Get("/", api.organizationsByUser)
271271
r.Get("/{organizationname}", api.organizationByUserAndName)
272272
})
273-
r.Get("/workspace/{workspacename}", api.workspaceByOwnerAndName)
273+
r.Route("/workspace/{workspacename}", func(r chi.Router) {
274+
r.Get("/", api.workspaceByOwnerAndName)
275+
r.Get("/builds/{buildnumber}", api.workspaceBuildByBuildNumber)
276+
})
274277
r.Get("/gitsshkey", api.gitSSHKey)
275278
r.Put("/gitsshkey", api.regenerateGitSSHKey)
276279
})

coderd/coderd_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"io"
66
"net/http"
7+
"strconv"
78
"strings"
89
"testing"
910
"time"
@@ -163,6 +164,10 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
163164
AssertObject: rbac.ResourceWorkspace,
164165
AssertAction: rbac.ActionRead,
165166
},
167+
"GET:/api/v2/users/me/workspace/{workspacename}/builds/{buildnumber}": {
168+
AssertObject: rbac.ResourceWorkspace,
169+
AssertAction: rbac.ActionRead,
170+
},
166171
"GET:/api/v2/workspaces/{workspace}/builds/{workspacebuildname}": {
167172
AssertAction: rbac.ActionRead,
168173
AssertObject: workspaceRBACObj,
@@ -388,6 +393,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
388393
route = strings.ReplaceAll(route, "{workspacename}", workspace.Name)
389394
route = strings.ReplaceAll(route, "{workspacebuildname}", workspace.LatestBuild.Name)
390395
route = strings.ReplaceAll(route, "{workspaceagent}", workspaceResources[0].Agents[0].ID.String())
396+
route = strings.ReplaceAll(route, "{buildnumber}", strconv.FormatInt(int64(workspace.LatestBuild.BuildNumber), 10))
391397
route = strings.ReplaceAll(route, "{template}", template.ID.String())
392398
route = strings.ReplaceAll(route, "{hash}", file.Hash)
393399
route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String())

coderd/database/databasefake/databasefake.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,22 @@ func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndName(_ context.Context, a
625625
return database.WorkspaceBuild{}, sql.ErrNoRows
626626
}
627627

628+
func (q *fakeQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(_ context.Context, arg database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams) (database.WorkspaceBuild, error) {
629+
q.mutex.RLock()
630+
defer q.mutex.RUnlock()
631+
632+
for _, workspaceBuild := range q.workspaceBuilds {
633+
if workspaceBuild.WorkspaceID.String() != arg.WorkspaceID.String() {
634+
continue
635+
}
636+
if workspaceBuild.BuildNumber != arg.BuildNumber {
637+
continue
638+
}
639+
return workspaceBuild, nil
640+
}
641+
return database.WorkspaceBuild{}, sql.ErrNoRows
642+
}
643+
628644
func (q *fakeQuerier) GetWorkspacesByOrganizationIDs(_ context.Context, req database.GetWorkspacesByOrganizationIDsParams) ([]database.Workspace, error) {
629645
q.mutex.RLock()
630646
defer q.mutex.RUnlock()
@@ -1325,6 +1341,7 @@ func (q *fakeQuerier) InsertTemplate(_ context.Context, arg database.InsertTempl
13251341
Description: arg.Description,
13261342
MaxTtl: arg.MaxTtl,
13271343
MinAutostartInterval: arg.MinAutostartInterval,
1344+
CreatedBy: arg.CreatedBy,
13281345
}
13291346
q.templates = append(q.templates, template)
13301347
return template, nil

coderd/database/dump.sql

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE ONLY templates DROP COLUMN IF EXISTS created_by;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE ONLY templates ADD COLUMN IF NOT EXISTS created_by uuid REFERENCES users (id) ON DELETE RESTRICT;

coderd/database/models.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/querier.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)