8000 chore: cherry-pick commits for 2.18.1 by stirby · Pull Request #15885 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

chore: cherry-pick commits for 2.18.1 #15885

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

Merged
merged 9 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
feat(coderd): add matched provisioner daemons information to more pla…
…ces (#15688)

- Refactors `checkProvisioners` into `db2sdk.MatchedProvisioners`
- Adds a separate RBAC subject just for reading provisioner daemons
- Adds matched provisioners information to additional endpoints relating to
  workspace builds and templates
-Updates existing unit tests for above endpoints
-Adds API endpoint for matched provisioners of template dry-run job
-Updates CLI to show warning when creating/starting/stopping/deleting
 workspaces for which no provisoners are available

---------

Co-authored-by: Danny Kopping <danny@coder.com>
(cherry picked from commit 2b57dcc)
  • Loading branch information
johnstcn and dannykopping committed Dec 12, 2024
commit 4578e6b265bb163458554bfd4b258658515e505d
53 changes: 53 additions & 0 deletions cli/cliutil/provisionerwarn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cliutil

import (
"encoding/json"
"fmt"
"io"
"strings"

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

var (
warnNoMatchedProvisioners = `Your build has been enqueued, but there are no provisioners that accept the required tags. Once a compatible provisioner becomes available, your build will continue. Please contact your administrator.
Details:
Provisioner job ID : %s
Requested tags : %s
`
warnNoAvailableProvisioners = `Provisioners that accept the required tags have not responded for longer than expected. This may delay your build. Please contact your administrator if your build does not complete.
Details:
Provisioner job ID : %s
Requested tags : %s
Most recently seen : %s
`
)

// WarnMatchedProvisioners warns the user if there are no provisioners that
// match the requested tags for a given provisioner job.
// If the job is not pending, it is ignored.
func WarnMatchedProvisioners(w io.Writer, mp *codersdk.MatchedProvisioners, job codersdk.ProvisionerJob) {
if mp == nil {
// Nothing in the response, nothing to do here!
return
}
if job.Status != codersdk.ProvisionerJobPending {
// Only warn if the job is pending.
return
}
var tagsJSON strings.Builder
if err := json.NewEncoder(&tagsJSON).Encode(job.Tags); err != nil {
// Fall back to the less-pretty string representation.
tagsJSON.Reset()
_, _ = tagsJSON.WriteString(fmt.Sprintf("%v", job.Tags))
}
if mp.Count == 0 {
cliui.Warnf(w, warnNoMatchedProvisioners, job.ID, tagsJSON.String())
return
}
if mp.Available == 0 {
cliui.Warnf(w, warnNoAvailableProvisioners, job.ID, strings.TrimSpace(tagsJSON.String()), mp.MostRecentlySeen.Time)
return
}
}
74 changes: 74 additions & 0 deletions cli/cliutil/provisionerwarn_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package cliutil_test

import (
"strings"
"testing"

"github.com/stretchr/testify/require"

"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
)

func TestWarnMatchedProvisioners(t *testing.T) {
t.Parallel()

for _, tt := range []struct {
name string
mp *codersdk.MatchedProvisioners
job codersdk.ProvisionerJob
expect string
}{
{
name: "no_match",
mp: &codersdk.MatchedProvisioners{
Count: 0,
Available: 0,
},
job: codersdk.ProvisionerJob{
Status: codersdk.ProvisionerJobPending,
},
expect: `there are no provisioners that accept the required tags`,
},
{
name: "no_available",
mp: &codersdk.MatchedProvisioners{
Count: 1,
Available: 0,
},
job: codersdk.ProvisionerJob{
Status: codersdk.ProvisionerJobPending,
},
expect: `Provisioners that accept the required tags have not responded for longer than expected`,
},
{
name: "match",
mp: &codersdk.MatchedProvisioners{
Count: 1,
Available: 1,
},
job: codersdk.ProvisionerJob{
Status: codersdk.ProvisionerJobPending,
},
},
{
name: "not_pending",
mp: &codersdk.MatchedProvisioners{},
job: codersdk.ProvisionerJob{
Status: codersdk.ProvisionerJobRunning,
},
},
} {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
var w strings.Builder
cliutil.WarnMatchedProvisioners(&w, tt.mp, tt.job)
if tt.expect != "" {
require.Contains(t, w.String(), tt.expect)
} else {
require.Empty(t, w.String())
}
})
}
}
11 changes: 10 additions & 1 deletion cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/coder/pretty"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
Expand Down Expand Up @@ -289,7 +290,7 @@ func (r *RootCmd) create() *serpent.Command {
ttlMillis = ptr.Ref(stopAfter.Milliseconds())
}

