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

Commitefdcbba

Browse files
committed
feat(cli): add provisioner job cancel command
1 parent84081e9 commitefdcbba

18 files changed

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

‎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