mirror of https://github.com/coder/coder.git
feat: archive template versions to hide them from the ui (#10179)
* api + cli implementation
This commit is contained in:
parent
edbd51955c
commit
1e950fa9a8
|
@ -48,33 +48,13 @@ func (r *RootCmd) templateDelete() *clibase.Cmd {
|
||||||
templates = append(templates, template)
|
templates = append(templates, template)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
allTemplates, err := client.TemplatesByOrganization(ctx, organization.ID)
|
template, err := selectTemplate(inv, client, organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get templates by organization: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(allTemplates) == 0 {
|
templates = append(templates, template)
|
||||||
return xerrors.Errorf("no templates exist in the current organization %q", organization.Name)
|
templateNames = append(templateNames, template.Name)
|
||||||
}
|
|
||||||
|
|
||||||
opts := make([]string, 0, len(allTemplates))
|
|
||||||
for _, template := range allTemplates {
|
|
||||||
opts = append(opts, template.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
selection, err := cliui.Select(inv, cliui.SelectOptions{
|
|
||||||
Options: opts,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return xerrors.Errorf("select template: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, template := range allTemplates {
|
|
||||||
if template.Name == selection {
|
|
||||||
templates = append(templates, template)
|
|
||||||
templateNames = append(templateNames, template.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Confirm deletion of the template.
|
// Confirm deletion of the template.
|
||||||
|
|
|
@ -3,9 +3,9 @@ package cli
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
|
|
||||||
"github.com/coder/pretty"
|
"github.com/coder/pretty"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/coder/coder/v2/cli/clibase"
|
"github.com/coder/coder/v2/cli/clibase"
|
||||||
"github.com/coder/coder/v2/cli/cliui"
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
|
@ -43,12 +43,45 @@ func (r *RootCmd) templates() *clibase.Cmd {
|
||||||
r.templateVersions(),
|
r.templateVersions(),
|
||||||
r.templateDelete(),
|
r.templateDelete(),
|
||||||
r.templatePull(),
|
r.templatePull(),
|
||||||
|
r.archiveTemplateVersions(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectTemplate(inv *clibase.Invocation, client *codersdk.Client, organization codersdk.Organization) (codersdk.Template, error) {
|
||||||
|
var empty codersdk.Template
|
||||||
|
ctx := inv.Context()
|
||||||
|
allTemplates, err := client.TemplatesByOrganization(ctx, organization.ID)
|
||||||
|
if err != nil {
|
||||||
|
return empty, xerrors.Errorf("get templates by organization: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allTemplates) == 0 {
|
||||||
|
return empty, xerrors.Errorf("no templates exist in the current organization %q", organization.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := make([]string, 0, len(allTemplates))
|
||||||
|
for _, template := range allTemplates {
|
||||||
|
opts = append(opts, template.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
selection, err := cliui.Select(inv, cliui.SelectOptions{
|
||||||
|
Options: opts,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return empty, xerrors.Errorf("select template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, template := range allTemplates {
|
||||||
|
if template.Name == selection {
|
||||||
|
return template, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty, xerrors.Errorf("no template selected")
|
||||||
|
}
|
||||||
|
|
||||||
type templateTableRow struct {
|
type templateTableRow struct {
|
||||||
// Used by json format:
|
// Used by json format:
|
||||||
Template codersdk.Template
|
Template codersdk.Template
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/pretty"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/cli/clibase"
|
||||||
|
"github.com/coder/coder/v2/cli/cliui"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *RootCmd) unarchiveTemplateVersion() *clibase.Cmd {
|
||||||
|
return r.setArchiveTemplateVersion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootCmd) archiveTemplateVersion() *clibase.Cmd {
|
||||||
|
return r.setArchiveTemplateVersion(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:revive
|
||||||
|
func (r *RootCmd) setArchiveTemplateVersion(archive bool) *clibase.Cmd {
|
||||||
|
presentVerb := "archive"
|
||||||
|
pastVerb := "archived"
|
||||||
|
if !archive {
|
||||||
|
presentVerb = "unarchive"
|
||||||
|
pastVerb = "unarchived"
|
||||||
|
}
|
||||||
|
|
||||||
|
client := new(codersdk.Client)
|
||||||
|
cmd := &clibase.Cmd{
|
||||||
|
Use: presentVerb + " <template-name> [template-version-names...] ",
|
||||||
|
Short: strings.ToUpper(string(presentVerb[0])) + presentVerb[1:] + " a template version(s).",
|
||||||
|
Middleware: clibase.Chain(
|
||||||
|
r.InitClient(client),
|
||||||
|
),
|
||||||
|
Options: clibase.OptionSet{
|
||||||
|
cliui.SkipPromptOption(),
|
||||||
|
},
|
||||||
|
Handler: func(inv *clibase.Invocation) error {
|
||||||
|
var (
|
||||||
|
ctx = inv.Context()
|
||||||
|
versions []codersdk.TemplateVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
organization, err := CurrentOrganization(inv, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inv.Args) == 0 {
|
||||||
|
return xerrors.Errorf("missing template name")
|
||||||
|
}
|
||||||
|
if len(inv.Args) < 2 {
|
||||||
|
return xerrors.Errorf("missing template version name(s)")
|
||||||
|
}
|
||||||
|
|
||||||
|
templateName := inv.Args[0]
|
||||||
|
template, err := client.TemplateByName(ctx, organization.ID, templateName)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get template by name: %w", err)
|
||||||
|
}
|
||||||
|
for _, versionName := range inv.Args[1:] {
|
||||||
|
version, err := client.TemplateVersionByOrganizationAndName(ctx, organization.ID, template.Name, versionName)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get template version by name %q: %w", versionName, err)
|
||||||
|
}
|
||||||
|
versions = append(versions, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range versions {
|
||||||
|
if version.Archived == archive {
|
||||||
|
_, _ = fmt.Fprintln(
|
||||||
|
inv.Stdout, fmt.Sprintf("Version "+pretty.Sprint(cliui.DefaultStyles.Keyword, version.Name)+" already "+pastVerb),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err := client.SetArchiveTemplateVersion(ctx, version.ID, archive)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("%s template version %q: %w", presentVerb, version.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintln(
|
||||||
|
inv.Stdout, fmt.Sprintf("Version "+pretty.Sprint(cliui.DefaultStyles.Keyword, version.Name)+" "+pastVerb+" at "+cliui.Timestamp(time.Now())),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RootCmd) archiveTemplateVersions() *clibase.Cmd {
|
||||||
|
var all clibase.Bool
|
||||||
|
client := new(codersdk.Client)
|
||||||
|
cmd := &clibase.Cmd{
|
||||||
|
Use: "archive [template-name...] ",
|
||||||
|
Short: "Archive unused or failed template versions from a given template(s)",
|
||||||
|
Middleware: clibase.Chain(
|
||||||
|
r.InitClient(client),
|
||||||
|
),
|
||||||
|
Options: clibase.OptionSet{
|
||||||
|
cliui.SkipPromptOption(),
|
||||||
|
clibase.Option{
|
||||||
|
Name: "all",
|
||||||
|
Description: "Include all unused template versions. By default, only failed template versions are archived.",
|
||||||
|
Flag: "all",
|
||||||
|
Value: &all,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Handler: func(inv *clibase.Invocation) error {
|
||||||
|
var (
|
||||||
|
ctx = inv.Context()
|
||||||
|
templateNames = []string{}
|
||||||
|
templates = []codersdk.Template{}
|
||||||
|
)
|
||||||
|
|
||||||
|
organization, err := CurrentOrganization(inv, client)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inv.Args) > 0 {
|
||||||
|
templateNames = inv.Args
|
||||||
|
|
||||||
|
for _, templateName := range templateNames {
|
||||||
|
template, err := client.TemplateByName(ctx, organization.ID, templateName)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("get template by name: %w", err)
|
||||||
|
}
|
||||||
|
templates = append(templates, template)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
template, err := selectTemplate(inv, client, organization)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
templates = append(templates, template)
|
||||||
|
templateNames = append(templateNames, template.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Confirm archive of the template.
|
||||||
|
_, err = cliui.Prompt(inv, cliui.PromptOptions{
|
||||||
|
Text: fmt.Sprintf("Archive template versions of these templates: %s?", pretty.Sprint(cliui.DefaultStyles.Code, strings.Join(templateNames, ", "))),
|
||||||
|
IsConfirm: true,
|
||||||
|
Default: cliui.ConfirmNo,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, template := range templates {
|
||||||
|
resp, err := client.ArchiveTemplateVersions(ctx, template.ID, all.Value())
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("archive template %q: %w", template.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintln(
|
||||||
|
inv.Stdout, fmt.Sprintf("Archived %d versions from "+pretty.Sprint(cliui.DefaultStyles.Keyword, template.Name)+" at "+cliui.Timestamp(time.Now()), len(resp.ArchivedIDs)),
|
||||||
|
)
|
||||||
|
|
||||||
|
if ok, _ := inv.ParsedFlags().GetBool("verbose"); err == nil && ok {
|
||||||
|
data, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("marshal verbose response: %w", err)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintln(
|
||||||
|
inv.Stdout, string(data),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/cli/clitest"
|
||||||
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||||
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
|
"github.com/coder/coder/v2/codersdk"
|
||||||
|
"github.com/coder/coder/v2/provisioner/echo"
|
||||||
|
"github.com/coder/coder/v2/testutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplateVersionsArchive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("Archive-Unarchive", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||||
|
|
||||||
|
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
other := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
request.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, other.ID)
|
||||||
|
|
||||||
|
// Archive
|
||||||
|
inv, root := clitest.New(t, "templates", "versions", "archive", template.Name, other.Name, "-y")
|
||||||
|
clitest.SetupConfig(t, client, root)
|
||||||
|
w := clitest.StartWithWaiter(t, inv)
|
||||||
|
w.RequireSuccess()
|
||||||
|
|
||||||
|
// Verify archived
|
||||||
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||||
|
found, err := client.TemplateVersion(ctx, other.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, found.Archived, "expect archived")
|
||||||
|
|
||||||
|
// Unarchive
|
||||||
|
inv, root = clitest.New(t, "templates", "versions", "unarchive", template.Name, other.Name, "-y")
|
||||||
|
clitest.SetupConfig(t, client, root)
|
||||||
|
w = clitest.StartWithWaiter(t, inv)
|
||||||
|
w.RequireSuccess()
|
||||||
|
|
||||||
|
// Verify unarchived
|
||||||
|
ctx = testutil.Context(t, testutil.WaitMedium)
|
||||||
|
found, err = client.TemplateVersion(ctx, other.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, found.Archived, "expect unarchived")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ArchiveMany", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||||
|
|
||||||
|
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
|
||||||
|
// Add a failed
|
||||||
|
expArchived := map[uuid.UUID]bool{}
|
||||||
|
failed := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
ProvisionPlan: echo.PlanFailed,
|
||||||
|
}, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
request.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, failed.ID)
|
||||||
|
expArchived[failed.ID] = true
|
||||||
|
// Add unused
|
||||||
|
unused := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
request.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, unused.ID)
|
||||||
|
expArchived[unused.ID] = true
|
||||||
|
|
||||||
|
// Archive all unused versions
|
||||||
|
inv, root := clitest.New(t, "templates", "archive", template.Name, "-y", "--all")
|
||||||
|
clitest.SetupConfig(t, client, root)
|
||||||
|
w := clitest.StartWithWaiter(t, inv)
|
||||||
|
w.RequireSuccess()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||||
|
all, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
IncludeArchived: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "query all versions")
|
||||||
|
for _, v := range all {
|
||||||
|
if _, ok := expArchived[v.ID]; ok {
|
||||||
|
require.True(t, v.Archived, "expect archived")
|
||||||
|
delete(expArchived, v.ID)
|
||||||
|
} else {
|
||||||
|
require.False(t, v.Archived, "expect unarchived")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.Len(t, expArchived, 0, "expect all archived")
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/coder/pretty"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
@ -29,6 +30,8 @@ func (r *RootCmd) templateVersions() *clibase.Cmd {
|
||||||
},
|
},
|
||||||
Children: []*clibase.Cmd{
|
Children: []*clibase.Cmd{
|
||||||
r.templateVersionsList(),
|
r.templateVersionsList(),
|
||||||
|
r.archiveTemplateVersion(),
|
||||||
|
r.unarchiveTemplateVersion(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,19 +39,59 @@ func (r *RootCmd) templateVersions() *clibase.Cmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RootCmd) templateVersionsList() *clibase.Cmd {
|
func (r *RootCmd) templateVersionsList() *clibase.Cmd {
|
||||||
|
defaultColumns := []string{
|
||||||
|
"Name",
|
||||||
|
"Created At",
|
||||||
|
"Created By",
|
||||||
|
"Status",
|
||||||
|
"Active",
|
||||||
|
}
|
||||||
formatter := cliui.NewOutputFormatter(
|
formatter := cliui.NewOutputFormatter(
|
||||||
cliui.TableFormat([]templateVersionRow{}, nil),
|
cliui.TableFormat([]templateVersionRow{}, defaultColumns),
|
||||||
cliui.JSONFormat(),
|
cliui.JSONFormat(),
|
||||||
)
|
)
|
||||||
client := new(codersdk.Client)
|
client := new(codersdk.Client)
|
||||||
|
|
||||||
|
var includeArchived clibase.Bool
|
||||||
|
|
||||||
cmd := &clibase.Cmd{
|
cmd := &clibase.Cmd{
|
||||||
Use: "list <template>",
|
Use: "list <template>",
|
||||||
Middleware: clibase.Chain(
|
Middleware: clibase.Chain(
|
||||||
clibase.RequireNArgs(1),
|
clibase.RequireNArgs(1),
|
||||||
r.InitClient(client),
|
r.InitClient(client),
|
||||||
|
func(next clibase.HandlerFunc) clibase.HandlerFunc {
|
||||||
|
return func(i *clibase.Invocation) error {
|
||||||
|
// This is the only way to dynamically add the "archived"
|
||||||
|
// column if '--include-archived' is true.
|
||||||
|
// It does not make sense to show this column if the
|
||||||
|
// flag is false.
|
||||||
|
if includeArchived {
|
||||||
|
for _, opt := range i.Command.Options {
|
||||||
|
if opt.Flag == "column" {
|
||||||
|
if opt.ValueSource == clibase.ValueSourceDefault {
|
||||||
|
v, ok := opt.Value.(*clibase.StringArray)
|
||||||
|
if ok {
|
||||||
|
// Add the extra new default column.
|
||||||
|
*v = append(*v, "Archived")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next(i)
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Short: "List all the versions of the specified template",
|
Short: "List all the versions of the specified template",
|
||||||
|
Options: clibase.OptionSet{
|
||||||
|
{
|
||||||
|
Name: "include-archived",
|
||||||
|
Description: "Include archived versions in the result list.",
|
||||||
|
Flag: "include-archived",
|
||||||
|
Value: &includeArchived,
|
||||||
|
},
|
||||||
|
},
|
||||||
Handler: func(inv *clibase.Invocation) error {
|
Handler: func(inv *clibase.Invocation) error {
|
||||||
organization, err := CurrentOrganization(inv, client)
|
organization, err := CurrentOrganization(inv, client)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -59,7 +102,8 @@ func (r *RootCmd) templateVersionsList() *clibase.Cmd {
|
||||||
return xerrors.Errorf("get template by name: %w", err)
|
return xerrors.Errorf("get template by name: %w", err)
|
||||||
}
|
}
|
||||||
req := codersdk.TemplateVersionsByTemplateRequest{
|
req := codersdk.TemplateVersionsByTemplateRequest{
|
||||||
TemplateID: template.ID,
|
TemplateID: template.ID,
|
||||||
|
IncludeArchived: includeArchived.Value(),
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, err := client.TemplateVersionsByTemplate(inv.Context(), req)
|
versions, err := client.TemplateVersionsByTemplate(inv.Context(), req)
|
||||||
|
@ -92,6 +136,7 @@ type templateVersionRow struct {
|
||||||
CreatedBy string `json:"-" table:"created by"`
|
CreatedBy string `json:"-" table:"created by"`
|
||||||
Status string `json:"-" table:"status"`
|
Status string `json:"-" table:"status"`
|
||||||
Active string `json:"-" table:"active"`
|
Active string `json:"-" table:"active"`
|
||||||
|
Archived string `json:"-" table:"archived"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// templateVersionsToRows converts a list of template versions to a list of rows
|
// templateVersionsToRows converts a list of template versions to a list of rows
|
||||||
|
@ -104,6 +149,11 @@ func templateVersionsToRows(activeVersionID uuid.UUID, templateVersions ...coder
|
||||||
activeStatus = cliui.Keyword("Active")
|
activeStatus = cliui.Keyword("Active")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
archivedStatus := ""
|
||||||
|
if templateVersion.Archived {
|
||||||
|
archivedStatus = pretty.Sprint(cliui.DefaultStyles.Warn, "Archived")
|
||||||
|
}
|
||||||
|
|
||||||
rows[i] = templateVersionRow{
|
rows[i] = templateVersionRow{
|
||||||
TemplateVersion: templateVersion,
|
TemplateVersion: templateVersion,
|
||||||
Name: templateVersion.Name,
|
Name: templateVersion.Name,
|
||||||
|
@ -111,6 +161,7 @@ func templateVersionsToRows(activeVersionID uuid.UUID, templateVersions ...coder
|
||||||
CreatedBy: templateVersion.CreatedBy.Username,
|
CreatedBy: templateVersion.CreatedBy.Username,
|
||||||
Status: strings.Title(string(templateVersion.Job.Status)),
|
Status: strings.Title(string(templateVersion.Job.Status)),
|
||||||
Active: activeStatus,
|
Active: activeStatus,
|
||||||
|
Archived: archivedStatus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,8 @@ USAGE:
|
||||||
$ coder templates push my-template
|
$ coder templates push my-template
|
||||||
|
|
||||||
SUBCOMMANDS:
|
SUBCOMMANDS:
|
||||||
|
archive Archive unused or failed template versions from a given
|
||||||
|
template(s)
|
||||||
create Create a template from the current directory or as specified by
|
create Create a template from the current directory or as specified by
|
||||||
flag
|
flag
|
||||||
delete Delete templates
|
delete Delete templates
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
coder v0.0.0-devel
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
coder templates archive [flags] [template-name...]
|
||||||
|
|
||||||
|
Archive unused or failed template versions from a given template(s)
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
--all bool
|
||||||
|
Include all unused template versions. By default, only failed template
|
||||||
|
versions are archived.
|
||||||
|
|
||||||
|
-y, --yes bool
|
||||||
|
Bypass prompts.
|
||||||
|
|
||||||
|
———
|
||||||
|
Run `coder --help` for a list of global options.
|
|
@ -12,7 +12,9 @@ USAGE:
|
||||||
$ coder templates versions list my-template
|
$ coder templates versions list my-template
|
||||||
|
|
||||||
SUBCOMMANDS:
|
SUBCOMMANDS:
|
||||||
list List all the versions of the specified template
|
archive Archive a template version(s).
|
||||||
|
list List all the versions of the specified template
|
||||||
|
unarchive Unarchive a template version(s).
|
||||||
|
|
||||||
———
|
———
|
||||||
Run `coder --help` for a list of global options.
|
Run `coder --help` for a list of global options.
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
coder v0.0.0-devel
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
coder templates versions archive [flags] <template-name>
|
||||||
|
[template-version-names...]
|
||||||
|
|
||||||
|
Archive a template version(s).
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-y, --yes bool
|
||||||
|
Bypass prompts.
|
||||||
|
|
||||||
|
———
|
||||||
|
Run `coder --help` for a list of global options.
|
|
@ -6,9 +6,12 @@ USAGE:
|
||||||
List all the versions of the specified template
|
List all the versions of the specified template
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-c, --column string-array (default: name,created at,created by,status,active)
|
-c, --column string-array (default: Name,Created At,Created By,Status,Active)
|
||||||
Columns to display in table output. Available columns: name, created
|
Columns to display in table output. Available columns: name, created
|
||||||
at, created by, status, active.
|
at, created by, status, active, archived.
|
||||||
|
|
||||||
|
--include-archived bool
|
||||||
|
Include archived versions in the result list.
|
||||||
|
|
||||||
-o, --output string (default: table)
|
-o, --output string (default: table)
|
||||||
Output format. Available formats: table, json.
|
Output format. Available formats: table, json.
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
coder v0.0.0-devel
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
coder templates versions unarchive [flags] <template-name>
|
||||||
|
[template-version-names...]
|
||||||
|
|
||||||
|
Unarchive a template version(s).
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-y, --yes bool
|
||||||
|
Bypass prompts.
|
||||||
|
|
||||||
|
———
|
||||||
|
Run `coder --help` for a list of global options.
|
|
@ -2365,6 +2365,53 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templates/{template}/versions/archive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Templates"
|
||||||
|
],
|
||||||
|
"summary": "Archive template unused versions by template id",
|
||||||
|
"operationId": "archive-template-unused-versions-by-template-id",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template ID",
|
||||||
|
"name": "template",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Archive request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.ArchiveTemplateVersionsRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templates/{template}/versions/{templateversionname}": {
|
"/templates/{template}/versions/{templateversionname}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -2490,6 +2537,41 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templateversions/{templateversion}/archive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Templates"
|
||||||
|
],
|
||||||
|
"summary": "Archive template version",
|
||||||
|
"operationId": "archive-template-version",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template version ID",
|
||||||
|
"name": "templateversion",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templateversions/{templateversion}/cancel": {
|
"/templateversions/{templateversion}/cancel": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -2996,6 +3078,41 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templateversions/{templateversion}/unarchive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Templates"
|
||||||
|
],
|
||||||
|
"summary": "Unarchive template version",
|
||||||
|
"operationId": "unarchive-template-version",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template version ID",
|
||||||
|
"name": "templateversion",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templateversions/{templateversion}/variables": {
|
"/templateversions/{templateversion}/variables": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -7145,6 +7262,15 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.ArchiveTemplateVersionsRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"all": {
|
||||||
|
"description": "By default, only failed versions are archived. Set this to true\nto archive all unused versions regardless of job status.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.AssignableRoles": {
|
"codersdk.AssignableRoles": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -10087,6 +10213,9 @@ const docTemplate = `{
|
||||||
"codersdk.TemplateVersion": {
|
"codersdk.TemplateVersion": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
|
|
@ -2067,6 +2067,47 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templates/{template}/versions/archive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": ["application/json"],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"tags": ["Templates"],
|
||||||
|
"summary": "Archive template unused versions by template id",
|
||||||
|
"operationId": "archive-template-unused-versions-by-template-id",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template ID",
|
||||||
|
"name": "template",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Archive request",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.ArchiveTemplateVersionsRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templates/{template}/versions/{templateversionname}": {
|
"/templates/{template}/versions/{templateversionname}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -2178,6 +2219,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templateversions/{templateversion}/archive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"tags": ["Templates"],
|
||||||
|
"summary": "Archive template version",
|
||||||
|
"operationId": "archive-template-version",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template version ID",
|
||||||
|
"name": "templateversion",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templateversions/{templateversion}/cancel": {
|
"/templateversions/{templateversion}/cancel": {
|
||||||
"patch": {
|
"patch": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -2638,6 +2710,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/templateversions/{templateversion}/unarchive": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"produces": ["application/json"],
|
||||||
|
"tags": ["Templates"],
|
||||||
|
"summary": "Unarchive template version",
|
||||||
|
"operationId": "unarchive-template-version",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Template version ID",
|
||||||
|
"name": "templateversion",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.Response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/templateversions/{templateversion}/variables": {
|
"/templateversions/{templateversion}/variables": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -6347,6 +6450,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.ArchiveTemplateVersionsRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"all": {
|
||||||
|
"description": "By default, only failed versions are archived. Set this to true\nto archive all unused versions regardless of job status.",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.AssignableRoles": {
|
"codersdk.AssignableRoles": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
@ -9122,6 +9234,9 @@
|
||||||
"codersdk.TemplateVersion": {
|
"codersdk.TemplateVersion": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"archived": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
|
|
@ -670,6 +670,7 @@ func New(options *Options) *API {
|
||||||
r.Delete("/", api.deleteTemplate)
|
r.Delete("/", api.deleteTemplate)
|
||||||
r.Patch("/", api.patchTemplateMeta)
|
r.Patch("/", api.patchTemplateMeta)
|
||||||
r.Route("/versions", func(r chi.Router) {
|
r.Route("/versions", func(r chi.Router) {
|
||||||
|
r.Post("/archive", api.postArchiveTemplateVersions)
|
||||||
r.Get("/", api.templateVersionsByTemplate)
|
r.Get("/", api.templateVersionsByTemplate)
|
||||||
r.Patch("/", api.patchActiveTemplateVersion)
|
r.Patch("/", api.patchActiveTemplateVersion)
|
||||||
r.Get("/{templateversionname}", api.templateVersionByName)
|
r.Get("/{templateversionname}", api.templateVersionByName)
|
||||||
|
@ -683,6 +684,8 @@ func New(options *Options) *API {
|
||||||
r.Get("/", api.templateVersion)
|
r.Get("/", api.templateVersion)
|
||||||
r.Patch("/", api.patchTemplateVersion)
|
r.Patch("/", api.patchTemplateVersion)
|
||||||
r.Patch("/cancel", api.patchCancelTemplateVersion)
|
r.Patch("/cancel", api.patchCancelTemplateVersion)
|
||||||
|
r.Post("/archive", api.postArchiveTemplateVersion())
|
||||||
|
r.Post("/unarchive", api.postUnarchiveTemplateVersion())
|
||||||
// Old agents may expect a non-error response from /schema and /parameters endpoints.
|
// Old agents may expect a non-error response from /schema and /parameters endpoints.
|
||||||
// The idea is to return an empty [], so that the coder CLI won't get blocked accidentally.
|
// The idea is to return an empty [], so that the coder CLI won't get blocked accidentally.
|
||||||
r.Get("/schema", templateVersionSchemaDeprecated)
|
r.Get("/schema", templateVersionSchemaDeprecated)
|
||||||
|
|
|
@ -5345,6 +5345,8 @@ func (q *FakeQuerier) UnarchiveTemplateVersion(_ context.Context, arg database.U
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
for i, v := range q.data.templateVersions {
|
for i, v := range q.data.templateVersions {
|
||||||
if v.ID == arg.TemplateVersionID {
|
if v.ID == arg.TemplateVersionID {
|
||||||
|
|
|
@ -79,6 +79,17 @@ func (p *QueryParamParser) Int(vals url.Values, def int, queryParam string) int
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *QueryParamParser) Boolean(vals url.Values, def bool, queryParam string) bool {
|
||||||
|
v, err := parseQueryParam(p, vals, strconv.ParseBool, def, queryParam)
|
||||||
|
if err != nil {
|
||||||
|
p.Errors = append(p.Errors, codersdk.ValidationError{
|
||||||
|
Field: queryParam,
|
||||||
|
Detail: fmt.Sprintf("Query param %q must be a valid boolean (%s)", queryParam, err.Error()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
func (p *QueryParamParser) Required(queryParam string) *QueryParamParser {
|
func (p *QueryParamParser) Required(queryParam string) *QueryParamParser {
|
||||||
p.RequiredParams[queryParam] = true
|
p.RequiredParams[queryParam] = true
|
||||||
return p
|
return p
|
||||||
|
|
|
@ -157,6 +157,48 @@ func TestParseQueryParams(t *testing.T) {
|
||||||
testQueryParams(t, expParams, parser, parser.String)
|
testQueryParams(t, expParams, parser, parser.String)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Boolean", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
expParams := []queryParamTestCase[bool]{
|
||||||
|
{
|
||||||
|
QueryParam: "valid_true",
|
||||||
|
Value: "true",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QueryParam: "casing",
|
||||||
|
Value: "True",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QueryParam: "all_caps",
|
||||||
|
Value: "TRUE",
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QueryParam: "no_value_true_def",
|
||||||
|
NoSet: true,
|
||||||
|
Default: true,
|
||||||
|
Expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
QueryParam: "no_value",
|
||||||
|
NoSet: true,
|
||||||
|
Expected: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
QueryParam: "invalid_boolean",
|
||||||
|
Value: "yes",
|
||||||
|
Expected: false,
|
||||||
|
ExpectedErrorContains: "must be a valid boolean",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := httpapi.NewQueryParamParser()
|
||||||
|
testQueryParams(t, expParams, parser, parser.Boolean)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Int", func(t *testing.T) {
|
t.Run("Int", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
expParams := []queryParamTestCase[int]{
|
expParams := []queryParamTestCase[int]{
|
||||||
|
|
|
@ -193,6 +193,15 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if templateVersion.Archived {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: fmt.Sprintf("Template version %s is archived.", createTemplate.VersionID),
|
||||||
|
Validations: []codersdk.ValidationError{
|
||||||
|
{Field: "template_version_id", Detail: "Template version is archived"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
templateVersionAudit.Old = templateVersion
|
templateVersionAudit.Old = templateVersion
|
||||||
if templateVersion.TemplateID.Valid {
|
if templateVersion.TemplateID.Valid {
|
||||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
|
|
@ -717,6 +717,17 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this throws an error, the boolean is false. Which is the default we want.
|
||||||
|
parser := httpapi.NewQueryParamParser()
|
||||||
|
includeArchived := parser.Boolean(r.URL.Query(), false, "include_archived")
|
||||||
|
if len(parser.Errors) > 0 {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "Invalid query parameters.",
|
||||||
|
Validations: parser.Errors,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
apiVersions := []codersdk.TemplateVersion{}
|
apiVersions := []codersdk.TemplateVersion{}
|
||||||
err = api.Database.InTx(func(store database.Store) error {
|
err = api.Database.InTx(func(store database.Store) error {
|
||||||
|
@ -738,11 +749,21 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Exclude archived templates versions
|
||||||
|
archiveFilter := sql.NullBool{
|
||||||
|
Bool: false,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
if includeArchived {
|
||||||
|
archiveFilter = sql.NullBool{Valid: false}
|
||||||
|
}
|
||||||
|
|
||||||
versions, err := store.GetTemplateVersionsByTemplateID(ctx, database.GetTemplateVersionsByTemplateIDParams{
|
versions, err := store.GetTemplateVersionsByTemplateID(ctx, database.GetTemplateVersionsByTemplateIDParams{
|
||||||
TemplateID: template.ID,
|
TemplateID: template.ID,
|
||||||
AfterID: paginationParams.AfterID,
|
AfterID: paginationParams.AfterID,
|
||||||
LimitOpt: int32(paginationParams.Limit),
|
LimitOpt: int32(paginationParams.Limit),
|
||||||
OffsetOpt: int32(paginationParams.Offset),
|
OffsetOpt: int32(paginationParams.Offset),
|
||||||
|
Archived: archiveFilter,
|
||||||
})
|
})
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, apiVersions)
|
httpapi.Write(ctx, rw, http.StatusOK, apiVersions)
|
||||||
|
@ -991,6 +1012,173 @@ func (api *API) previousTemplateVersionByOrganizationTemplateAndName(rw http.Res
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(previousTemplateVersion, convertProvisionerJob(jobs[0]), nil))
|
httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(previousTemplateVersion, convertProvisionerJob(jobs[0]), nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Archive template unused versions by template id
|
||||||
|
// @ID archive-template-unused-versions-by-template-id
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Tags Templates
|
||||||
|
// @Param template path string true "Template ID" format(uuid)
|
||||||
|
// @Param request body codersdk.ArchiveTemplateVersionsRequest true "Archive request"
|
||||||
|
// @Success 200 {object} codersdk.Response
|
||||||
|
// @Router /templates/{template}/versions/archive [post]
|
||||||
|
func (api *API) postArchiveTemplateVersions(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
ctx = r.Context()
|
||||||
|
template = httpmw.TemplateParam(r)
|
||||||
|
auditor = *api.Auditor.Load()
|
||||||
|
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
|
||||||
|
Audit: auditor,
|
||||||
|
Log: api.Logger,
|
||||||
|
Request: r,
|
||||||
|
Action: database.AuditActionWrite,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
defer commitAudit()
|
||||||
|
aReq.Old = template
|
||||||
|
|
||||||
|
var req codersdk.ArchiveTemplateVersionsRequest
|
||||||
|
if !httpapi.Read(ctx, rw, r, &req) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
status := database.NullProvisionerJobStatus{
|
||||||
|
ProvisionerJobStatus: database.ProvisionerJobStatusFailed,
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
if req.All {
|
||||||
|
status = database.NullProvisionerJobStatus{}
|
||||||
|
}
|
||||||
|
|
||||||
|
archived, err := api.Database.ArchiveUnusedTemplateVersions(ctx, database.ArchiveUnusedTemplateVersionsParams{
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
|
TemplateID: template.ID,
|
||||||
|
JobStatus: status,
|
||||||
|
// Archive all versions that match
|
||||||
|
TemplateVersionID: uuid.Nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
if httpapi.Is404Error(err) {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||||
|
Message: "Template or template versions not found.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Internal error fetching template version.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusOK, codersdk.ArchiveTemplateVersionsResponse{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
ArchivedIDs: archived,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Archive template version
|
||||||
|
// @ID archive-template-version
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Produce json
|
||||||
|
// @Tags Templates
|
||||||
|
// @Param templateversion path string true "Template version ID" format(uuid)
|
||||||
|
// @Success 200 {object} codersdk.Response
|
||||||
|
// @Router /templateversions/{templateversion}/archive [post]
|
||||||
|
func (api *API) postArchiveTemplateVersion() func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
return api.setArchiveTemplateVersion(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Unarchive template version
|
||||||
|
// @ID unarchive-template-version
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Produce json
|
||||||
|
// @Tags Templates
|
||||||
|
// @Param templateversion path string true "Template version ID" format(uuid)
|
||||||
|
// @Success 200 {object} codersdk.Response
|
||||||
|
// @Router /templateversions/{templateversion}/unarchive [post]
|
||||||
|
func (api *API) postUnarchiveTemplateVersion() func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
return api.setArchiveTemplateVersion(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:revive
|
||||||
|
func (api *API) setArchiveTemplateVersion(archive bool) func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
return func(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
ctx = r.Context()
|
||||||
|
templateVersion = httpmw.TemplateVersionParam(r)
|
||||||
|
auditor = *api.Auditor.Load()
|
||||||
|
aReq, commitAudit = audit.InitRequest[database.TemplateVersion](rw, &audit.RequestParams{
|
||||||
|
Audit: auditor,
|
||||||
|
Log: api.Logger,
|
||||||
|
Request: r,
|
||||||
|
Action: database.AuditActionWrite,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
defer commitAudit()
|
||||||
|
aReq.Old = templateVersion
|
||||||
|
|
||||||
|
verb := "archived"
|
||||||
|
if !archive {
|
||||||
|
verb = "unarchived"
|
||||||
|
}
|
||||||
|
if templateVersion.Archived == archive {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: fmt.Sprintf("Template version already %s", verb),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !templateVersion.TemplateID.Valid {
|
||||||
|
// Maybe we should allow this?
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "Cannot archive template versions not associate with a template.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
if archive {
|
||||||
|
archived, archiveError := api.Database.ArchiveUnusedTemplateVersions(ctx, database.ArchiveUnusedTemplateVersionsParams{
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
|
TemplateID: templateVersion.TemplateID.UUID,
|
||||||
|
TemplateVersionID: templateVersion.ID,
|
||||||
|
JobStatus: database.NullProvisionerJobStatus{},
|
||||||
|
})
|
||||||
|
|
||||||
|
if archiveError != nil {
|
||||||
|
err = archiveError
|
||||||
|
} else {
|
||||||
|
if len(archived) == 0 {
|
||||||
|
err = xerrors.New("Unable to archive specified version, the version is likely in use by a workspace or currently set to the active version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = api.Database.UnarchiveTemplateVersion(ctx, database.UnarchiveTemplateVersionParams{
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
|
TemplateVersionID: templateVersion.ID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if httpapi.Is404Error(err) {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||||
|
Message: "Template or template versions not found.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Internal error fetching template version.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusOK, fmt.Sprintf("template version %q %s", templateVersion.ID.String(), verb))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// @Summary Update active template version by template ID
|
// @Summary Update active template version by template ID
|
||||||
// @ID update-active-template-version-by-template-id
|
// @ID update-active-template-version-by-template-id
|
||||||
// @Security CoderSessionToken
|
// @Security CoderSessionToken
|
||||||
|
@ -1055,6 +1243,12 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if version.Archived {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||||
|
Message: "The provided template version is archived.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = api.Database.InTx(func(store database.Store) error {
|
err = api.Database.InTx(func(store database.Store) error {
|
||||||
err = store.UpdateTemplateActiveVersionByID(ctx, database.UpdateTemplateActiveVersionByIDParams{
|
err = store.UpdateTemplateActiveVersionByID(ctx, database.UpdateTemplateActiveVersionByIDParams{
|
||||||
|
@ -1404,6 +1598,7 @@ func convertTemplateVersion(version database.TemplateVersion, job codersdk.Provi
|
||||||
Username: version.CreatedByUsername,
|
Username: version.CreatedByUsername,
|
||||||
AvatarURL: version.CreatedByAvatarURL.String,
|
AvatarURL: version.CreatedByAvatarURL.String,
|
||||||
},
|
},
|
||||||
|
Archived: version.Archived,
|
||||||
Warnings: warnings,
|
Warnings: warnings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -619,6 +619,34 @@ func TestPatchActiveTemplateVersion(t *testing.T) {
|
||||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Archived", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
auditor := audit.NewMock()
|
||||||
|
ownerClient := coderdtest.New(t, &coderdtest.Options{
|
||||||
|
IncludeProvisionerDaemon: true,
|
||||||
|
Auditor: auditor,
|
||||||
|
})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||||
|
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, version.ID)
|
||||||
|
version = coderdtest.UpdateTemplateVersion(t, client, owner.OrganizationID, nil, template.ID)
|
||||||
|
_ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
err := client.SetArchiveTemplateVersion(ctx, version.ID, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.UpdateActiveTemplateVersion(ctx, template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||||
|
ID: version.ID,
|
||||||
|
})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "The provided template version is archived")
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("SuccessfulBuild", func(t *testing.T) {
|
t.Run("SuccessfulBuild", func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
auditor := audit.NewMock()
|
auditor := audit.NewMock()
|
||||||
|
@ -1515,3 +1543,118 @@ func TestTemplateVersionParameters_Order(t *testing.T) {
|
||||||
require.Equal(t, secondParameterName, templateRichParameters[3].Name)
|
require.Equal(t, secondParameterName, templateRichParameters[3].Name)
|
||||||
require.Equal(t, thirdParameterName, templateRichParameters[4].Name)
|
require.Equal(t, thirdParameterName, templateRichParameters[4].Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateArchiveVersions(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||||
|
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
|
||||||
|
var totalVersions int
|
||||||
|
// Create a template to archive
|
||||||
|
initialVersion := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
totalVersions++
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, initialVersion.ID)
|
||||||
|
|
||||||
|
allFailed := make([]uuid.UUID, 0)
|
||||||
|
expArchived := make([]uuid.UUID, 0)
|
||||||
|
// create some failed versions
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
failed := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: echo.PlanFailed,
|
||||||
|
ProvisionApply: echo.ApplyFailed,
|
||||||
|
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
req.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
allFailed = append(allFailed, failed.ID)
|
||||||
|
totalVersions++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some unused versions
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
unused := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
req.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
expArchived = append(expArchived, unused.ID)
|
||||||
|
totalVersions++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some used template versions
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
used := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionPlan: echo.PlanComplete,
|
||||||
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
}, func(req *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
req.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, used.ID)
|
||||||
|
workspace := coderdtest.CreateWorkspace(t, client, owner.OrganizationID, uuid.Nil, func(request *codersdk.CreateWorkspaceRequest) {
|
||||||
|
request.TemplateVersionID = used.ID
|
||||||
|
})
|
||||||
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||||
|
totalVersions++
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||||
|
versions, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
Pagination: codersdk.Pagination{
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "fetch all versions")
|
||||||
|
require.Len(t, versions, totalVersions, "total versions")
|
||||||
|
|
||||||
|
// Archive failed versions
|
||||||
|
archiveFailed, err := client.ArchiveTemplateVersions(ctx, template.ID, false)
|
||||||
|
require.NoError(t, err, "archive failed versions")
|
||||||
|
require.ElementsMatch(t, archiveFailed.ArchivedIDs, allFailed, "all failed versions archived")
|
||||||
|
|
||||||
|
remaining, err := client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
Pagination: codersdk.Pagination{
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "fetch all non-failed versions")
|
||||||
|
require.Len(t, remaining, totalVersions-len(allFailed), "remaining non-failed versions")
|
||||||
|
|
||||||
|
// Try archiving "All" unused templates
|
||||||
|
archived, err := client.ArchiveTemplateVersions(ctx, template.ID, true)
|
||||||
|
require.NoError(t, err, "archive versions")
|
||||||
|
require.ElementsMatch(t, archived.ArchivedIDs, expArchived, "all expected versions archived")
|
||||||
|
|
||||||
|
remaining, err = client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
Pagination: codersdk.Pagination{
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "fetch all versions")
|
||||||
|
require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed), "remaining versions")
|
||||||
|
|
||||||
|
// Unarchive a version
|
||||||
|
err = client.SetArchiveTemplateVersion(ctx, expArchived[0], false)
|
||||||
|
require.NoError(t, err, "unarchive a version")
|
||||||
|
|
||||||
|
tv, err := client.TemplateVersion(ctx, expArchived[0])
|
||||||
|
require.NoError(t, err, "fetch version")
|
||||||
|
require.False(t, tv.Archived, "expect unarchived")
|
||||||
|
|
||||||
|
// Check the remaining again
|
||||||
|
remaining, err = client.TemplateVersionsByTemplate(ctx, codersdk.TemplateVersionsByTemplateRequest{
|
||||||
|
TemplateID: template.ID,
|
||||||
|
Pagination: codersdk.Pagination{
|
||||||
|
Limit: 100,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err, "fetch all versions")
|
||||||
|
require.Len(t, remaining, totalVersions-len(expArchived)-len(allFailed)+1, "remaining versions")
|
||||||
|
}
|
||||||
|
|
|
@ -352,6 +352,18 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if templateVersion.Archived {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Archived template versions cannot be used to make a workspace.",
|
||||||
|
Validations: []codersdk.ValidationError{
|
||||||
|
{
|
||||||
|
Field: "template_version_id",
|
||||||
|
Detail: "template version archived",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
templateID = templateVersion.TemplateID.UUID
|
templateID = templateVersion.TemplateID.UUID
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,6 +308,36 @@ func TestWorkspace(t *testing.T) {
|
||||||
assert.NotEmpty(t, agent2.Health.Reason)
|
assert.NotEmpty(t, agent2.Health.Reason)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("Archived", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ownerClient := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
owner := coderdtest.CreateFirstUser(t, ownerClient)
|
||||||
|
|
||||||
|
client, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
|
||||||
|
active := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil)
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, active.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, owner.OrganizationID, active.ID)
|
||||||
|
// We need another version because the active template version cannot be
|
||||||
|
// archived.
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, owner.OrganizationID, nil, func(request *codersdk.CreateTemplateVersionRequest) {
|
||||||
|
request.TemplateID = template.ID
|
||||||
|
})
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||||
|
|
||||||
|
err := client.SetArchiveTemplateVersion(ctx, version.ID, true)
|
||||||
|
require.NoError(t, err, "archive version")
|
||||||
|
|
||||||
|
_, err = client.CreateWorkspace(ctx, owner.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
|
||||||
|
TemplateVersionID: version.ID,
|
||||||
|
Name: "testworkspace",
|
||||||
|
})
|
||||||
|
require.Error(t, err, "create workspace with archived version")
|
||||||
|
require.ErrorContains(t, err, "Archived template versions cannot")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdminViewAllWorkspaces(t *testing.T) {
|
func TestAdminViewAllWorkspaces(t *testing.T) {
|
||||||
|
|
|
@ -136,6 +136,17 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ArchiveTemplateVersionsRequest struct {
|
||||||
|
// By default, only failed versions are archived. Set this to true
|
||||||
|
// to archive all unused versions regardless of job status.
|
||||||
|
All bool `json:"all"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArchiveTemplateVersionsResponse struct {
|
||||||
|
TemplateID uuid.UUID `json:"template_id" format:"uuid"`
|
||||||
|
ArchivedIDs []uuid.UUID `json:"archived_ids"`
|
||||||
|
}
|
||||||
|
|
||||||
type TemplateRole string
|
type TemplateRole string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -227,6 +238,44 @@ func (c *Client) Template(ctx context.Context, template uuid.UUID) (Template, er
|
||||||
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ArchiveTemplateVersions(ctx context.Context, template uuid.UUID, all bool) (ArchiveTemplateVersionsResponse, error) {
|
||||||
|
res, err := c.Request(ctx, http.MethodPost,
|
||||||
|
fmt.Sprintf("/api/v2/templates/%s/versions/archive", template),
|
||||||
|
ArchiveTemplateVersionsRequest{
|
||||||
|
All: all,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return ArchiveTemplateVersionsResponse{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return ArchiveTemplateVersionsResponse{}, ReadBodyAsError(res)
|
||||||
|
}
|
||||||
|
var resp ArchiveTemplateVersionsResponse
|
||||||
|
return resp, json.NewDecoder(res.Body).Decode(&resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:revive
|
||||||
|
func (c *Client) SetArchiveTemplateVersion(ctx context.Context, templateVersion uuid.UUID, archive bool) error {
|
||||||
|
u := fmt.Sprintf("/api/v2/templateversions/%s", templateVersion.String())
|
||||||
|
if archive {
|
||||||
|
u += "/archive"
|
||||||
|
} else {
|
||||||
|
u += "/unarchive"
|
||||||
|
}
|
||||||
|
res, err := c.Request(ctx, http.MethodPost, u, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
return ReadBodyAsError(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) DeleteTemplate(ctx context.Context, template uuid.UUID) error {
|
func (c *Client) DeleteTemplate(ctx context.Context, template uuid.UUID) error {
|
||||||
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/templates/%s", template), nil)
|
res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/templates/%s", template), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -311,13 +360,18 @@ func (c *Client) UpdateActiveTemplateVersion(ctx context.Context, template uuid.
|
||||||
// TemplateVersionsByTemplateRequest defines the request parameters for
|
// TemplateVersionsByTemplateRequest defines the request parameters for
|
||||||
// TemplateVersionsByTemplate.
|
// TemplateVersionsByTemplate.
|
||||||
type TemplateVersionsByTemplateRequest struct {
|
type TemplateVersionsByTemplateRequest struct {
|
||||||
TemplateID uuid.UUID `json:"template_id" validate:"required" format:"uuid"`
|
TemplateID uuid.UUID `json:"template_id" validate:"required" format:"uuid"`
|
||||||
|
IncludeArchived bool `json:"include_archived"`
|
||||||
Pagination
|
Pagination
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateVersionsByTemplate lists versions associated with a template.
|
// TemplateVersionsByTemplate lists versions associated with a template.
|
||||||
func (c *Client) TemplateVersionsByTemplate(ctx context.Context, req TemplateVersionsByTemplateRequest) ([]TemplateVersion, error) {
|
func (c *Client) TemplateVersionsByTemplate(ctx context.Context, req TemplateVersionsByTemplateRequest) ([]TemplateVersion, error) {
|
||||||
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/templates/%s/versions", req.TemplateID), nil, req.Pagination.asRequestOption())
|
u := fmt.Sprintf("/api/v2/templates/%s/versions", req.TemplateID)
|
||||||
|
if req.IncludeArchived {
|
||||||
|
u += "?include_archived=true"
|
||||||
|
}
|
||||||
|
res, err := c.Request(ctx, http.MethodGet, u, nil, req.Pagination.asRequestOption())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ type TemplateVersion struct {
|
||||||
Job ProvisionerJob `json:"job"`
|
Job ProvisionerJob `json:"job"`
|
||||||
Readme string `json:"readme"`
|
Readme string `json:"readme"`
|
||||||
CreatedBy MinimalUser `json:"created_by"`
|
CreatedBy MinimalUser `json:"created_by"`
|
||||||
|
Archived bool `json:"archived"`
|
||||||
|
|
||||||
Warnings []TemplateVersionWarning `json:"warnings,omitempty" enums:"DEPRECATED_PARAMETERS"`
|
Warnings []TemplateVersionWarning `json:"warnings,omitempty" enums:"DEPRECATED_PARAMETERS"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -987,6 +987,20 @@ _None_
|
||||||
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
| `service_banner` | [codersdk.ServiceBannerConfig](#codersdkservicebannerconfig) | false | | |
|
||||||
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
| `support_links` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | |
|
||||||
|
|
||||||
|
## codersdk.ArchiveTemplateVersionsRequest
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"all": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|
| ----- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
|
||||||
|
| `all` | boolean | false | | By default, only failed versions are archived. Set this to true to archive all unused versions regardless of job status. |
|
||||||
|
|
||||||
## codersdk.AssignableRoles
|
## codersdk.AssignableRoles
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
@ -4709,6 +4723,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -4748,6 +4763,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||||
|
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
| ----------------- | --------------------------------------------------------------------------- | -------- | ------------ | ----------- |
|
| ----------------- | --------------------------------------------------------------------------- | -------- | ------------ | ----------- |
|
||||||
|
| `archived` | boolean | false | | |
|
||||||
| `created_at` | string | false | | |
|
| `created_at` | string | false | | |
|
||||||
| `created_by` | [codersdk.MinimalUser](#codersdkminimaluser) | false | | |
|
| `created_by` | [codersdk.MinimalUser](#codersdkminimaluser) | false | | |
|
||||||
| `id` | string | false | | |
|
| `id` | string | false | | |
|
||||||
|
|
|
@ -373,6 +373,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -443,6 +444,7 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/templat
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -537,6 +539,7 @@ curl -X POST http://coder-server:8080/api/v2/organizations/{organization}/templa
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -838,6 +841,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions \
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -887,6 +891,7 @@ Status Code **200**
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
| -------------------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- |
|
| -------------------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||||
| `[array item]` | array | false | | |
|
| `[array item]` | array | false | | |
|
||||||
|
| `» archived` | boolean | false | | |
|
||||||
| `» created_at` | string(date-time) | false | | |
|
| `» created_at` | string(date-time) | false | | |
|
||||||
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
||||||
| `»» avatar_url` | string(uri) | false | | |
|
| `»» avatar_url` | string(uri) | false | | |
|
||||||
|
@ -984,6 +989,60 @@ curl -X PATCH http://coder-server:8080/api/v2/templates/{template}/versions \
|
||||||
|
|
||||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Archive template unused versions by template id
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X POST http://coder-server:8080/api/v2/templates/{template}/versions/archive \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /templates/{template}/versions/archive`
|
||||||
|
|
||||||
|
> Body parameter
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"all": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| ---------- | ---- | -------------------------------------------------------------------------------------------- | -------- | --------------- |
|
||||||
|
| `template` | path | string(uuid) | true | Template ID |
|
||||||
|
| `body` | body | [codersdk.ArchiveTemplateVersionsRequest](schemas.md#codersdkarchivetemplateversionsrequest) | true | Archive request |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 200 Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"message": "string",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"field": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
|
||||||
|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
## Get template version by template ID and name
|
## Get template version by template ID and name
|
||||||
|
|
||||||
### Code samples
|
### Code samples
|
||||||
|
@ -1011,6 +1070,7 @@ curl -X GET http://coder-server:8080/api/v2/templates/{template}/versions/{templ
|
||||||
```json
|
```json
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -1060,6 +1120,7 @@ Status Code **200**
|
||||||
| Name | Type | Required | Restrictions | Description |
|
| Name | Type | Required | Restrictions | Description |
|
||||||
| -------------------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- |
|
| -------------------- | ------------------------------------------------------------------------ | -------- | ------------ | ----------- |
|
||||||
| `[array item]` | array | false | | |
|
| `[array item]` | array | false | | |
|
||||||
|
| `» archived` | boolean | false | | |
|
||||||
| `» created_at` | string(date-time) | false | | |
|
| `» created_at` | string(date-time) | false | | |
|
||||||
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
| `» created_by` | [codersdk.MinimalUser](schemas.md#codersdkminimaluser) | false | | |
|
||||||
| `»» avatar_url` | string(uri) | false | | |
|
| `»» avatar_url` | string(uri) | false | | |
|
||||||
|
@ -1128,6 +1189,7 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion} \
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -1207,6 +1269,7 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"archived": true,
|
||||||
"created_at": "2019-08-24T14:15:22Z",
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
"created_by": {
|
"created_by": {
|
||||||
"avatar_url": "http://example.com",
|
"avatar_url": "http://example.com",
|
||||||
|
@ -1250,6 +1313,50 @@ curl -X PATCH http://coder-server:8080/api/v2/templateversions/{templateversion}
|
||||||
|
|
||||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Archive template version
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/archive \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /templateversions/{templateversion}/archive`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| ----------------- | ---- | ------------ | -------- | ------------------- |
|
||||||
|
| `templateversion` | path | string(uuid) | true | Template version ID |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 200 Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"message": "string",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"field": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
|
||||||
|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
## Cancel template version by ID
|
## Cancel template version by ID
|
||||||
|
|
||||||
### Code samples
|
### Code samples
|
||||||
|
@ -2342,6 +2449,50 @@ curl -X GET http://coder-server:8080/api/v2/templateversions/{templateversion}/s
|
||||||
|
|
||||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Unarchive template version
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X POST http://coder-server:8080/api/v2/templateversions/{templateversion}/unarchive \
|
||||||
|
-H 'Accept: application/json' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /templateversions/{templateversion}/unarchive`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
| ----------------- | ---- | ------------ | -------- | ------------------- |
|
||||||
|
| `templateversion` | path | string(uuid) | true | Template version ID |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 200 Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"message": "string",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"detail": "string",
|
||||||
|
"field": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
| ------ | ------------------------------------------------------- | ----------- | ------------------------------------------------ |
|
||||||
|
| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | [codersdk.Response](schemas.md#codersdkresponse) |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
## Get template variables by template version
|
## Get template variables by template version
|
||||||
|
|
||||||
### Code samples
|
### Code samples
|
||||||
|
|
|
@ -35,6 +35,7 @@ Templates are written in standard Terraform and describe the infrastructure for
|
||||||
|
|
||||||
| Name | Purpose |
|
| Name | Purpose |
|
||||||
| ------------------------------------------------ | ------------------------------------------------------------------------------ |
|
| ------------------------------------------------ | ------------------------------------------------------------------------------ |
|
||||||
|
| [<code>archive</code>](./templates_archive.md) | Archive unused or failed template versions from a given template(s) |
|
||||||
| [<code>create</code>](./templates_create.md) | Create a template from the current directory or as specified by flag |
|
| [<code>create</code>](./templates_create.md) | Create a template from the current directory or as specified by flag |
|
||||||
| [<code>delete</code>](./templates_delete.md) | Delete templates |
|
| [<code>delete</code>](./templates_delete.md) | Delete templates |
|
||||||
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
|
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||||
|
|
||||||
|
# templates archive
|
||||||
|
|
||||||
|
Archive unused or failed template versions from a given template(s)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```console
|
||||||
|
coder templates archive [flags] [template-name...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### --all
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Include all unused template versions. By default, only failed template versions are archived.
|
||||||
|
|
||||||
|
### -y, --yes
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Bypass prompts.
|
|
@ -24,6 +24,8 @@ coder templates versions
|
||||||
|
|
||||||
## Subcommands
|
## Subcommands
|
||||||
|
|
||||||
| Name | Purpose |
|
| Name | Purpose |
|
||||||
| ------------------------------------------------- | ----------------------------------------------- |
|
| ----------------------------------------------------------- | ----------------------------------------------- |
|
||||||
| [<code>list</code>](./templates_versions_list.md) | List all the versions of the specified template |
|
| [<code>archive</code>](./templates_versions_archive.md) | Archive a template version(s). |
|
||||||
|
| [<code>list</code>](./templates_versions_list.md) | List all the versions of the specified template |
|
||||||
|
| [<code>unarchive</code>](./templates_versions_unarchive.md) | Unarchive a template version(s). |
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||||
|
|
||||||
|
# templates versions archive
|
||||||
|
|
||||||
|
Archive a template version(s).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```console
|
||||||
|
coder templates versions archive [flags] <template-name> [template-version-names...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### -y, --yes
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Bypass prompts.
|
|
@ -17,9 +17,17 @@ coder templates versions list [flags] <template>
|
||||||
| | |
|
| | |
|
||||||
| ------- | ----------------------------------------------------- |
|
| ------- | ----------------------------------------------------- |
|
||||||
| Type | <code>string-array</code> |
|
| Type | <code>string-array</code> |
|
||||||
| Default | <code>name,created at,created by,status,active</code> |
|
| Default | <code>Name,Created At,Created By,Status,Active</code> |
|
||||||
|
|
||||||
Columns to display in table output. Available columns: name, created at, created by, status, active.
|
Columns to display in table output. Available columns: name, created at, created by, status, active, archived.
|
||||||
|
|
||||||
|
### --include-archived
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Include archived versions in the result list.
|
||||||
|
|
||||||
### -o, --output
|
### -o, --output
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||||
|
|
||||||
|
# templates versions unarchive
|
||||||
|
|
||||||
|
Unarchive a template version(s).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```console
|
||||||
|
coder templates versions unarchive [flags] <template-name> [template-version-names...]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### -y, --yes
|
||||||
|
|
||||||
|
| | |
|
||||||
|
| ---- | ----------------- |
|
||||||
|
| Type | <code>bool</code> |
|
||||||
|
|
||||||
|
Bypass prompts.
|
|
@ -811,6 +811,11 @@
|
||||||
"description": "Manage templates",
|
"description": "Manage templates",
|
||||||
"path": "cli/templates.md"
|
"path": "cli/templates.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "templates archive",
|
||||||
|
"description": "Archive unused or failed template versions from a given template(s)",
|
||||||
|
"path": "cli/templates_archive.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "templates create",
|
"title": "templates create",
|
||||||
"description": "Create a template from the current directory or as specified by flag",
|
"description": "Create a template from the current directory or as specified by flag",
|
||||||
|
@ -851,11 +856,21 @@
|
||||||
"description": "Manage different versions of the specified template",
|
"description": "Manage different versions of the specified template",
|
||||||
"path": "cli/templates_versions.md"
|
"path": "cli/templates_versions.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "templates versions archive",
|
||||||
|
"description": "Archive a template version(s).",
|
||||||
|
"path": "cli/templates_versions_archive.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "templates versions list",
|
"title": "templates versions list",
|
||||||
"description": "List all the versions of the specified template",
|
"description": "List all the versions of the specified template",
|
||||||
"path": "cli/templates_versions_list.md"
|
"path": "cli/templates_versions_list.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "templates versions unarchive",
|
||||||
|
"description": "Unarchive a template version(s).",
|
||||||
|
"path": "cli/templates_versions_unarchive.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "tokens",
|
"title": "tokens",
|
||||||
"description": "Manage personal access tokens",
|
"description": "Manage personal access tokens",
|
||||||
|
|
|
@ -52,6 +52,17 @@ export interface AppearanceConfig {
|
||||||
readonly support_links?: LinkConfig[];
|
readonly support_links?: LinkConfig[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// From codersdk/templates.go
|
||||||
|
export interface ArchiveTemplateVersionsRequest {
|
||||||
|
readonly all: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From codersdk/templates.go
|
||||||
|
export interface ArchiveTemplateVersionsResponse {
|
||||||
|
readonly template_id: string;
|
||||||
|
readonly archived_ids: string[];
|
||||||
|
}
|
||||||
|
|
||||||
// From codersdk/roles.go
|
// From codersdk/roles.go
|
||||||
export interface AssignableRoles extends Role {
|
export interface AssignableRoles extends Role {
|
||||||
readonly assignable: boolean;
|
readonly assignable: boolean;
|
||||||
|
@ -1012,6 +1023,7 @@ export interface TemplateVersion {
|
||||||
readonly job: ProvisionerJob;
|
readonly job: ProvisionerJob;
|
||||||
readonly readme: string;
|
readonly readme: string;
|
||||||
readonly created_by: MinimalUser;
|
readonly created_by: MinimalUser;
|
||||||
|
readonly archived: boolean;
|
||||||
readonly warnings?: TemplateVersionWarning[];
|
readonly warnings?: TemplateVersionWarning[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1067,6 +1079,7 @@ export interface TemplateVersionVariable {
|
||||||
// From codersdk/templates.go
|
// From codersdk/templates.go
|
||||||
export interface TemplateVersionsByTemplateRequest extends Pagination {
|
export interface TemplateVersionsByTemplateRequest extends Pagination {
|
||||||
readonly template_id: string;
|
readonly template_id: string;
|
||||||
|
readonly include_archived: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/apikey.go
|
// From codersdk/apikey.go
|
||||||
|
|
|
@ -379,6 +379,7 @@ You can add instructions here
|
||||||
|
|
||||||
[Some link info](https://coder.com)`,
|
[Some link info](https://coder.com)`,
|
||||||
created_by: MockUser,
|
created_by: MockUser,
|
||||||
|
archived: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockTemplateVersion2: TypesGen.TemplateVersion = {
|
export const MockTemplateVersion2: TypesGen.TemplateVersion = {
|
||||||
|
@ -397,6 +398,7 @@ You can add instructions here
|
||||||
|
|
||||||
[Some link info](https://coder.com)`,
|
[Some link info](https://coder.com)`,
|
||||||
created_by: MockUser,
|
created_by: MockUser,
|
||||||
|
archived: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
||||||
|
@ -410,6 +412,7 @@ export const MockTemplateVersion3: TypesGen.TemplateVersion = {
|
||||||
readme: "README",
|
readme: "README",
|
||||||
created_by: MockUser,
|
created_by: MockUser,
|
||||||
warnings: ["UNSUPPORTED_WORKSPACES"],
|
warnings: ["UNSUPPORTED_WORKSPACES"],
|
||||||
|
archived: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MockTemplate: TypesGen.Template = {
|
export const MockTemplate: TypesGen.Template = {
|
||||||
|
|
Loading…
Reference in New Issue