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

Commit8cfe223

Browse files
authored
feat: cli: allow editing template metadata (#2159)
This PR adds a CLI command template edit which allows updating the following metadata fields of a template:- Description- Max TTL- Min Autostart Interval
1 parentb65259f commit8cfe223

File tree

12 files changed

+447
-3
lines changed

12 files changed

+447
-3
lines changed

‎cli/templateedit.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"time"
6+
7+
"github.com/spf13/cobra"
8+
"golang.org/x/xerrors"
9+
10+
"github.com/coder/coder/cli/cliui"
11+
"github.com/coder/coder/codersdk"
12+
)
13+
14+
functemplateEdit()*cobra.Command {
15+
var (
16+
descriptionstring
17+
maxTTL time.Duration
18+
minAutostartInterval time.Duration
19+
)
20+
21+
cmd:=&cobra.Command{
22+
Use:"edit <template> [flags]",
23+
Args:cobra.ExactArgs(1),
24+
Short:"Edit the metadata of a template by name.",
25+
RunE:func(cmd*cobra.Command,args []string)error {
26+
client,err:=createClient(cmd)
27+
iferr!=nil {
28+
returnxerrors.Errorf("create client: %w",err)
29+
}
30+
organization,err:=currentOrganization(cmd,client)
31+
iferr!=nil {
32+
returnxerrors.Errorf("get current organization: %w",err)
33+
}
34+
template,err:=client.TemplateByName(cmd.Context(),organization.ID,args[0])
35+
iferr!=nil {
36+
returnxerrors.Errorf("get workspace template: %w",err)
37+
}
38+
39+
// NOTE: coderd will ignore empty fields.
40+
req:= codersdk.UpdateTemplateMeta{
41+
Description:description,
42+
MaxTTLMillis:maxTTL.Milliseconds(),
43+
MinAutostartIntervalMillis:minAutostartInterval.Milliseconds(),
44+
}
45+
46+
_,err=client.UpdateTemplateMeta(cmd.Context(),template.ID,req)
47+
iferr!=nil {
48+
returnxerrors.Errorf("update template metadata: %w",err)
49+
}
50+
_,_=fmt.Printf("Updated template metadata!\n")
51+
returnnil
52+
},
53+
}
54+
55+
cmd.Flags().StringVarP(&description,"description","","","Edit the template description")
56+
cmd.Flags().DurationVarP(&maxTTL,"max_ttl","",0,"Edit the template maximum time before shutdown")
57+
cmd.Flags().DurationVarP(&minAutostartInterval,"min_autostart_interval","",0,"Edit the template minimum autostart interval")
58+
cliui.AllowSkipPrompt(cmd)
59+
60+
returncmd
61+
}

