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 <AbhineetJain@users.noreply.github.com> Co-authored-by: Joe Previte <jjprevite@gmail.com> Co-authored-by: Mathias Fredriksson <mafredri@gmail.com> Co-authored-by: G r e y <grey@coder.com> Co-authored-by: Kyle Carberry <kyle@coder.com> Co-authored-by: David Wahler <david@coder.com> Co-authored-by: Colin Adler <colin1adler@gmail.com> Co-authored-by: Garrett Delfosse <garrett@coder.com> Co-authored-by: Katie Horne <katie@coder.com> Co-authored-by: Steven Masley <Emyrk@users.noreply.github.com>
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.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy