diff --git a/cli/templatecreate.go b/cli/templatecreate.go
index 555707b28baf2..369757e26da6c 100644
--- a/cli/templatecreate.go
+++ b/cli/templatecreate.go
@@ -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),
diff --git a/cli/templatepush.go b/cli/templatepush.go
index 02884aed360b3..bc5516b092ef9 100644
--- a/cli/templatepush.go
+++ b/cli/templatepush.go
@@ -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),
diff --git a/cli/templatepush_test.go b/cli/templatepush_test.go
index 22f5b6a38f1f5..c890ea6593b94 100644
--- a/cli/templatepush_test.go
+++ b/cli/templatepush_test.go
@@ -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})
diff --git a/cli/testdata/coder_templates_create_--help.golden b/cli/testdata/coder_templates_create_--help.golden
index d23b0f7571457..4ef57e5afd0d9 100644
--- a/cli/testdata/coder_templates_create_--help.golden
+++ b/cli/testdata/coder_templates_create_--help.golden
@@ -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
diff --git a/cli/testdata/coder_templates_push_--help.golden b/cli/testdata/coder_templates_push_--help.golden
index ebe0a73e7014a..6aeff1641fd0d 100644
--- a/cli/testdata/coder_templates_push_--help.golden
+++ b/cli/testdata/coder_templates_push_--help.golden
@@ -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.
diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index c1171e36c441a..47ea6ba415526 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -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"
},
diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json
index 1579439282a2f..e0428e6676d33 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -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"
},
diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go
index 55c4973cb74b2..810f8e0b929d9 100644
--- a/coderd/database/dbfake/dbfake.go
+++ b/coderd/database/dbfake/dbfake.go
@@ -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,
diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go
index d10d7c04593c6..93002010d5765 100644
--- a/coderd/database/dbgen/dbgen.go
+++ b/coderd/database/dbgen/dbgen.go
@@ -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()),
diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql
index 18f311a7d2d1d..322de586cfa31 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -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,
diff --git a/coderd/database/migrations/000137_add_template_version_message.down.sql b/coderd/database/migrations/000137_add_template_version_message.down.sql
new file mode 100644
index 0000000000000..39be7b7995a56
--- /dev/null
+++ b/coderd/database/migrations/000137_add_template_version_message.down.sql
@@ -0,0 +1 @@
+ALTER TABLE template_versions DROP COLUMN message;
diff --git a/coderd/database/migrations/000137_add_template_version_message.up.sql b/coderd/database/migrations/000137_add_template_version_message.up.sql
new file mode 100644
index 0000000000000..e8dd2e7b050f2
--- /dev/null
+++ b/coderd/database/migrations/000137_add_template_version_message.up.sql
@@ -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.';
diff --git a/coderd/database/models.go b/coderd/database/models.go
index 415eefb242196..35ab32c134ee0 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -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 {
diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go
index 94d643f25bb58..32d85aa9d6516 100644
--- a/coderd/database/queries.sql.go
+++ b/coderd/database/queries.sql.go
@@ -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
}
diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql
index 89f56ef617bcd..2245ef72fec1c 100644
--- a/coderd/database/queries/templateversions.sql
+++ b/coderd/database/queries/templateversions.sql
@@ -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
diff --git a/coderd/templateversions.go b/coderd/templateversions.go
index 139558688bf92..37a7bba98b2be 100644
--- a/coderd/templateversions.go
+++ b/coderd/templateversions.go
@@ -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,
diff --git a/coderd/templateversions_test.go b/coderd/templateversions_test.go
index 5f9887055b271..cef88235a08a4 100644
--- a/coderd/templateversions_test.go
+++ b/coderd/templateversions_test.go
@@ -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) {
diff --git a/codersdk/organizations.go b/codersdk/organizations.go
index 206dd12c0665f..e1a0d2f1e2c4c 100644
--- a/codersdk/organizations.go
+++ b/codersdk/organizations.go
@@ -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"`
diff --git a/codersdk/templateversions.go b/codersdk/templateversions.go
index c8296979aea4f..de3719fce53c0 100644
--- a/codersdk/templateversions.go
+++ b/codersdk/templateversions.go
@@ -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"`
diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md
index 131c9e7651aa5..454d041225385 100644
--- a/docs/admin/audit-logs.md
+++ b/docs/admin/audit-logs.md
@@ -17,7 +17,7 @@ We track the following resources:
| GitSSHKey
create |
Field | Tracked |
---|
created_at | false |
private_key | true |
public_key | true |
updated_at | false |
user_id | true |
|
| License
create, delete | Field | Tracked |
---|
exp | true |
id | false |
jwt | false |
uploaded_at | true |
uuid | true |
|
| Template
write, delete | Field | Tracked |
---|
active_version_id | true |
allow_user_autostart | true |
allow_user_autostop | true |
allow_user_cancel_workspace_jobs | true |
created_at | false |
created_by | true |
default_ttl | true |
deleted | false |
description | true |
display_name | true |
failure_ttl | true |
group_acl | true |
icon | true |
id | true |
inactivity_ttl | true |
locked_ttl | true |
max_ttl | true |
name | true |
organization_id | false |
provisioner | true |
updated_at | false |
user_acl | true |
|
-| TemplateVersion
create, write | Field | Tracked |
---|
created_at | false |
created_by | true |
git_auth_providers | false |
id | true |
job_id | false |
name | true |
organization_id | false |
readme | true |
template_id | true |
updated_at | false |
|
+| TemplateVersion
create, write | Field | Tracked |
---|
created_at | false |
created_by | true |
git_auth_providers | false |
id | true |
job_id | false |
message | false |
name | true |
organization_id | false |
readme | true |
template_id | true |
updated_at | false |
|
| User
create, write, delete | Field | Tracked |
---|
avatar_url | false |
created_at | false |
deleted | true |
email | true |
hashed_password | true |
id | true |
last_seen_at | false |
login_type | true |
rbac_roles | true |
status | true |
updated_at | false |
username | true |
|
| Workspace
create, write, delete | Field | Tracked |
---|
autostart_schedule | true |
created_at | false |
deleted | false |
id | true |
last_used_at | false |
locked_at | true |
name | true |
organization_id | false |
owner_id | true |
template_id | true |
ttl | true |
updated_at | false |
|
| WorkspaceBuild
start, stop | Field | Tracked |
---|
build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
id | false |
initiator_id | false |
job_id | false |
max_deadline | false |
provisioner_state | false |
reason | false |
template_version_id | true |
transition | false |
updated_at | false |
workspace_id | false |
|
diff --git a/docs/api/schemas.md b/docs/api/schemas.md
index 27e6f02b3e63f..7cde3c40f934a 100644
--- a/docs/api/schemas.md
+++ b/docs/api/schemas.md
@@ -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 | | |
diff --git a/docs/api/templates.md b/docs/api/templates.md
index 9309dcc8ba9ec..4d2cf8d4874b7 100644
--- a/docs/api/templates.md
+++ b/docs/api/templates.md
@@ -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",
diff --git a/docs/cli/templates_create.md b/docs/cli/templates_create.md
index 197ba3fdcf8a4..7b3957bbb38cd 100644
--- a/docs/cli/templates_create.md
+++ b/docs/cli/templates_create.md
@@ -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 | string
|
+
+Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.
+
### --private
| | |
diff --git a/docs/cli/templates_push.md b/docs/cli/templates_push.md
index 692887b6921d2..4043de558198b 100644
--- a/docs/cli/templates_push.md
+++ b/docs/cli/templates_push.md
@@ -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 | string
|
+
+Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.
+
### --name
| | |
diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go
index fd7d03827829a..4a2df1e64194b 100644
--- a/enterprise/audit/table.go
+++ b/enterprise/audit/table.go
@@ -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,
diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts
index 7a8f01ffc1a01..3048dbf2ae643 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -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
diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts
index 4c7a44a63f3b5..4f6321c940eab 100644
--- a/site/src/testHelpers/entities.ts
+++ b/site/src/testHelpers/entities.ts
@@ -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"],