‎cli/templateedit_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/coder/coder/cli/clitest"
12+
"github.com/coder/coder/coderd/coderdtest"
13+
"github.com/coder/coder/coderd/util/ptr"
14+
"github.com/coder/coder/codersdk"
15+
)
16+
17+
funcTestTemplateEdit(t*testing.T) {
18+
t.Parallel()
19+
20+
t.Run("Modified",func(t*testing.T) {
21+
t.Parallel()
22+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
23+
user:=coderdtest.CreateFirstUser(t,client)
24+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
25+
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
26+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
27+
ctr.Description="original description"
28+
ctr.MaxTTLMillis=ptr.Ref(24*time.Hour.Milliseconds())
29+
ctr.MinAutostartIntervalMillis=ptr.Ref(time.Hour.Milliseconds())
30+
})
31+
32+
// Test the cli command.
33+
desc:="lorem ipsum dolor sit amet et cetera"
34+
maxTTL:=12*time.Hour
35+
minAutostartInterval:=time.Minute
36+
cmdArgs:= []string{
37+
"templates",
38+
"edit",
39+
template.Name,
40+
"--description",desc,
41+
"--max_ttl",maxTTL.String(),
42+
"--min_autostart_interval",minAutostartInterval.String(),
43+
}
44+
cmd,root:=clitest.New(t,cmdArgs...)
45+
clitest.SetupConfig(t,client,root)
46+
47+
err:=cmd.Execute()
48+
49+
require.NoError(t,err)
50+
51+
// Assert that the template metadata changed.
52+
updated,err:=client.Template(context.Background(),template.ID)
53+
require.NoError(t,err)
54+
assert.Equal(t,desc,updated.Description)
55+
assert.Equal(t,maxTTL.Milliseconds(),updated.MaxTTLMillis)
56+
assert.Equal(t,minAutostartInterval.Milliseconds(),updated.MinAutostartIntervalMillis)
57+
})
58+
59+
t.Run("NotModified",func(t*testing.T) {
60+
t.Parallel()
61+
client:=coderdtest.New(t,&coderdtest.Options{IncludeProvisionerD:true})
62+
user:=coderdtest.CreateFirstUser(t,client)
63+
version:=coderdtest.CreateTemplateVersion(t,client,user.OrganizationID,nil)
64+
_=coderdtest.AwaitTemplateVersionJob(t,client,version.ID)
65+
template:=coderdtest.CreateTemplate(t,client,user.OrganizationID,version.ID,func(ctr*codersdk.CreateTemplateRequest) {
66+
ctr.Description="original description"
67+
ctr.MaxTTLMillis=ptr.Ref(24*time.Hour.Milliseconds())
68+
ctr.MinAutostartIntervalMillis=ptr.Ref(time.Hour.Milliseconds())
69+
})
70+
71+
// Test the cli command.
72+
cmdArgs:= []string{
73+
"templates",
74+
"edit",
75+
template.Name,
76+
"--description",template.Description,
77+
"--max_ttl", (time.Duration(template.MaxTTLMillis)*time.Millisecond).String(),
78+
"--min_autostart_interval", (time.Duration(template.MinAutostartIntervalMillis)*time.Millisecond).String(),
79+
}
80+
cmd,root:=clitest.New(t,cmdArgs...)
81+
clitest.SetupConfig(t,client,root)
82+
83+
err:=cmd.Execute()
84+
85+
require.ErrorContains(t,err,"not modified")
86+
87+
// Assert that the template metadata did not change.
88+
updated,err:=client.Template(context.Background(),template.ID)
89+
require.NoError(t,err)
90+
assert.Equal(t,template.Description,updated.Description)
91+
assert.Equal(t,template.MaxTTLMillis,updated.MaxTTLMillis)
92+
assert.Equal(t,template.MinAutostartIntervalMillis,updated.MinAutostartIntervalMillis)
93+
})
94+
}

