mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: deprecate template create command in favor of template push (#11390)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package cliui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
func DeprecationWarning(message string) clibase.MiddlewareFunc {
|
||||
return func(next clibase.HandlerFunc) clibase.HandlerFunc {
|
||||
return func(i *clibase.Invocation) error {
|
||||
_, _ = fmt.Fprintln(i.Stdout, "\n"+pretty.Sprint(DefaultStyles.Wrap,
|
||||
pretty.Sprint(
|
||||
DefaultStyles.Warn,
|
||||
"DEPRECATION WARNING: This command will be removed in a future release."+"\n"+message+"\n"),
|
||||
))
|
||||
return next(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-109
@@ -1,15 +1,11 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/pretty"
|
||||
@@ -40,9 +36,13 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "create [name]",
|
||||
Short: "Create a template from the current directory or as specified by flag",
|
||||
Short: "DEPRECATED: Create a template from the current directory or as specified by flag",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireRangeArgs(0, 1),
|
||||
cliui.DeprecationWarning(
|
||||
"Use `coder templates push` command for creating and updating templates. \n"+
|
||||
"Use `coder templates edit` command for editing template settings. ",
|
||||
),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
@@ -253,107 +253,3 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
|
||||
cmd.Options = append(cmd.Options, uploadFlags.options()...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type createValidTemplateVersionArgs struct {
|
||||
Name string
|
||||
Message string
|
||||
Client *codersdk.Client
|
||||
Organization codersdk.Organization
|
||||
Provisioner codersdk.ProvisionerType
|
||||
FileID uuid.UUID
|
||||
|
||||
// Template is only required if updating a template's active version.
|
||||
Template *codersdk.Template
|
||||
// ReuseParameters will attempt to reuse params from the Template field
|
||||
// before prompting the user. Set to false to always prompt for param
|
||||
// values.
|
||||
ReuseParameters bool
|
||||
ProvisionerTags map[string]string
|
||||
UserVariableValues []codersdk.VariableValue
|
||||
}
|
||||
|
||||
func createValidTemplateVersion(inv *clibase.Invocation, args createValidTemplateVersionArgs) (*codersdk.TemplateVersion, error) {
|
||||
client := args.Client
|
||||
|
||||
req := codersdk.CreateTemplateVersionRequest{
|
||||
Name: args.Name,
|
||||
Message: args.Message,
|
||||
StorageMethod: codersdk.ProvisionerStorageMethodFile,
|
||||
FileID: args.FileID,
|
||||
Provisioner: args.Provisioner,
|
||||
ProvisionerTags: args.ProvisionerTags,
|
||||
UserVariableValues: args.UserVariableValues,
|
||||
}
|
||||
if args.Template != nil {
|
||||
req.TemplateID = args.Template.ID
|
||||
}
|
||||
version, err := client.CreateTemplateVersion(inv.Context(), args.Organization.ID, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
|
||||
Fetch: func() (codersdk.ProvisionerJob, error) {
|
||||
version, err := client.TemplateVersion(inv.Context(), version.ID)
|
||||
return version.Job, err
|
||||
},
|
||||
Cancel: func() error {
|
||||
return client.CancelTemplateVersion(inv.Context(), version.ID)
|
||||
},
|
||||
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
|
||||
return client.TemplateVersionLogsAfter(inv.Context(), version.ID, 0)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
var jobErr *cliui.ProvisionerJobError
|
||||
if errors.As(err, &jobErr) && !codersdk.JobIsMissingParameterErrorCode(jobErr.Code) {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
version, err = client.TemplateVersion(inv.Context(), version.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if version.Job.Status != codersdk.ProvisionerJobSucceeded {
|
||||
return nil, xerrors.New(version.Job.Error)
|
||||
}
|
||||
|
||||
resources, err := client.TemplateVersionResources(inv.Context(), version.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only display the resources on the start transition, to avoid listing them more than once.
|
||||
var startResources []codersdk.WorkspaceResource
|
||||
for _, r := range resources {
|
||||
if r.Transition == codersdk.WorkspaceTransitionStart {
|
||||
startResources = append(startResources, r)
|
||||
}
|
||||
}
|
||||
err = cliui.WorkspaceResources(inv.Stdout, startResources, cliui.WorkspaceResourcesOptions{
|
||||
HideAgentState: true,
|
||||
HideAccess: true,
|
||||
Title: "Template Preview",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("preview template resources: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func ParseProvisionerTags(rawTags []string) (map[string]string, error) {
|
||||
tags := map[string]string{}
|
||||
for _, rawTag := range rawTags {
|
||||
parts := strings.SplitN(rawTag, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
return nil, xerrors.Errorf("invalid tag format for %q. must be key=value", rawTag)
|
||||
}
|
||||
tags[parts[0]] = parts[1]
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
@@ -19,54 +19,6 @@ import (
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func completeWithAgent() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemplateCreate(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
@@ -418,15 +370,3 @@ func TestTemplateCreate(t *testing.T) {
|
||||
require.Contains(t, err.Error(), "your deployment appears to be an AGPL deployment, so you cannot set enterprise-only flags")
|
||||
})
|
||||
}
|
||||
|
||||
// Need this for Windows because of a known issue with Go:
|
||||
// https://github.com/golang/go/issues/52986
|
||||
func removeTmpDirUntilSuccessAfterTest(t *testing.T, tempDir string) {
|
||||
t.Helper()
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll(tempDir)
|
||||
for err != nil {
|
||||
err = os.RemoveAll(tempDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
allowUserAutostop bool
|
||||
requireActiveVersion bool
|
||||
deprecationMessage string
|
||||
disableEveryone bool
|
||||
)
|
||||
client := new(codersdk.Client)
|
||||
|
||||
@@ -162,6 +163,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
AllowUserAutostop: allowUserAutostop,
|
||||
RequireActiveVersion: requireActiveVersion,
|
||||
DeprecationMessage: deprecated,
|
||||
DisableEveryoneGroupAccess: disableEveryone,
|
||||
}
|
||||
|
||||
_, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req)
|
||||
@@ -292,6 +294,13 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
|
||||
Value: clibase.BoolOf(&requireActiveVersion),
|
||||
Default: "false",
|
||||
},
|
||||
{
|
||||
Flag: "private",
|
||||
Description: "Disable the default behavior of granting template access to the 'everyone' group. " +
|
||||
"The template permissions must be updated to allow non-admin users to use this template.",
|
||||
Value: clibase.BoolOf(&disableEveryone),
|
||||
Default: "false",
|
||||
},
|
||||
cliui.SkipPromptOption(),
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -113,7 +113,7 @@ func (*RootCmd) templateInit() *clibase.Cmd {
|
||||
inv.Stdout,
|
||||
pretty.Sprint(
|
||||
cliui.DefaultStyles.Code,
|
||||
"cd "+relPath+" && coder templates create"),
|
||||
"cd "+relPath+" && coder templates push"),
|
||||
)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "\nExamples provide a starting point and are expected to be edited! 🎨"))
|
||||
return nil
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ func (r *RootCmd) templateList() *clibase.Cmd {
|
||||
|
||||
if len(templates) == 0 {
|
||||
_, _ = fmt.Fprintf(inv.Stderr, "%s No templates found in %s! Create one:\n\n", Caret, color.HiWhiteString(organization.Name))
|
||||
_, _ = fmt.Fprintln(inv.Stderr, color.HiMagentaString(" $ coder templates create <directory>\n"))
|
||||
_, _ = fmt.Fprintln(inv.Stderr, color.HiMagentaString(" $ coder templates push <directory>\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+281
-176
@@ -2,25 +2,210 @@ package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/pretty"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/coder/v2/cli/cliui"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
// templateUploadFlags is shared by `templates create` and `templates push`.
|
||||
func (r *RootCmd) templatePush() *clibase.Cmd {
|
||||
var (
|
||||
versionName string
|
||||
provisioner string
|
||||
workdir string
|
||||
variablesFile string
|
||||
commandLineVariables []string
|
||||
alwaysPrompt bool
|
||||
provisionerTags []string
|
||||
uploadFlags templateUploadFlags
|
||||
activate bool
|
||||
)
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "push [template]",
|
||||
Short: "Create or update a template from the current directory or as specified by flag",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireRangeArgs(0, 1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
uploadFlags.setWorkdir(workdir)
|
||||
|
||||
organization, err := CurrentOrganization(inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
name, err := uploadFlags.templateName(inv.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(name) >= 32 {
|
||||
return xerrors.Errorf("Template name must be less than 32 characters")
|
||||
}
|
||||
|
||||
var createTemplate bool
|
||||
template, err := client.TemplateByName(inv.Context(), organization.ID, name)
|
||||
if err != nil {
|
||||
var apiError *codersdk.Error
|
||||
if errors.As(err, &apiError) && apiError.StatusCode() != http.StatusNotFound {
|
||||
return err
|
||||
}
|
||||
// Template doesn't exist, create it.
|
||||
createTemplate = true
|
||||
}
|
||||
|
||||
err = uploadFlags.checkForLockfile(inv)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("check for lockfile: %w", err)
|
||||
}
|
||||
|
||||
message := uploadFlags.templateMessage(inv)
|
||||
|
||||
resp, err := uploadFlags.upload(inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags, err := ParseProvisionerTags(provisionerTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userVariableValues, err := ParseUserVariableValues(
|
||||
variablesFile,
|
||||
commandLineVariables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := createValidTemplateVersionArgs{
|
||||
Message: message,
|
||||
Client: client,
|
||||
Organization: organization,
|
||||
Provisioner: codersdk.ProvisionerType(provisioner),
|
||||
FileID: resp.ID,
|
||||
ProvisionerTags: tags,
|
||||
UserVariableValues: userVariableValues,
|
||||
}
|
||||
|
||||
if !createTemplate {
|
||||
args.Name = versionName
|
||||
args.Template = &template
|
||||
args.ReuseParameters = !alwaysPrompt
|
||||
}
|
||||
|
||||
job, err := createValidTemplateVersion(inv, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if job.Job.Status != codersdk.ProvisionerJobSucceeded {
|
||||
return xerrors.Errorf("job failed: %s", job.Job.Status)
|
||||
}
|
||||
|
||||
if createTemplate {
|
||||
_, err = client.CreateTemplate(inv.Context(), organization.ID, codersdk.CreateTemplateRequest{
|
||||
Name: name,
|
||||
VersionID: job.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(
|
||||
inv.Stdout, "\n"+cliui.Wrap(
|
||||
"The "+cliui.Keyword(name)+" template has been created at "+cliui.Timestamp(time.Now())+"! "+
|
||||
"Developers can provision a workspace with this template using:")+"\n")
|
||||
} else if activate {
|
||||
err = client.UpdateActiveTemplateVersion(inv.Context(), template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||
ID: job.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout, "Updated version at %s!\n", pretty.Sprint(cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp)))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Options = clibase.OptionSet{
|
||||
{
|
||||
Flag: "test.provisioner",
|
||||
Description: "Customize the provisioner backend.",
|
||||
Default: "terraform",
|
||||
Value: clibase.StringOf(&provisioner),
|
||||
// This is for testing!
|
||||
Hidden: true,
|
||||
},
|
||||
{
|
||||
Flag: "test.workdir",
|
||||
Description: "Customize the working directory.",
|
||||
Default: "",
|
||||
Value: clibase.StringOf(&workdir),
|
||||
// This is for testing!
|
||||
Hidden: true,
|
||||
},
|
||||
{
|
||||
Flag: "variables-file",
|
||||
Description: "Specify a file path with values for Terraform-managed variables.",
|
||||
Value: clibase.StringOf(&variablesFile),
|
||||
},
|
||||
{
|
||||
Flag: "variable",
|
||||
Description: "Specify a set of values for Terraform-managed variables.",
|
||||
Value: clibase.StringArrayOf(&commandLineVariables),
|
||||
},
|
||||
{
|
||||
Flag: "var",
|
||||
Description: "Alias of --variable.",
|
||||
Value: clibase.StringArrayOf(&commandLineVariables),
|
||||
},
|
||||
{
|
||||
Flag: "provisioner-tag",
|
||||
Description: "Specify a set of tags to target provisioner daemons.",
|
||||
Value: clibase.StringArrayOf(&provisionerTags),
|
||||
},
|
||||
{
|
||||
Flag: "name",
|
||||
Description: "Specify a name for the new template version. It will be automatically generated if not provided.",
|
||||
Value: clibase.StringOf(&versionName),
|
||||
},
|
||||
{
|
||||
Flag: "always-prompt",
|
||||
Description: "Always prompt all parameters. Does not pull parameter values from active template version.",
|
||||
Value: clibase.BoolOf(&alwaysPrompt),
|
||||
},
|
||||
{
|
||||
Flag: "activate",
|
||||
Description: "Whether the new template will be marked active.",
|
||||
Default: "true",
|
||||
Value: clibase.BoolOf(&activate),
|
||||
},
|
||||
cliui.SkipPromptOption(),
|
||||
}
|
||||
cmd.Options = append(cmd.Options, uploadFlags.options()...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
type templateUploadFlags struct {
|
||||
directory string
|
||||
ignoreLockfile bool
|
||||
@@ -154,188 +339,108 @@ func (pf *templateUploadFlags) templateName(args []string) (string, error) {
|
||||
return filepath.Base(absPath), nil
|
||||
}
|
||||
|
||||
func (r *RootCmd) templatePush() *clibase.Cmd {
|
||||
var (
|
||||
versionName string
|
||||
provisioner string
|
||||
workdir string
|
||||
variablesFile string
|
||||
commandLineVariables []string
|
||||
alwaysPrompt bool
|
||||
provisionerTags []string
|
||||
uploadFlags templateUploadFlags
|
||||
activate bool
|
||||
create bool
|
||||
)
|
||||
client := new(codersdk.Client)
|
||||
cmd := &clibase.Cmd{
|
||||
Use: "push [template]",
|
||||
Short: "Push a new template version from the current directory or as specified by flag",
|
||||
Middleware: clibase.Chain(
|
||||
clibase.RequireRangeArgs(0, 1),
|
||||
r.InitClient(client),
|
||||
),
|
||||
Handler: func(inv *clibase.Invocation) error {
|
||||
uploadFlags.setWorkdir(workdir)
|
||||
type createValidTemplateVersionArgs struct {
|
||||
Name string
|
||||
Message string
|
||||
Client *codersdk.Client
|
||||
Organization codersdk.Organization
|
||||
Provisioner codersdk.ProvisionerType
|
||||
FileID uuid.UUID
|
||||
|
||||
organization, err := CurrentOrganization(inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Template is only required if updating a template's active version.
|
||||
Template *codersdk.Template
|
||||
// ReuseParameters will attempt to reuse params from the Template field
|
||||
// before prompting the user. Set to false to always prompt for param
|
||||
// values.
|
||||
ReuseParameters bool
|
||||
ProvisionerTags map[string]string
|
||||
UserVariableValues []codersdk.VariableValue
|
||||
}
|
||||
|
||||
name, err := uploadFlags.templateName(inv.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func createValidTemplateVersion(inv *clibase.Invocation, args createValidTemplateVersionArgs) (*codersdk.TemplateVersion, error) {
|
||||
client := args.Client
|
||||
|
||||
var createTemplate bool
|
||||
template, err := client.TemplateByName(inv.Context(), organization.ID, name)
|
||||
if err != nil {
|
||||
if !create {
|
||||
return err
|
||||
}
|
||||
createTemplate = true
|
||||
}
|
||||
|
||||
err = uploadFlags.checkForLockfile(inv)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("check for lockfile: %w", err)
|
||||
}
|
||||
|
||||
message := uploadFlags.templateMessage(inv)
|
||||
|
||||
resp, err := uploadFlags.upload(inv, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tags, err := ParseProvisionerTags(provisionerTags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userVariableValues, err := ParseUserVariableValues(
|
||||
variablesFile,
|
||||
commandLineVariables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
args := createValidTemplateVersionArgs{
|
||||
Message: message,
|
||||
Client: client,
|
||||
Organization: organization,
|
||||
Provisioner: codersdk.ProvisionerType(provisioner),
|
||||
FileID: resp.ID,
|
||||
ProvisionerTags: tags,
|
||||
UserVariableValues: userVariableValues,
|
||||
}
|
||||
|
||||
if !createTemplate {
|
||||
args.Name = versionName
|
||||
args.Template = &template
|
||||
args.ReuseParameters = !alwaysPrompt
|
||||
}
|
||||
|
||||
job, err := createValidTemplateVersion(inv, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if job.Job.Status != codersdk.ProvisionerJobSucceeded {
|
||||
return xerrors.Errorf("job failed: %s", job.Job.Status)
|
||||
}
|
||||
|
||||
if createTemplate {
|
||||
_, err = client.CreateTemplate(inv.Context(), organization.ID, codersdk.CreateTemplateRequest{
|
||||
Name: name,
|
||||
VersionID: job.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(
|
||||
inv.Stdout, "\n"+cliui.Wrap(
|
||||
"The "+cliui.Keyword(name)+" template has been created at "+cliui.Timestamp(time.Now())+"! "+
|
||||
"Developers can provision a workspace with this template using:")+"\n")
|
||||
} else if activate {
|
||||
err = client.UpdateActiveTemplateVersion(inv.Context(), template.ID, codersdk.UpdateActiveTemplateVersion{
|
||||
ID: job.ID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(inv.Stdout, "Updated version at %s!\n", pretty.Sprint(cliui.DefaultStyles.DateTimeStamp, time.Now().Format(time.Stamp)))
|
||||
return nil
|
||||
},
|
||||
req := codersdk.CreateTemplateVersionRequest{
|
||||
Name: args.Name,
|
||||
Message: args.Message,
|
||||
StorageMethod: codersdk.ProvisionerStorageMethodFile,
|
||||
FileID: args.FileID,
|
||||
Provisioner: args.Provisioner,
|
||||
ProvisionerTags: args.ProvisionerTags,
|
||||
UserVariableValues: args.UserVariableValues,
|
||||
}
|
||||
if args.Template != nil {
|
||||
req.TemplateID = args.Template.ID
|
||||
}
|
||||
version, err := client.CreateTemplateVersion(inv.Context(), args.Organization.ID, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.Options = clibase.OptionSet{
|
||||
{
|
||||
Flag: "test.provisioner",
|
||||
Description: "Customize the provisioner backend.",
|
||||
Default: "terraform",
|
||||
Value: clibase.StringOf(&provisioner),
|
||||
// This is for testing!
|
||||
Hidden: true,
|
||||
err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
|
||||
Fetch: func() (codersdk.ProvisionerJob, error) {
|
||||
version, err := client.TemplateVersion(inv.Context(), version.ID)
|
||||
return version.Job, err
|
||||
},
|
||||
{
|
||||
Flag: "test.workdir",
|
||||
Description: "Customize the working directory.",
|
||||
Default: "",
|
||||
Value: clibase.StringOf(&workdir),
|
||||
// This is for testing!
|
||||
Hidden: true,
|
||||
Cancel: func() error {
|
||||
return client.CancelTemplateVersion(inv.Context(), version.ID)
|
||||
},
|
||||
{
|
||||
Flag: "variables-file",
|
||||
Description: "Specify a file path with values for Terraform-managed variables.",
|
||||
Value: clibase.StringOf(&variablesFile),
|
||||
Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
|
||||
return client.TemplateVersionLogsAfter(inv.Context(), version.ID, 0)
|
||||
},
|
||||
{
|
||||
Flag: "variable",
|
||||
Description: "Specify a set of values for Terraform-managed variables.",
|
||||
Value: clibase.StringArrayOf(&commandLineVariables),
|
||||
},
|
||||
{
|
||||
Flag: "var",
|
||||
Description: "Alias of --variable.",
|
||||
Value: clibase.StringArrayOf(&commandLineVariables),
|
||||
},
|
||||
{
|
||||
Flag: "provisioner-tag",
|
||||
Description: "Specify a set of tags to target provisioner daemons.",
|
||||
Value: clibase.StringArrayOf(&provisionerTags),
|
||||
},
|
||||
{
|
||||
Flag: "name",
|
||||
Description: "Specify a name for the new template version. It will be automatically generated if not provided.",
|
||||
Value: clibase.StringOf(&versionName),
|
||||
},
|
||||
{
|
||||
Flag: "always-prompt",
|
||||
Description: "Always prompt all parameters. Does not pull parameter values from active template version.",
|
||||
Value: clibase.BoolOf(&alwaysPrompt),
|
||||
},
|
||||
{
|
||||
Flag: "activate",
|
||||
Description: "Whether the new template will be marked active.",
|
||||
Default: "true",
|
||||
Value: clibase.BoolOf(&activate),
|
||||
},
|
||||
{
|
||||
Flag: "create",
|
||||
Description: "Create the template if it does not exist.",
|
||||
Default: "false",
|
||||
Value: clibase.BoolOf(&create),
|
||||
},
|
||||
cliui.SkipPromptOption(),
|
||||
})
|
||||
if err != nil {
|
||||
var jobErr *cliui.ProvisionerJobError
|
||||
if errors.As(err, &jobErr) && !codersdk.JobIsMissingParameterErrorCode(jobErr.Code) {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
cmd.Options = append(cmd.Options, uploadFlags.options()...)
|
||||
return cmd
|
||||
version, err = client.TemplateVersion(inv.Context(), version.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if version.Job.Status != codersdk.ProvisionerJobSucceeded {
|
||||
return nil, xerrors.New(version.Job.Error)
|
||||
}
|
||||
|
||||
resources, err := client.TemplateVersionResources(inv.Context(), version.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only display the resources on the start transition, to avoid listing them more than once.
|
||||
var startResources []codersdk.WorkspaceResource
|
||||
for _, r := range resources {
|
||||
if r.Transition == codersdk.WorkspaceTransitionStart {
|
||||
startResources = append(startResources, r)
|
||||
}
|
||||
}
|
||||
err = cliui.WorkspaceResources(inv.Stdout, startResources, cliui.WorkspaceResourcesOptions{
|
||||
HideAgentState: true,
|
||||
HideAccess: true,
|
||||
Title: "Template Preview",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("preview template resources: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func ParseProvisionerTags(rawTags []string) (map[string]string, error) {
|
||||
tags := map[string]string{}
|
||||
for _, rawTag := range rawTags {
|
||||
parts := strings.SplitN(rawTag, "=", 2)
|
||||
if len(parts) < 2 {
|
||||
return nil, xerrors.Errorf("invalid tag format for %q. must be key=value", rawTag)
|
||||
}
|
||||
tags[parts[0]] = parts[1]
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// prettyDirectoryPath returns a prettified path when inside the users
|
||||
|
||||
@@ -679,7 +679,6 @@ func TestTemplatePush(t *testing.T) {
|
||||
templateName,
|
||||
"--directory", source,
|
||||
"--test.provisioner", string(database.ProvisionerTypeEcho),
|
||||
"--create",
|
||||
}
|
||||
inv, root := clitest.New(t, args...)
|
||||
clitest.SetupConfig(t, templateAdmin, root)
|
||||
@@ -726,3 +725,63 @@ func createEchoResponsesWithTemplateVariables(templateVariables []*proto.Templat
|
||||
ProvisionApply: echo.ApplyComplete,
|
||||
}
|
||||
}
|
||||
|
||||
func completeWithAgent() *echo.Responses {
|
||||
return &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
ProvisionPlan: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Plan{
|
||||
Plan: &proto.PlanComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ProvisionApply: []*proto.Response{
|
||||
{
|
||||
Type: &proto.Response_Apply{
|
||||
Apply: &proto.ApplyComplete{
|
||||
Resources: []*proto.Resource{
|
||||
{
|
||||
Type: "compute",
|
||||
Name: "main",
|
||||
Agents: []*proto.Agent{
|
||||
{
|
||||
Name: "smith",
|
||||
OperatingSystem: "linux",
|
||||
Architecture: "i386",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Need this for Windows because of a known issue with Go:
|
||||
// https://github.com/golang/go/issues/52986
|
||||
func removeTmpDirUntilSuccessAfterTest(t *testing.T, tempDir string) {
|
||||
t.Helper()
|
||||
t.Cleanup(func() {
|
||||
err := os.RemoveAll(tempDir)
|
||||
for err != nil {
|
||||
err = os.RemoveAll(tempDir)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+1
-5
@@ -17,16 +17,12 @@ func (r *RootCmd) templates() *clibase.Cmd {
|
||||
Use: "templates",
|
||||
Short: "Manage templates",
|
||||
Long: "Templates are written in standard Terraform and describe the infrastructure for workspaces\n" + formatExamples(
|
||||
example{
|
||||
Description: "Create a template for developers to create workspaces",
|
||||
Command: "coder templates create",
|
||||
},
|
||||
example{
|
||||
Description: "Make changes to your template, and plan the changes",
|
||||
Command: "coder templates plan my-template",
|
||||
},
|
||||
example{
|
||||
Description: "Push an update to the template. Your developers can update their workspaces",
|
||||
Description: "Create or push an update to the template. Your developers can update their workspaces",
|
||||
Command: "coder templates push my-template",
|
||||
},
|
||||
),
|
||||
|
||||
+4
-8
@@ -9,15 +9,11 @@ USAGE:
|
||||
|
||||
Templates are written in standard Terraform and describe the infrastructure
|
||||
for workspaces
|
||||
- Create a template for developers to create workspaces:
|
||||
|
||||
$ coder templates create
|
||||
|
||||
- Make changes to your template, and plan the changes:
|
||||
|
||||
$ coder templates plan my-template
|
||||
|
||||
- Push an update to the template. Your developers can update their
|
||||
- Create or push an update to the template. Your developers can update their
|
||||
workspaces:
|
||||
|
||||
$ coder templates push my-template
|
||||
@@ -25,15 +21,15 @@ USAGE:
|
||||
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
|
||||
flag
|
||||
create DEPRECATED: Create a template from the current directory or as
|
||||
specified by flag
|
||||
delete Delete templates
|
||||
edit Edit the metadata of a template by name.
|
||||
init Get started with a templated template.
|
||||
list List all the templates available for the organization
|
||||
pull Download the active, latest, or specified version of a template
|
||||
to a path.
|
||||
push Push a new template version from the current directory or as
|
||||
push Create or update a template from the current directory or as
|
||||
specified by flag
|
||||
versions Manage different versions of the specified template
|
||||
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@ coder v0.0.0-devel
|
||||
USAGE:
|
||||
coder templates create [flags] [name]
|
||||
|
||||
Create a template from the current directory or as specified by flag
|
||||
DEPRECATED: Create a template from the current directory or as specified by
|
||||
flag
|
||||
|
||||
OPTIONS:
|
||||
--default-ttl duration (default: 24h)
|
||||
|
||||
@@ -66,6 +66,11 @@ OPTIONS:
|
||||
--name string
|
||||
Edit the template name.
|
||||
|
||||
--private bool (default: false)
|
||||
Disable the default behavior of granting template access to the
|
||||
'everyone' group. The template permissions must be updated to allow
|
||||
non-admin users to use this template.
|
||||
|
||||
--require-active-version bool (default: false)
|
||||
Requires workspace builds to use the active template version. This
|
||||
setting does not apply to template admins. This is an enterprise-only
|
||||
|
||||
+1
-4
@@ -3,7 +3,7 @@ coder v0.0.0-devel
|
||||
USAGE:
|
||||
coder templates push [flags] [template]
|
||||
|
||||
Push a new template version from the current directory or as specified by flag
|
||||
Create or update a template from the current directory or as specified by flag
|
||||
|
||||
OPTIONS:
|
||||
--activate bool (default: true)
|
||||
@@ -13,9 +13,6 @@ OPTIONS:
|
||||
Always prompt all parameters. Does not pull parameter values from
|
||||
active template version.
|
||||
|
||||
--create bool (default: false)
|
||||
Create the template if it does not exist.
|
||||
|
||||
-d, --directory string (default: .)
|
||||
Specify the directory to create from, use '-' to read tar from stdin.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user