mirror of https://github.com/coder/coder.git
feat: add support for template version messages in api and cli (#8336)
This commit is contained in:
parent
b4a7fe3221
commit
75f62dc39d
|
@ -92,6 +92,8 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||
return xerrors.Errorf("check for lockfile: %w", err)
|
||||
}
|
||||
|
||||
message := uploadFlags.templateMessage(inv)
|
||||
|
||||
// Confirm upload of the directory.
|
||||
resp, err := uploadFlags.upload(inv, client)
|
||||
if err != nil {
|
||||
|
@ -104,6 +106,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||
}
|
||||
|
||||
job, err := createValidTemplateVersion(inv, createValidTemplateVersionArgs{
|
||||
Message: message,
|
||||
Client: client,
|
||||
Organization: organization,
|
||||
Provisioner: database.ProvisionerType(provisioner),
|
||||
|
@ -205,6 +208,7 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
|||
|
||||
type createValidTemplateVersionArgs struct {
|
||||
Name string
|
||||
Message string
|
||||
Client *codersdk.Client
|
||||
Organization codersdk.Organization
|
||||
Provisioner database.ProvisionerType
|
||||
|
@ -238,6 +242,7 @@ func createValidTemplateVersion(inv *clibase.Invocation, args createValidTemplat
|
|||
|
||||
req := codersdk.CreateTemplateVersionRequest{
|
||||
Name: args.Name,
|
||||
Message: args.Message,
|
||||
StorageMethod: codersdk.ProvisionerStorageMethodFile,
|
||||
FileID: args.FileID,
|
||||
Provisioner: codersdk.ProvisionerType(args.Provisioner),
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
type templateUploadFlags struct {
|
||||
directory string
|
||||
ignoreLockfile bool
|
||||
message string
|
||||
}
|
||||
|
||||
func (pf *templateUploadFlags) options() []clibase.Option {
|
||||
|
@ -35,6 +37,11 @@ func (pf *templateUploadFlags) options() []clibase.Option {
|
|||
Description: "Ignore warnings about not having a .terraform.lock.hcl file present in the template.",
|
||||
Default: "false",
|
||||
Value: clibase.BoolOf(&pf.ignoreLockfile),
|
||||
}, {
|
||||
Flag: "message",
|
||||
FlagShorthand: "m",
|
||||
Description: "Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.",
|
||||
Value: clibase.StringOf(&pf.message),
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -110,6 +117,20 @@ func (pf *templateUploadFlags) checkForLockfile(inv *clibase.Invocation) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (pf *templateUploadFlags) templateMessage(inv *clibase.Invocation) string {
|
||||
title := strings.SplitN(pf.message, "\n", 2)[0]
|
||||
if len(title) > 72 {
|
||||
cliui.Warn(inv.Stdout, "Template message is longer than 72 characters, it will be displayed as truncated.")
|
||||
}
|
||||
if title != pf.message {
|
||||
cliui.Warn(inv.Stdout, "Template message contains newlines, only the first line will be displayed.")
|
||||
}
|
||||
if pf.message != "" {
|
||||
return pf.message
|
||||
}
|
||||
return "Uploaded from the CLI"
|
||||
}
|
||||
|
||||
func (pf *templateUploadFlags) templateName(args []string) (string, error) {
|
||||
if pf.stdin() {
|
||||
// Can't infer name from directory if none provided.
|
||||
|
@ -174,6 +195,8 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
|
|||
return xerrors.Errorf("check for lockfile: %w", err)
|
||||
}
|
||||
|
||||
message := uploadFlags.templateMessage(inv)
|
||||
|
||||
resp, err := uploadFlags.upload(inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -186,6 +209,7 @@ func (r *RootCmd) templatePush() *clibase.Cmd {
|
|||
|
||||
job, err := createValidTemplateVersion(inv, createValidTemplateVersionArgs{
|
||||
Name: versionName,
|
||||
Message: message,
|
||||
Client: client,
|
||||
Organization: organization,
|
||||
Provisioner: database.ProvisionerType(provisioner),
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -70,6 +71,90 @@ func TestTemplatePush(t *testing.T) {
|
|||
require.Equal(t, "example", templateVersions[1].Name)
|
||||
})
|
||||
|
||||
t.Run("Message less than or equal to 72 chars", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
})
|
||||
|
||||
wantMessage := strings.Repeat("a", 72)
|
||||
|
||||
inv, root := clitest.New(t, "templates", "push", template.Name, "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--name", "example", "--message", wantMessage, "--yes")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||
defer cancel()
|
||||
|
||||
inv = inv.WithContext(ctx)
|
||||
w := clitest.StartWithWaiter(t, inv)
|
||||
|
||||
pty.ExpectNoMatchBefore(ctx, "Template message is longer than 72 characters", "Updated version at")
|
||||
|
||||
w.RequireSuccess()
|
||||
|
||||
// Assert that the template version changed.
|
||||
templateVersions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templateVersions, 2)
|
||||
assert.NotEqual(t, template.ActiveVersionID, templateVersions[1].ID)
|
||||
require.Equal(t, wantMessage, templateVersions[1].Message)
|
||||
})
|
||||
|
||||
t.Run("Message too long, warn but continue", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
|
||||
|
||||
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||
source := clitest.CreateTemplateVersionSource(t, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionApply: echo.ProvisionComplete,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
for i, tt := range []struct {
|
||||
wantMessage string
|
||||
wantMatch string
|
||||
}{
|
||||
{wantMessage: strings.Repeat("a", 73), wantMatch: "Template message is longer than 72 characters"},
|
||||
{wantMessage: "This is my title\n\nAnd this is my body.", wantMatch: "Template message contains newlines"},
|
||||
} {
|
||||
inv, root := clitest.New(t, "templates", "push", template.Name, "--directory", source, "--test.provisioner", string(database.ProvisionerTypeEcho), "--message", tt.wantMessage, "--yes")
|
||||
clitest.SetupConfig(t, client, root)
|
||||
pty := ptytest.New(t).Attach(inv)
|
||||
|
||||
inv = inv.WithContext(ctx)
|
||||
w := clitest.StartWithWaiter(t, inv)
|
||||
|
||||
pty.ExpectMatchContext(ctx, tt.wantMatch)
|
||||
|
||||
w.RequireSuccess()
|
||||
|
||||
// Assert that the template version changed.
|
||||
templateVersions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||
TemplateID: template.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, templateVersions, 2+i)
|
||||
assert.NotEqual(t, template.ActiveVersionID, templateVersions[1+i].ID)
|
||||
require.Equal(t, tt.wantMessage, templateVersions[1+i].Message)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NoLockfile", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||
|
|
|
@ -21,6 +21,11 @@ Create a template from the current directory or as specified by flag
|
|||
Specify an inactivity TTL for workspaces created from this template.
|
||||
This licensed feature's default is 0h (off).
|
||||
|
||||
-m, --message string
|
||||
Specify a message describing the changes in this version of the
|
||||
template. Messages longer than 72 characters will be displayed as
|
||||
truncated.
|
||||
|
||||
--private bool
|
||||
Disable the default behavior of granting template access to the
|
||||
'everyone' group. The template permissions must be updated to allow
|
||||
|
|
|
@ -17,6 +17,11 @@ Push a new template version from the current directory or as specified by flag
|
|||
Ignore warnings about not having a .terraform.lock.hcl file present in
|
||||
the template.
|
||||
|
||||
-m, --message string
|
||||
Specify a message describing the changes in this version of the
|
||||
template. Messages longer than 72 characters will be displayed as
|
||||
truncated.
|
||||
|
||||
--name string
|
||||
Specify a name for the new template version. It will be automatically
|
||||
generated if not provided.
|
||||
|
|
|
@ -6989,6 +6989,9 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -9081,6 +9084,9 @@ const docTemplate = `{
|
|||
"job": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerJob"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -6229,6 +6229,9 @@
|
|||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -8197,6 +8200,9 @@
|
|||
"job": {
|
||||
"$ref": "#/definitions/codersdk.ProvisionerJob"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
|
|
|
@ -3921,6 +3921,10 @@ func (q *fakeQuerier) InsertTemplateVersion(_ context.Context, arg database.Inse
|
|||
return database.TemplateVersion{}, err
|
||||
}
|
||||
|
||||
if len(arg.Message) > 1048576 {
|
||||
return database.TemplateVersion{}, xerrors.New("message too long")
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
|
@ -3932,6 +3936,7 @@ func (q *fakeQuerier) InsertTemplateVersion(_ context.Context, arg database.Inse
|
|||
CreatedAt: arg.CreatedAt,
|
||||
UpdatedAt: arg.UpdatedAt,
|
||||
Name: arg.Name,
|
||||
Message: arg.Message,
|
||||
Readme: arg.Readme,
|
||||
JobID: arg.JobID,
|
||||
CreatedBy: arg.CreatedBy,
|
||||
|
|
|
@ -477,6 +477,7 @@ func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVers
|
|||
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
|
||||
Name: takeFirst(orig.Name, namesgenerator.GetRandomName(1)),
|
||||
Message: orig.Message,
|
||||
Readme: takeFirst(orig.Readme, namesgenerator.GetRandomName(1)),
|
||||
JobID: takeFirst(orig.JobID, uuid.New()),
|
||||
CreatedBy: takeFirst(orig.CreatedBy, uuid.New()),
|
||||
|
|
|
@ -536,11 +536,14 @@ CREATE TABLE template_versions (
|
|||
readme character varying(1048576) NOT NULL,
|
||||
job_id uuid NOT NULL,
|
||||
created_by uuid NOT NULL,
|
||||
git_auth_providers text[]
|
||||
git_auth_providers text[],
|
||||
message character varying(1048576) DEFAULT ''::character varying NOT NULL
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN template_versions.git_auth_providers IS 'IDs of Git auth providers for a specific template version';
|
||||
|
||||
COMMENT ON COLUMN template_versions.message IS 'Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact.';
|
||||
|
||||
CREATE TABLE templates (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE template_versions DROP COLUMN message;
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE template_versions ADD COLUMN message varchar(1048576) NOT NULL DEFAULT '';
|
||||
|
||||
COMMENT ON COLUMN template_versions.message IS 'Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact.';
|
|
@ -1609,6 +1609,8 @@ type TemplateVersion struct {
|
|||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
// IDs of Git auth providers for a specific template version
|
||||
GitAuthProviders []string `db:"git_auth_providers" json:"git_auth_providers"`
|
||||
// Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact.
|
||||
Message string `db:"message" json:"message"`
|
||||
}
|
||||
|
||||
type TemplateVersionParameter struct {
|
||||
|
|
|
@ -4326,7 +4326,7 @@ func (q *sqlQuerier) InsertTemplateVersionParameter(ctx context.Context, arg Ins
|
|||
|
||||
const getPreviousTemplateVersion = `-- name: GetPreviousTemplateVersion :one
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4361,13 +4361,14 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplateVersionByID = `-- name: GetTemplateVersionByID :one
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4388,13 +4389,14 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) (
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplateVersionByJobID = `-- name: GetTemplateVersionByJobID :one
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4415,13 +4417,14 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplateVersionByTemplateIDAndName = `-- name: GetTemplateVersionByTemplateIDAndName :one
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4448,13 +4451,14 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context,
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getTemplateVersionsByIDs = `-- name: GetTemplateVersionsByIDs :many
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4481,6 +4485,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4497,7 +4502,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU
|
|||
|
||||
const getTemplateVersionsByTemplateID = `-- name: GetTemplateVersionsByTemplateID :many
|
||||
SELECT
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
FROM
|
||||
template_versions
|
||||
WHERE
|
||||
|
@ -4562,6 +4567,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4577,7 +4583,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge
|
|||
}
|
||||
|
||||
const getTemplateVersionsCreatedAfter = `-- name: GetTemplateVersionsCreatedAfter :many
|
||||
SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers FROM template_versions WHERE created_at > $1
|
||||
SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message FROM template_versions WHERE created_at > $1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) {
|
||||
|
@ -4600,6 +4606,7 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -4623,12 +4630,13 @@ INSERT INTO
|
|||
created_at,
|
||||
updated_at,
|
||||
"name",
|
||||
message,
|
||||
readme,
|
||||
job_id,
|
||||
created_by
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
`
|
||||
|
||||
type InsertTemplateVersionParams struct {
|
||||
|
@ -4638,6 +4646,7 @@ type InsertTemplateVersionParams struct {
|
|||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Message string `db:"message" json:"message"`
|
||||
Readme string `db:"readme" json:"readme"`
|
||||
JobID uuid.UUID `db:"job_id" json:"job_id"`
|
||||
CreatedBy uuid.UUID `db:"created_by" json:"created_by"`
|
||||
|
@ -4651,6 +4660,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla
|
|||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
arg.Name,
|
||||
arg.Message,
|
||||
arg.Readme,
|
||||
arg.JobID,
|
||||
arg.CreatedBy,
|
||||
|
@ -4667,6 +4677,7 @@ func (q *sqlQuerier) InsertTemplateVersion(ctx context.Context, arg InsertTempla
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -4679,7 +4690,7 @@ SET
|
|||
updated_at = $3,
|
||||
name = $4
|
||||
WHERE
|
||||
id = $1 RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers
|
||||
id = $1 RETURNING id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, git_auth_providers, message
|
||||
`
|
||||
|
||||
type UpdateTemplateVersionByIDParams struct {
|
||||
|
@ -4708,6 +4719,7 @@ func (q *sqlQuerier) UpdateTemplateVersionByID(ctx context.Context, arg UpdateTe
|
|||
&i.JobID,
|
||||
&i.CreatedBy,
|
||||
pq.Array(&i.GitAuthProviders),
|
||||
&i.Message,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
@ -77,12 +77,13 @@ INSERT INTO
|
|||
created_at,
|
||||
updated_at,
|
||||
"name",
|
||||
message,
|
||||
readme,
|
||||
job_id,
|
||||
created_by
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *;
|
||||
|
||||
-- name: UpdateTemplateVersionByID :one
|
||||
UPDATE
|
||||
|
|
|
@ -1306,6 +1306,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
|
|||
CreatedAt: database.Now(),
|
||||
UpdatedAt: database.Now(),
|
||||
Name: req.Name,
|
||||
Message: req.Message,
|
||||
Readme: "",
|
||||
JobID: provisionerJob.ID,
|
||||
CreatedBy: apiKey.UserID,
|
||||
|
@ -1420,6 +1421,7 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi
|
|||
CreatedAt: version.CreatedAt,
|
||||
UpdatedAt: version.UpdatedAt,
|
||||
Name: version.Name,
|
||||
Message: version.Message,
|
||||
Job: job,
|
||||
Readme: version.Readme,
|
||||
CreatedBy: createdBy,
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -33,7 +34,10 @@ func TestTemplateVersion(t *testing.T) {
|
|||
user := coderdtest.CreateFirstUser(t, client)
|
||||
authz := coderdtest.AssertRBAC(t, api, client).Reset()
|
||||
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
|
||||
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||
req.Name = "bananas"
|
||||
req.Message = "first try"
|
||||
})
|
||||
authz.AssertChecked(t, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(user.OrganizationID))
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
|
@ -43,6 +47,29 @@ func TestTemplateVersion(t *testing.T) {
|
|||
tv, err := client.TemplateVersion(ctx, version.ID)
|
||||
authz.AssertChecked(t, rbac.ActionRead, tv)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "bananas", tv.Name)
|
||||
assert.Equal(t, "first try", tv.Message)
|
||||
})
|
||||
|
||||
t.Run("Message limit exceeded", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, _, _ := coderdtest.NewWithAPI(t, nil)
|
||||
user := coderdtest.CreateFirstUser(t, client)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
file, err := client.Upload(ctx, codersdk.ContentTypeTar, bytes.NewReader([]byte{}))
|
||||
require.NoError(t, err)
|
||||
_, err = client.CreateTemplateVersion(ctx, user.OrganizationID, codersdk.CreateTemplateVersionRequest{
|
||||
Name: "bananas",
|
||||
Message: strings.Repeat("a", 1048577),
|
||||
StorageMethod: codersdk.ProvisionerStorageMethodFile,
|
||||
FileID: file.ID,
|
||||
Provisioner: codersdk.ProvisionerTypeEcho,
|
||||
})
|
||||
require.Error(t, err, "message too long, create should fail")
|
||||
})
|
||||
|
||||
t.Run("MemberCanRead", func(t *testing.T) {
|
||||
|
|
|
@ -42,7 +42,8 @@ type OrganizationMember struct {
|
|||
|
||||
// CreateTemplateVersionRequest enables callers to create a new Template Version.
|
||||
type CreateTemplateVersionRequest struct {
|
||||
Name string `json:"name,omitempty" validate:"omitempty,template_version_name"`
|
||||
Name string `json:"name,omitempty" validate:"omitempty,template_version_name"`
|
||||
Message string `json:"message,omitempty" validate:"lt=1048577"`
|
||||
// TemplateID optionally associates a version with a template.
|
||||
TemplateID uuid.UUID `json:"template_id,omitempty" format:"uuid"`
|
||||
StorageMethod ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required" enums:"file"`
|
||||
|
|
|
@ -25,6 +25,7 @@ type TemplateVersion struct {
|
|||
CreatedAt time.Time `json:"created_at" format:"date-time"`
|
||||
UpdatedAt time.Time `json:"updated_at" format:"date-time"`
|
||||
Name string `json:"name"`
|
||||
Message string `json:"message"`
|
||||
Job ProvisionerJob `json:"job"`
|
||||
Readme string `json:"readme"`
|
||||
CreatedBy User `json:"created_by"`
|
||||
|
|
|
@ -17,7 +17,7 @@ We track the following resources:
|
|||
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
|
||||
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>inactivity_ttl</td><td>true</td></tr><tr><td>locked_ttl</td><td>true</td></tr><tr><td>max_ttl</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
|
||||
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>git_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>git_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>locked_at</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
|
|
|
@ -1435,6 +1435,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
{
|
||||
"example_id": "string",
|
||||
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"provisioner": "terraform",
|
||||
"storage_method": "file",
|
||||
|
@ -1458,6 +1459,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| ---------------------- | ---------------------------------------------------------------------- | -------- | ------------ | ------------------------------------------------------------ |
|
||||
| `example_id` | string | false | | |
|
||||
| `file_id` | string | false | | |
|
||||
| `message` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `provisioner` | string | true | | |
|
||||
| `storage_method` | [codersdk.ProvisionerStorageMethod](#codersdkprovisionerstoragemethod) | true | | |
|
||||
|
@ -4149,6 +4151,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -4166,6 +4169,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
|||
| `created_by` | [codersdk.User](#codersdkuser) | false | | |
|
||||
| `id` | string | false | | |
|
||||
| `job` | [codersdk.ProvisionerJob](#codersdkprovisionerjob) | false | | |
|
||||
| `message` | string | false | | |
|
||||
| `name` | string | false | | |
|
||||
| `organization_id` | string | false | | |
|
||||
| `readme` | string | false | | |
|
||||
|
|
|
@ -389,6 +389,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -469,6 +470,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -506,6 +508,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
|||
{
|
||||
"example_id": "string",
|
||||
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"provisioner": "terraform",
|
||||
"storage_method": "file",
|
||||
|
@ -572,6 +575,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -875,6 +879,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -927,6 +932,7 @@ Status Code **200**
|
|||
| `»» tags` | object | false | | |
|
||||
| `»»» [any property]` | string | false | | |
|
||||
| `»» worker_id` | string(uuid) | false | | |
|
||||
| `» message` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
| `» readme` | string | false | | |
|
||||
|
@ -1068,6 +1074,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templ
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -1120,6 +1127,7 @@ Status Code **200**
|
|||
| `»» tags` | object | false | | |
|
||||
| `»»» [any property]` | string | false | | |
|
||||
| `»» worker_id` | string(uuid) | false | | |
|
||||
| `» message` | string | false | | |
|
||||
| `» name` | string | false | | |
|
||||
| `» organization_id` | string(uuid) | false | | |
|
||||
| `» readme` | string | false | | |
|
||||
|
@ -1205,6 +1213,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
@ -1293,6 +1302,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}
|
|||
},
|
||||
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b"
|
||||
},
|
||||
"message": "string",
|
||||
"name": "string",
|
||||
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||
"readme": "string",
|
||||
|
|
|
@ -57,6 +57,14 @@ Ignore warnings about not having a .terraform.lock.hcl file present in the templ
|
|||
|
||||
Specify an inactivity TTL for workspaces created from this template. This licensed feature's default is 0h (off).
|
||||
|
||||
### -m, --message
|
||||
|
||||
| | |
|
||||
| ---- | ------------------- |
|
||||
| Type | <code>string</code> |
|
||||
|
||||
Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.
|
||||
|
||||
### --private
|
||||
|
||||
| | |
|
||||
|
|
|
@ -47,6 +47,14 @@ Specify the directory to create from, use '-' to read tar from stdin.
|
|||
|
||||
Ignore warnings about not having a .terraform.lock.hcl file present in the template.
|
||||
|
||||
### -m, --message
|
||||
|
||||
| | |
|
||||
| ---- | ------------------- |
|
||||
| Type | <code>string</code> |
|
||||
|
||||
Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.
|
||||
|
||||
### --name
|
||||
|
||||
| | |
|
||||
|
|
|
@ -88,6 +88,7 @@ var auditableResourcesTypes = map[any]map[string]Action{
|
|||
"created_at": ActionIgnore, // Never changes, but is implicit and not helpful in a diff.
|
||||
"updated_at": ActionIgnore, // Changes, but is implicit and not helpful in a diff.
|
||||
"name": ActionTrack,
|
||||
"message": ActionIgnore, // Never changes after creation.
|
||||
"readme": ActionTrack,
|
||||
"job_id": ActionIgnore, // Not helpful in a diff because jobs aren't tracked in audit logs.
|
||||
"created_by": ActionTrack,
|
||||
|
|
|
@ -200,6 +200,7 @@ export interface CreateTemplateVersionDryRunRequest {
|
|||
// From codersdk/organizations.go
|
||||
export interface CreateTemplateVersionRequest {
|
||||
readonly name?: string
|
||||
readonly message?: string
|
||||
readonly template_id?: string
|
||||
readonly storage_method: ProvisionerStorageMethod
|
||||
readonly file_id?: string
|
||||
|
@ -915,6 +916,7 @@ export interface TemplateVersion {
|
|||
readonly created_at: string
|
||||
readonly updated_at: string
|
||||
readonly name: string
|
||||
readonly message: string
|
||||
readonly job: ProvisionerJob
|
||||
readonly readme: string
|
||||
readonly created_by: User
|
||||
|
|
|
@ -352,6 +352,7 @@ export const MockTemplateVersion: TypesGen.TemplateVersion = {
|
|||
template_id: "test-template",
|
||||
job: MockProvisionerJob,
|
||||
name: "test-version",
|
||||
message: "first version",
|
||||
readme: `---
|
||||
name:Template test
|
||||
---
|
||||
|
@ -369,6 +370,7 @@ export const MockTemplateVersion2: TypesGen.TemplateVersion = {
|
|||
template_id: "test-template",
|
||||
job: MockProvisionerJob,
|
||||
name: "test-version-2",
|
||||
message: "first version",
|
||||
readme: `---
|
||||
name:Template test 2
|
||||
---
|
||||
|
@ -386,6 +388,7 @@ export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
|||
template_id: "test-template",
|
||||
job: MockProvisionerJob,
|
||||
name: "test-version-3",
|
||||
message: "first version",
|
||||
readme: "README",
|
||||
created_by: MockUser,
|
||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
||||
|
|
Loading…
Reference in New Issue