Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit75c899f

Browse files
authored
feat(cli): add provisioner job cancel command (#16252)
Fixes#16117Updates#15084
1 parent84a54c1 commit75c899f

19 files changed

+568
-21
lines changed

‎cli/provisionerjobs.go‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"fmt"
55
"slices"
66

7+
"github.com/google/uuid"
78
"golang.org/x/xerrors"
89

910
"github.com/coder/coder/v2/cli/cliui"
11+
"github.com/coder/coder/v2/coderd/util/ptr"
1012
"github.com/coder/coder/v2/coderd/util/slice"
1113
"github.com/coder/coder/v2/codersdk"
1214
"github.com/coder/serpent"
@@ -21,6 +23,7 @@ func (r *RootCmd) provisionerJobs() *serpent.Command {
2123
},
2224
Aliases: []string{"job"},
2325
Children: []*serpent.Command{
26+
r.provisionerJobsCancel(),
2427
r.provisionerJobsList(),
2528
},
2629
}
@@ -124,3 +127,58 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command {
124127

125128
returncmd
126129
}
130+
131+
func (r*RootCmd)provisionerJobsCancel()*serpent.Command {
132+
var (
133+
client=new(codersdk.Client)
134+
orgContext=NewOrganizationContext()
135+
)
136+
cmd:=&serpent.Command{
137+
Use:"cancel <job_id>",
138+
Short:"Cancel a provisioner job",
139+
Middleware:serpent.Chain(
140+
serpent.RequireNArgs(1),
141+
r.InitClient(client),
142+
),
143+
Handler:func(inv*serpent.Invocation)error {
144+
ctx:=inv.Context()
145+
org,err:=orgContext.Selected(inv,client)
146+
iferr!=nil {
147+
returnxerrors.Errorf("current organization: %w",err)
148+
}
149+
150+
jobID,err:=uuid.Parse(inv.Args[0])
151+
iferr!=nil {
152+
returnxerrors.Errorf("invalid job ID: %w",err)
153+
}
154+
155+
job,err:=client.OrganizationProvisionerJob(ctx,org.ID,jobID)
156+
iferr!=nil {
157+
returnxerrors.Errorf("get provisioner job: %w",err)
158+
}
159+
160+
switchjob.Type {
161+
casecodersdk.ProvisionerJobTypeTemplateVersionDryRun:
162+
_,_=fmt.Fprintf(inv.Stdout,"Canceling template version dry run job %s...\n",job.ID)
163+
err=client.CancelTemplateVersionDryRun(ctx,ptr.NilToEmpty(job.Input.TemplateVersionID),job.ID)
164+
casecodersdk.ProvisionerJobTypeTemplateVersionImport:
165+
_,_=fmt.Fprintf(inv.Stdout,"Canceling template version import job %s...\n",job.ID)
166+
err=client.CancelTemplateVersion(ctx,ptr.NilToEmpty(job.Input.TemplateVersionID))
167+
casecodersdk.ProvisionerJobTypeWorkspaceBuild:
168+
_,_=fmt.Fprintf(inv.Stdout,"Canceling workspace build job %s...\n",job.ID)
169+
err=client.CancelWorkspaceBuild(ctx,ptr.NilToEmpty(job.Input.WorkspaceBuildID))
170+
}
171+
iferr!=nil {
172+
returnxerrors.Errorf("cancel provisioner job: %w",err)
173+
}
174+
175+
_,_=fmt.Fprintln(inv.Stdout,"Job canceled")
176+
177+
returnnil
178+
},
179+
}
180+
181+
orgContext.AttachOptions(cmd)
182+
183+
returncmd
184+
}