workspace, err := client.CreateWorkspace(inv.Context(), template.OrganizationID, workspaceOwner, codersdk.CreateWorkspaceRequest{
workspace, err := client.CreateUserWorkspace(inv.Context(), workspaceOwner, codersdk.CreateWorkspaceRequest{
TemplateVersionID: templateVersionID,
Name: workspaceName,
AutostartSchedule: schedSpec,
Expand All @@ -301,6 +302,8 @@ func (r *RootCmd) create() *serpent.Command {
return xerrors.Errorf("create workspace: %w", err)
}

cliutil.WarnMatchedProvisioners(inv.Stderr, workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)

err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, workspace.LatestBuild.ID)
if err != nil {
return xerrors.Errorf("watch build: %w", err)
Expand Down Expand Up @@ -433,6 +436,12 @@ func prepWorkspaceBuild(inv *serpent.Invocation, client *codersdk.Client, args p
if err != nil {
return nil, xerrors.Errorf("begin workspace dry-run: %w", err)
}

matchedProvisioners, err := client.TemplateVersionDryRunMatchedProvisioners(inv.Context(), templateVersion.ID, dryRun.ID)
if err != nil {
return nil, xerrors.Errorf("get matched provisioners: %w", err)
}
cliutil.WarnMatchedProvisioners(inv.Stdout, &matchedProvisioners, dryRun)
_, _ = fmt.Fprintln(inv.Stdout, "Planning workspace...")
err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
Expand Down
2 changes: 2 additions & 0 deletions cli/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
Expand Down Expand Up @@ -55,6 +56,7 @@ func (r *RootCmd) deleteWorkspace() *serpent.Command {
if err != nil {
return err
}
cliutil.WarnMatchedProvisioners(inv.Stdout, build.MatchedProvisioners, build.Job)

err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, build.ID)
if err != nil {
Expand Down
44 changes: 44 additions & 0 deletions cli/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"github.com/coder/coder/v2/cli/clitest"
"github.com/coder/coder/v2/coderd/coderdtest"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/pty/ptytest"
"github.com/coder/coder/v2/testutil"
Expand Down Expand Up @@ -164,4 +166,46 @@ func TestDelete(t *testing.T) {
}()
<-doneChan
})

t.Run("WarnNoProvisioners", func(t *testing.T) {
t.Parallel()
if !dbtestutil.WillUsePostgres() {
t.Skip("this test requires postgres")
}

store, ps, db := dbtestutil.NewDBWithSQLDB(t)
client, closeDaemon := coderdtest.NewWithProvisionerCloser(t, &coderdtest.Options{
Database: store,
Pubsub: ps,
IncludeProvisionerDaemon: true,
})

// Given: a user, template, and workspace
user := coderdtest.CreateFirstUser(t, client)
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleTemplateAdmin())
version := coderdtest.CreateTemplateVersion(t, templateAdmin, user.OrganizationID, nil)
template := coderdtest.CreateTemplate(t, templateAdmin, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, templateAdmin, template.ID)
coderdtest.AwaitWorkspaceBuildJobCompleted(t, templateAdmin, workspace.LatestBuild.ID)

// When: all provisioner daemons disappear
require.NoError(t, closeDaemon.Close())
_, err := db.Exec("DELETE FROM provisioner_daemons;")
require.NoError(t, err)

// Then: the workspace deletion should warn about no provisioners
inv, root := clitest.New(t, "delete", workspace.Name, "-y")
pty := ptytest.New(t).Attach(inv)
clitest.SetupConfig(t, templateAdmin, root)
doneChan := make(chan struct{})
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
defer cancel()
go func() {
defer close(doneChan)
_ = inv.WithContext(ctx).Run()
}()
pty.ExpectMatch("there are no provisioners that accept the required tags")
cancel()
<-doneChan
})
}
19 changes: 19 additions & 0 deletions cli/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
Expand Down Expand Up @@ -35,6 +36,23 @@ func (r *RootCmd) start() *serpent.Command {
}
var build codersdk.WorkspaceBuild
switch workspace.LatestBuild.Status {
case codersdk.WorkspaceStatusPending:
// The above check is technically duplicated in cliutil.WarnmatchedProvisioners
// but we still want to avoid users spamming multiple builds that will
// not be picked up.
_, _ = fmt.Fprintf(
inv.Stdout,
"\nThe %s workspace is waiting to start!\n",
cliui.Keyword(workspace.Name),
)
cliutil.WarnMatchedProvisioners(inv.Stderr, workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)
if _, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Enqueue another start?",
IsConfirm: true,
Default: cliui.ConfirmNo,
}); err != nil {
return err
}
case codersdk.WorkspaceStatusRunning:
_, _ = fmt.Fprintf(
inv.Stdout, "\nThe %s workspace is already running!\n",
Expand Down Expand Up @@ -159,6 +177,7 @@ func startWorkspace(inv *serpent.Invocation, client *codersdk.Client, workspace
if err != nil {
return codersdk.WorkspaceBuild{}, xerrors.Errorf("create workspace build: %w", err)
}
cliutil.WarnMatchedProvisioners(inv.Stderr, build.MatchedProvisioners, build.Job)

return build, nil
}
17 changes: 17 additions & 0 deletions cli/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
Expand Down Expand Up @@ -36,6 +37,21 @@ func (r *RootCmd) stop() *serpent.Command {
if err != nil {
return err
}
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobPending {
// cliutil.WarnMatchedProvisioners also checks if the job is pending
// but we still want to avoid users spamming multiple builds that will
// not be picked up.
cliui.Warn(inv.Stderr, "The workspace is already stopping!")
cliutil.WarnMatchedProvisioners(inv.Stderr F987 , workspace.LatestBuild.MatchedProvisioners, workspace.LatestBuild.Job)
if _, err := cliui.Prompt(inv, cliui.PromptOptions{
Text: "Enqueue another stop?",
IsConfirm: true,
Default: cliui.ConfirmNo,
}); err != nil {
return err
}
}

wbr := codersdk.CreateWorkspaceBuildRequest{
Transition: codersdk.WorkspaceTransitionStop,
}
Expand All @@ -46,6 +62,7 @@ func (r *RootCmd) stop() *serpent.Command {
if err != nil {
return err
}
cliutil.WarnMatchedProvisioners(inv.Stderr, build.MatchedProvisioners, build.Job)

err = cliui.WorkspaceBuild(inv.Context(), inv.Stdout, client, build.ID)
if err != nil {
Expand Down
39 changes: 2 additions & 37 deletions cli/templatepush.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cli

import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -17,6 +16,7 @@ import (
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/cli/cliutil"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisionersdk"
"github.com/coder/pretty"
Expand Down Expand Up @@ -416,7 +416,7 @@ func createValidTemplateVersion(inv *serpent.Invocation, args createValidTemplat
if err != nil {
return nil, err
}
WarnMatchedProvisioners(inv, version)
cliutil.WarnMatchedProvisioners(inv.Stderr, version.MatchedProvisioners, version.Job)
err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
Fetch: func() (codersdk.ProvisionerJob, error) {
version, err := client.TemplateVersion(inv.Context(), version.ID)
Expand Down Expand Up @@ -482,41 +482,6 @@ func ParseProvisionerTags(rawTags []string) (map[string]string, error) {
return tags, nil
}

var (
warnNoMatchedProvisioners = `Your build has been enqueued, but there are no provisioners that accept the required tags. Once a compatible provisioner becomes available, your build will continue. Please contact your administrator.
Details:
Provisioner job ID : %s
Requested tags : %s
`
warnNoAvailableProvisioners = `Provisioners that accept the required tags have not responded for longer than expected. This may delay your build. Please contact your administrator if your build does not complete.
Details:
Provisioner job ID : %s
Requested tags : %s
Most recently seen : %s
`
)

func WarnMatchedProvisioners(inv *serpent.Invocation, tv codersdk.TemplateVersion) {
if tv.MatchedProvisioners == nil {
// Nothing in the response, nothing to do here!
return
}
var tagsJSON strings.Builder
if err := json.NewEncoder(&tagsJSON).Encode(tv.Job.Tags); err != nil {
/ 6F13 / Fall back to the less-pretty string representation.
tagsJSON.Reset()
_, _ = tagsJSON.WriteString(fmt.Sprintf("%v", tv.Job.Tags))
}
if tv.MatchedProvisioners.Count == 0 {
cliui.Warnf(inv.Stderr, warnNoMatchedProvisioners, tv.Job.ID, tagsJSON.String())
return
}
if tv.MatchedProvisioners.Available == 0 {
cliui.Warnf(inv.Stderr, warnNoAvailableProvisioners, tv.Job.ID, strings.TrimSpace(tagsJSON.String()), tv.MatchedProvisioners.MostRecentlySeen.Time)
return
}
}

// prettyDirectoryPath returns a prettified path when inside the users
// home directory. Falls back to dir if the users home directory cannot
// discerned. This function calls filepath.Clean on the result.
Expand Down
Loading
0