‎cli/templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ func templates() *cobra.Command {
2626
}
2727
cmd.AddCommand(
2828
templateCreate(),
29+
templateEdit(),
2930
templateInit(),
3031
templateList(),
3132
templatePlan(),

‎coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ func New(options *Options) *API {
199199

200200
r.Get("/",api.template)
201201
r.Delete("/",api.deleteTemplate)
202+
r.Patch("/",api.patchTemplateMeta)
202203
r.Route("/versions",func(r chi.Router) {
203204
r.Get("/",api.templateVersionsByTemplate)
204205
r.Patch("/",api.patchActiveTemplateVersion)

‎coderd/database/databasefake/databasefake.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,25 @@ func (q *fakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg da
742742
return database.Template{},sql.ErrNoRows
743743
}
744744

745+
func (q*fakeQuerier)UpdateTemplateMetaByID(_ context.Context,arg database.UpdateTemplateMetaByIDParams)error {
746+
q.mutex.RLock()
747+
deferq.mutex.RUnlock()
748+
749+
foridx,tpl:=rangeq.templates {
750+
iftpl.ID!=arg.ID {
751+
continue
752+
}
753+
tpl.UpdatedAt=database.Now()
754+
tpl.Description=arg.Description
755+
tpl.MaxTtl=arg.MaxTtl
756+
tpl.MinAutostartInterval=arg.MinAutostartInterval
757+
q.templates[idx]=tpl
758+
returnnil
759+
}
760+
761+
returnsql.ErrNoRows
762+
}
763+
745764
func (q*fakeQuerier)GetTemplateVersionsByTemplateID(_ context.Context,arg database.GetTemplateVersionsByTemplateIDParams) (version []database.TemplateVersion,errerror) {
746765
q.mutex.RLock()
747766
deferq.mutex.RUnlock()

‎coderd/database/querier.go

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

‎coderd/database/queries.sql.go

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

‎coderd/database/queries/templates.sql

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,16 @@ SET
6969
deleted= $2
7070
WHERE
7171
id= $1;
72+
73+
-- name: UpdateTemplateMetaByID :exec
74+
UPDATE
75+
templates
76+
SET
77+
updated_at= $2,
78+
description= $3,
79+
max_ttl= $4,
80+
min_autostart_interval= $5
81+
WHERE
82+
id= $1
83+
RETURNING
84+
*;

‎coderd/templates.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,102 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
307307
httpapi.Write(rw,http.StatusOK,convertTemplate(template,count))
308308
}
309309

310+
func (api*API)patchTemplateMeta(rw http.ResponseWriter,r*http.Request) {
311+
template:=httpmw.TemplateParam(r)
312+
if!api.Authorize(rw,r,rbac.ActionUpdate,template) {
313+
return
314+
}
315+
316+
varreq codersdk.UpdateTemplateMeta
317+
if!httpapi.Read(rw,r,&req) {
318+
return
319+
}
320+
321+
varvalidErrs []httpapi.Error
322+
ifreq.MaxTTLMillis<0 {
323+
validErrs=append(validErrs, httpapi.Error{Field:"max_ttl_ms",Detail:"Must be a positive integer."})
324+
}
325+
ifreq.MinAutostartIntervalMillis<0 {
326+
validErrs=append(validErrs, httpapi.Error{Field:"min_autostart_interval_ms",Detail:"Must be a positive integer."})
327+
}
328+
329+
iflen(validErrs)>0 {
330+
httpapi.Write(rw,http.StatusBadRequest, httpapi.Response{
331+
Message:"Invalid request to update template metadata!",
332+
Validations:validErrs,
333+
})
334+
return
335+
}
336+
337+
count:=uint32(0)
338+
varupdated database.Template
339+
err:=api.Database.InTx(func(s database.Store)error {
340+
// Fetch workspace counts
341+
workspaceCounts,err:=s.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID})
342+
ifxerrors.Is(err,sql.ErrNoRows) {
343+
err=nil
344+
}
345+
iferr!=nil {
346+
returnerr
347+
}
348+
349+
iflen(workspaceCounts)>0 {
350+
count=uint32(workspaceCounts[0].Count)
351+
}
352+
353+
ifreq.Description==template.Description&&
354+
req.MaxTTLMillis==time.Duration(template.MaxTtl).Milliseconds()&&
355+
req.MinAutostartIntervalMillis==time.Duration(template.MinAutostartInterval).Milliseconds() {
356+
returnnil
357+
}
358+
359+
// Update template metadata -- empty fields are not overwritten.
360+
desc:=req.Description
361+
maxTTL:=time.Duration(req.MaxTTLMillis)*time.Millisecond
362+
minAutostartInterval:=time.Duration(req.MinAutostartIntervalMillis)*time.Millisecond
363+
364+
ifdesc=="" {
365+
desc=template.Description
366+
}
367+
ifmaxTTL==0 {
368+
maxTTL=time.Duration(template.MaxTtl)
369+
}
370+
ifminAutostartInterval==0 {
371+
minAutostartInterval=time.Duration(template.MinAutostartInterval)
372+
}
373+
374+
iferr:=s.UpdateTemplateMetaByID(r.Context(), database.UpdateTemplateMetaByIDParams{
375+
ID:template.ID,
376+
UpdatedAt:database.Now(),
377+
Description:desc,
378+
MaxTtl:int64(maxTTL),
379+
MinAutostartInterval:int64(minAutostartInterval),
380+
});err!=nil {
381+
returnerr
382+
}
383+
384+
updated,err=s.GetTemplateByID(r.Context(),template.ID)
385+
iferr!=nil {
386+
returnerr
387+
}
388+
returnnil
389+
})
390+
iferr!=nil {
391+
httpapi.Write(rw,http.StatusInternalServerError, httpapi.Response{
392+
Message:"Internal error updating template metadata.",
393+
Detail:err.Error(),
394+
})
395+
return
396+
}
397+
398+
ifupdated.UpdatedAt.IsZero() {
399+
httpapi.Write(rw,http.StatusNotModified,nil)
400+
return
401+
}
402+
403+
httpapi.Write(rw,http.StatusOK,convertTemplate(updated,count))
404+
}
405+
310406
funcconvertTemplates(templates []database.Template,workspaceCounts []database.GetWorkspaceOwnerCountsByTemplateIDsRow) []codersdk.Template {
311407
apiTemplates:=make([]codersdk.Template,0,len(templates))
312408
for_,template:=rangetemplates {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp