Skip to content

feat: archive template versions to hide them from the ui #10086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 40 commits into from
Closed
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
fc715fd
prune template versions
Emyrk Oct 5, 2023
611dac1
Implement fakes and mocks
Emyrk Oct 5, 2023
1de0a5d
Exclude template active versions
Emyrk Oct 5, 2023
3e5f91d
Delete versions are unusable
Emyrk Oct 5, 2023
9200779
Merge remote-tracking branch 'origin/main' into stevenmasley/soft_del…
Emyrk Oct 5, 2023
6e6fbc3
migration bump
Emyrk Oct 5, 2023
2e2c092
Spaces to tabs
Emyrk Oct 5, 2023
7f3d805
add template version prune command
Emyrk Oct 9, 2023
02aa085
Rename to archive
Emyrk Oct 9, 2023
9a416c3
Rename to "archive"
Emyrk Oct 9, 2023
c82ff9a
Merge remote-tracking branch 'origin/main' into stevenmasley/soft_del…
Emyrk Oct 9, 2023
c06d63a
Bump migration file
Emyrk Oct 9, 2023
460b0d2
Make update golden files
Emyrk Oct 9, 2023
d9bfea5
Fix swagger id
Emyrk Oct 9, 2023
655c906
Fix swagger accept json
Emyrk Oct 9, 2023
d4e54b2
Make gen
Emyrk Oct 9, 2023
938f256
Add unarchive
Emyrk Oct 9, 2023
7498c73
Add unarchive api
Emyrk Oct 9, 2023
685512a
Add cli command to unarchive a version
Emyrk Oct 9, 2023
f446ae5
Linting
Emyrk Oct 9, 2023
a9ff9d6
Update golden files
Emyrk Oct 9, 2023
b83a15e
Move cmd commands, allow archiuve deleted
Emyrk Oct 9, 2023
0ecbb79
update golden files
Emyrk Oct 9, 2023
bb3571b
Fix cli errors
Emyrk Oct 9, 2023
533e913
Move cmd
Emyrk Oct 9, 2023
02555b1
Implement fake
Emyrk Oct 9, 2023
1606b99
Linting and gen
Emyrk Oct 9, 2023
3818a57
fixup! Linting and gen
Emyrk Oct 9, 2023
48e44b6
fix: properly trim spaces so multi-line shebang executes (#10146)
kylecarbs Oct 9, 2023
3c97681
chore: reorganize storybook (#10144)
aslilac Oct 9, 2023
f01a4b3
chore: bump the golang-x group with 6 updates (#10128)
dependabot[bot] Oct 9, 2023
e9b4d15
chore: bump google.golang.org/api from 0.143.0 to 0.145.0 (#10130)
dependabot[bot] Oct 9, 2023
4236fc8
ci: bump the github-actions group with 2 updates (#10131)
dependabot[bot] Oct 9, 2023
8c86767
chore: add icons for popular programming languages (#10141)
aslilac Oct 9, 2023
02bcd20
chore: run `go mod tidy`
coadler Oct 9, 2023
91a2025
feat: add `external-auth` cli (#10052)
kylecarbs Oct 9, 2023
54509e5
feat: allow storing extra oauth token properties in the database (#10…
kylecarbs Oct 9, 2023
3fe8966
bump migration number:
Emyrk Oct 10, 2023
44bbbfe
Swagger annotations
Emyrk Oct 10, 2023
f0294c3
Merge remote-tracking branch 'origin/main' into stevenmasley/soft_del…
Emyrk Oct 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ jobs:

# Check for any typos
- name: Check for typos
uses: crate-ci/typos@v1.16.15
uses: crate-ci/typos@v1.16.17
with:
config: .github/workflows/typos.toml

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dogfood.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
echo "tag=${tag}" >> $GITHUB_OUTPUT

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v4
uses: DeterminateSystems/nix-installer-action@v5

- name: Run the Magic Nix Cache
uses: DeterminateSystems/magic-nix-cache-action@v2
Expand Down
7 changes: 5 additions & 2 deletions agent/agentssh/agentssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,16 @@ func (s *Server) CreateCommand(ctx context.Context, script string, env []string)
name := shell
args := []string{caller, script}

// A preceding space is generally not idiomatic for a shebang,
// but in Terraform it's quite standard to use <<EOF for a multi-line
// string which would indent with spaces, so we accept it for user-ease.
if strings.HasPrefix(strings.TrimSpace(script), "#!") {
// If the script starts with a shebang, we should
// execute it directly. This is useful for running
// scripts that aren't executable.
shebang := strings.SplitN(script, "\n", 2)[0]
shebang = strings.TrimPrefix(shebang, "#!")
shebang := strings.SplitN(strings.TrimSpace(script), "\n", 2)[0]
shebang = strings.TrimSpace(shebang)
shebang = strings.TrimPrefix(shebang, "#!")
words, err := shellquote.Split(shebang)
if err != nil {
return nil, xerrors.Errorf("split shebang: %w", err)
Expand Down
91 changes: 91 additions & 0 deletions cli/externalauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cli

import (
"os/signal"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk/agentsdk"
)

func (r *RootCmd) externalAuth() *clibase.Cmd {
return &clibase.Cmd{
Use: "external-auth",
Short: "Manage external authentication",
Long: "Authenticate with external services inside of a workspace.",
Handler: func(i *clibase.Invocation) error {
return i.Command.HelpHandler(i)
},
Children: []*clibase.Cmd{
r.externalAuthAccessToken(),
},
}
}

func (r *RootCmd) externalAuthAccessToken() *clibase.Cmd {
var silent bool
return &clibase.Cmd{
Use: "access-token <provider>",
Short: "Print auth for an external provider",
Long: "Print an access-token for an external auth provider. " +
"The access-token will be validated and sent to stdout with exit code 0. " +
"If a valid access-token cannot be obtained, the URL to authenticate will be sent to stdout with exit code 1\n" + formatExamples(
example{
Description: "Ensure that the user is authenticated with GitHub before cloning.",
Command: `#!/usr/bin/env sh

OUTPUT=$(coder external-auth access-token github)
if [ $? -eq 0 ]; then
echo "Authenticated with GitHub"
else
echo "Please authenticate with GitHub:"
echo $OUTPUT
fi
`,
},
),
Options: clibase.OptionSet{{
Name: "Silent",
Flag: "s",
Description: "Do not print the URL or access token.",
Value: clibase.BoolOf(&silent),
}},

Handler: func(inv *clibase.Invocation) error {
ctx := inv.Context()

ctx, stop := signal.NotifyContext(ctx, InterruptSignals...)
defer stop()

client, err := r.createAgentClient()
if err != nil {
return xerrors.Errorf("create agent client: %w", err)
}

token, err := client.ExternalAuth(ctx, agentsdk.ExternalAuthRequest{
ID: inv.Args[0],
})
if err != nil {
return xerrors.Errorf("get external auth token: %w", err)
}

if !silent {
if token.URL != "" {
_, err = inv.Stdout.Write([]byte(token.URL))
} else {
_, err = inv.Stdout.Write([]byte(token.AccessToken))
}
if err != nil {
return err
}
}

if token.URL != "" {
return cliui.Canceled
}
return nil
},
}
}
49 changes: 49 additions & 0 deletions cli/externalauth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cli_test

import (
"context"
"net/http"
"net/http/httptest"
"testing"

"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/coder/v2/pty/ptytest"
)

func TestExternalAuth(t *testing.T) {
t.Parallel()
t.Run("CanceledWithURL", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{
URL: "https://github.com",
})
}))
t.Cleanup(srv.Close)
url := srv.URL
inv, _ := clitest.New(t, "--agent-url", url, "external-auth", "access-token", "github")
pty := ptytest.New(t)
inv.Stdout = pty.Output()
waiter := clitest.StartWithWaiter(t, inv)
pty.ExpectMatch("https://github.com")
waiter.RequireIs(cliui.Canceled)
})
t.Run("SuccessWithToken", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{
AccessToken: "bananas",
})
}))
t.Cleanup(srv.Close)
url := srv.URL
inv, _ := clitest.New(t, "--agent-url", url, "external-auth", "access-token", "github")
pty := ptytest.New(t)
inv.Stdout = pty.Output()
clitest.Start(t, inv)
pty.ExpectMatch("bananas")
})
}
10 changes: 8 additions & 2 deletions cli/gitaskpass.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/gitauth"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/codersdk/agentsdk"
"github.com/coder/retry"
)

Expand All @@ -38,7 +39,9 @@ func (r *RootCmd) gitAskpass() *clibase.Cmd {
return xerrors.Errorf("create agent client: %w", err)
}

token, err := client.GitAuth(ctx, host, false)
token, err := client.ExternalAuth(ctx, agentsdk.ExternalAuthRequest{
Match: host,
})
if err != nil {
var apiError *codersdk.Error
if errors.As(err, &apiError) && apiError.StatusCode() == http.StatusNotFound {
Expand All @@ -63,7 +66,10 @@ func (r *RootCmd) gitAskpass() *clibase.Cmd {
}

for r := retry.New(250*time.Millisecond, 10*time.Second); r.Wait(ctx); {
token, err = client.GitAuth(ctx, host, true)
token, err = client.ExternalAuth(ctx, agentsdk.ExternalAuthRequest{
Match: host,
Listen: true,
})
if err != nil {
continue
}
Expand Down
8 changes: 4 additions & 4 deletions cli/gitaskpass_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestGitAskpass(t *testing.T) {
t.Run("UsernameAndPassword", func(t *testing.T) {
t.Parallel()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.GitAuthResponse{
httpapi.Write(context.Background(), w, http.StatusOK, agentsdk.ExternalAuthResponse{
Username: "something",
Password: "bananas",
})
Expand Down Expand Up @@ -65,8 +65,8 @@ func TestGitAskpass(t *testing.T) {

t.Run("Poll", func(t *testing.T) {
t.Parallel()
resp := atomic.Pointer[agentsdk.GitAuthResponse]{}
resp.Store(&agentsdk.GitAuthResponse{
resp := atomic.Pointer[agentsdk.ExternalAuthResponse]{}
resp.Store(&agentsdk.ExternalAuthResponse{
URL: "https://something.org",
})
poll := make(chan struct{}, 10)
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestGitAskpass(t *testing.T) {
}()
<-poll
stderr.ExpectMatch("Open the following URL to authenticate")
resp.Store(&agentsdk.GitAuthResponse{
resp.Store(&agentsdk.ExternalAuthResponse{
Username: "username",
Password: "password",
})
Expand Down
1 change: 1 addition & 0 deletions cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ func (r *RootCmd) Core() []*clibase.Cmd {
// Please re-sort this list alphabetically if you change it!
return []*clibase.Cmd{
r.dotfiles(),
r.externalAuth(),
r.login(),
r.logout(),
r.netcheck(),
Expand Down
2 changes: 2 additions & 0 deletions cli/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,8 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder
provider.NoRefresh = b
case "SCOPES":
provider.Scopes = strings.Split(v.Value, " ")
case "EXTRA_TOKEN_KEYS":
provider.ExtraTokenKeys = strings.Split(v.Value, " ")
case "APP_INSTALL_URL":
provider.AppInstallURL = v.Value
case "APP_INSTALLATIONS_URL":
Expand Down
28 changes: 4 additions & 24 deletions cli/templatedelete.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,13 @@ func (r *RootCmd) templateDelete() *clibase.Cmd {
templates = append(templates, template)
}
} else {
allTemplates, err := client.TemplatesByOrganization(ctx, organization.ID)
template, err := selectTemplate(inv, client, organization)
if err != nil {
return xerrors.Errorf("get templates by organization: %w", err)
return err
}

if len(allTemplates) == 0 {
return xerrors.Errorf("no templates exist in the current organization %q", organization.Name)
}

opts := make([]string, 0, len(allTemplates))
for _, template := range allTemplates {
opts = append(opts, template.Name)
}

selection, err := cliui.Select(inv, cliui.SelectOptions{
Options: opts,
})
if err != nil {
return xerrors.Errorf("select template: %w", err)
}

for _, template := range allTemplates {
if template.Name == selection {
templates = append(templates, template)
templateNames = append(templateNames, template.Name)
}
}
templates = append(templates, template)
templateNames = append(templateNames, template.Name)
}

// Confirm deletion of the template.
Expand Down
37 changes: 35 additions & 2 deletions cli/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package cli
import (
"time"

"github.com/google/uuid"

"github.com/coder/pretty"
"github.com/google/uuid"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui"
Expand Down Expand Up @@ -43,12 +43,45 @@ func (r *RootCmd) templates() *clibase.Cmd {
r.templateVersions(),
r.templateDelete(),
r.templatePull(),
r.archiveTemplateVersions(),
},
}

return cmd
}

func selectTemplate(inv *clibase.Invocation, client *codersdk.Client, organization codersdk.Organization) (codersdk.Template, error) {
var empty codersdk.Template
ctx := inv.Context()
allTemplates, err := client.TemplatesByOrganization(ctx, organization.ID)
if err != nil {
return empty, xerrors.Errorf("get templates by organization: %w", err)
}

if len(allTemplates) == 0 {
return empty, xerrors.Errorf("no templates exist in the current organization %q", organization.Name)
}

opts := make([]string, 0, len(allTemplates))
for _, template := range allTemplates {
opts = append(opts, template.Name)
}

selection, err := cliui.Select(inv, cliui.SelectOptions{
Options: opts,
})
if err != nil {
return empty, xerrors.Errorf("select template: %w", err)
}

for _, template := range allTemplates {
if template.Name == selection {
return template, nil
}
}
return empty, xerrors.Errorf("no template selected")
}

type templateTableRow struct {
// Used by json format:
Template codersdk.Template
Expand Down
Loading