‎cli/provisionerjobs_test.go‎

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package cli_test
2+
3+
import (
4+
"bytes"
5+
"database/sql"
6+
"encoding/json"
7+
"fmt"
8+
"testing"
9+
"time"
10+
11+
"github.com/aws/smithy-go/ptr"
12+
"github.com/google/uuid"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/coder/coder/v2/cli/clitest"
17+
"github.com/coder/coder/v2/coderd/coderdtest"
18+
"github.com/coder/coder/v2/coderd/database"
19+
"github.com/coder/coder/v2/coderd/database/dbgen"
20+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
21+
"github.com/coder/coder/v2/coderd/rbac"
22+
"github.com/coder/coder/v2/codersdk"
23+
"github.com/coder/coder/v2/testutil"
24+
)
25+
26+
funcTestProvisionerJobs(t*testing.T) {
27+
t.Parallel()
28+
29+
db,ps:=dbtestutil.NewDB(t)
30+
client,_,coderdAPI:=coderdtest.NewWithAPI(t,&coderdtest.Options{
31+
IncludeProvisionerDaemon:false,
32+
Database:db,
33+
Pubsub:ps,
34+
})
35+
owner:=coderdtest.CreateFirstUser(t,client)
36+
templateAdminClient,templateAdmin:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID,rbac.ScopedRoleOrgTemplateAdmin(owner.OrganizationID))
37+
memberClient,member:=coderdtest.CreateAnotherUser(t,client,owner.OrganizationID)
38+
39+
// Create initial resources with a running provisioner.
40+
firstProvisioner:=coderdtest.NewTaggedProvisionerDaemon(t,coderdAPI,"default-provisioner",map[string]string{"owner":"","scope":"organization"})
41+
t.Cleanup(func() {_=firstProvisioner.Close() })
42+
version:=coderdtest.CreateTemplateVersion(t,client,owner.OrganizationID,completeWithAgent())
43+
coderdtest.AwaitTemplateVersionJobCompleted(t,client,version.ID)
44+
template:=coderdtest.CreateTemplate(t,client,owner.OrganizationID,version.ID,func(req*codersdk.CreateTemplateRequest) {
45+
req.AllowUserCancelWorkspaceJobs=ptr.Bool(true)
46+
})
47+
48+
// Stop the provisioner so it doesn't grab any more jobs.
49+
firstProvisioner.Close()
50+
51+
t.Run("Cancel",func(t*testing.T) {
52+
t.Parallel()
53+
54+
// Set up test helpers.
55+
typejobInputstruct {
56+
WorkspaceBuildIDstring`json:"workspace_build_id,omitempty"`
57+
TemplateVersionIDstring`json:"template_version_id,omitempty"`
58+
DryRunbool`json:"dry_run,omitempty"`
59+
}
60+
prepareJob:=func(t*testing.T,inputjobInput) database.ProvisionerJob {
61+
t.Helper()
62+
63+
inputBytes,err:=json.Marshal(input)
64+
require.NoError(t,err)
65+
66+
vartyp database.ProvisionerJobType
67+
switch {
68+
caseinput.WorkspaceBuildID!="":
69+
typ=database.ProvisionerJobTypeWorkspaceBuild
70+
caseinput.TemplateVersionID!="":
71+
ifinput.DryRun {
72+
typ=database.ProvisionerJobTypeTemplateVersionDryRun
73+
}else {
74+
typ=database.ProvisionerJobTypeTemplateVersionImport
75+
}
76+
default:
77+
t.Fatal("invalid input")
78+
}
79+
80+
var (
81+
tags= database.StringMap{"owner":"","scope":"organization","foo":uuid.New().String()}
82+
_=dbgen.ProvisionerDaemon(t,db, database.ProvisionerDaemon{Tags:tags})
83+
job=dbgen.ProvisionerJob(t,db,coderdAPI.Pubsub, database.ProvisionerJob{
84+
InitiatorID:member.ID,
85+
Input:json.RawMessage(inputBytes),
86+
Type:typ,
87+
Tags:tags,
88+
StartedAt: sql.NullTime{Time:coderdAPI.Clock.Now().Add(-time.Minute),Valid:true},
89+
})
90+
)
91+
returnjob
92+
}
93+
94+
prepareWorkspaceBuildJob:=func(t*testing.T) database.ProvisionerJob {
95+
t.Helper()
96+
var (
97+
wbID=uuid.New()
98+
job=prepareJob(t,jobInput{WorkspaceBuildID:wbID.String()})
99+
w=dbgen.Workspace(t,db, database.WorkspaceTable{
100+
OrganizationID:owner.OrganizationID,
101+
OwnerID:member.ID,
102+
TemplateID:template.ID,
103+
})
104+
_=dbgen.WorkspaceBuild(t,db, database.WorkspaceBuild{
105+
ID:wbID,
106+
InitiatorID:member.ID,
107+
WorkspaceID:w.ID,
108+
TemplateVersionID:version.ID,
109+
JobID:job.ID,
110+
})
111+
)
112+
returnjob
113+
}
114+
115+
prepareTemplateVersionImportJobBuilder:=func(t*testing.T,dryRunbool) database.ProvisionerJob {
116+
t.Helper()
117+
var (
118+
tvID=uuid.New()
119+
job=prepareJob(t,jobInput{TemplateVersionID:tvID.String(),DryRun:dryRun})
120+
_=dbgen.TemplateVersion(t,db, database.TemplateVersion{
121+
OrganizationID:owner.OrganizationID,
122+
CreatedBy:templateAdmin.ID,
123+
ID:tvID,
124+
TemplateID: uuid.NullUUID{UUID:template.ID,Valid:true},
125+
JobID:job.ID,
126+
})
127+
)
128+
returnjob
129+
}
130+
prepareTemplateVersionImportJob:=func(t*testing.T) database.ProvisionerJob {
131+
returnprepareTemplateVersionImportJobBuilder(t,false)
132+
}
133+
prepareTemplateVersionImportJobDryRun:=func(t*testing.T) database.ProvisionerJob {
134+
returnprepareTemplateVersionImportJobBuilder(t,true)
135+
}
136+
137+
// Run the cancellation test suite.
138+
for_,tt:=range []struct {
139+
rolestring
140+
client*codersdk.Client
141+
namestring
142+
preparefunc(*testing.T) database.ProvisionerJob
143+
wantCancelledbool
144+
}{
145+
{"Owner",client,"WorkspaceBuild",prepareWorkspaceBuildJob,true},
146+
{"Owner",client,"TemplateVersionImport",prepareTemplateVersionImportJob,true},
147+
{"Owner",client,"TemplateVersionImportDryRun",prepareTemplateVersionImportJobDryRun,true},
148+
{"TemplateAdmin",templateAdminClient,"WorkspaceBuild",prepareWorkspaceBuildJob,false},
149+
{"TemplateAdmin",templateAdminClient,"TemplateVersionImport",prepareTemplateVersionImportJob,true},
150+
{"TemplateAdmin",templateAdminClient,"TemplateVersionImportDryRun",prepareTemplateVersionImportJobDryRun,false},
151+
{"Member",memberClient,"WorkspaceBuild",prepareWorkspaceBuildJob,false},
152+
{"Member",memberClient,"TemplateVersionImport",prepareTemplateVersionImportJob,false},
153+
{"Member",memberClient,"TemplateVersionImportDryRun",prepareTemplateVersionImportJobDryRun,false},
154+
} {
155+
tt:=tt
156+
wantMsg:="OK"
157+
if!tt.wantCancelled {
158+
wantMsg="FAIL"
159+
}
160+
t.Run(fmt.Sprintf("%s/%s/%v",tt.role,tt.name,wantMsg),func(t*testing.T) {
161+
t.Parallel()
162+
163+
job:=tt.prepare(t)
164+
require.False(t,job.CanceledAt.Valid,"job.CanceledAt.Valid")
165+
166+
inv,root:=clitest.New(t,"provisioner","jobs","cancel",job.ID.String())
167+
clitest.SetupConfig(t,tt.client,root)
168+
varbuf bytes.Buffer
169+
inv.Stdout=&buf
170+
err:=inv.Run()
171+
iftt.wantCancelled {
172+
assert.NoError(t,err)
173+
}else {
174+
assert.Error(t,err)
175+
}
176+
177+
job,err=db.GetProvisionerJobByID(testutil.Context(t,testutil.WaitShort),job.ID)
178+
require.NoError(t,err)
179+
assert.Equal(t,tt.wantCancelled,job.CanceledAt.Valid,"job.CanceledAt.Valid")
180+
assert.Equal(t,tt.wantCancelled,job.CanceledAt.Time.After(job.StartedAt.Time),"job.CanceledAt.Time")
181+
iftt.wantCancelled {
182+
assert.Contains(t,buf.String(),"Job canceled")
183+
}else {
184+
assert.NotContains(t,buf.String(),"Job canceled")
185+
}
186+
})
187+
}
188+
})
189+
}

‎cli/testdata/coder_provisioner_jobs_--help.golden‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ USAGE:
88
Aliases: job
99

1010
SUBCOMMANDS:
11-
list List provisioner jobs
11+
cancel Cancel a provisioner job
12+
list List provisioner jobs
1213

1314
———
1415
Run `coder --help` for a list of global options.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
coder v0.0.0-devel
2+
3+
USAGE:
4+
coder provisioner jobs cancel [flags] <job_id>
5+
6+
Cancel a provisioner job
7+
8+
OPTIONS:
9+
-O, --org string, $CODER_ORGANIZATION
10+
Select which organization (uuid or name) to use.
11+
12+
———
13+
Run `coder --help` for a list of global options.

‎coderd/apidoc/docs.go‎

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/apidoc/swagger.json‎

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/coderd.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,6 +1011,7 @@ func New(options *Options) *API {
10111011
r.Get("/",api.provisionerDaemons)
10121012
})
10131013
r.Route("/provisionerjobs",func(r chi.Router) {
1014+
r.Get("/{job}",api.provisionerJob)
10141015
r.Get("/",api.provisionerJobs)
10151016
})
10161017
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp