mirror of https://github.com/coder/coder.git
chore: deprecate template create command in favor of template push (#11390)
This commit is contained in:
parent
3d54bc06f6
commit
b21da38bea
|
@ -416,7 +416,7 @@ jobs:
|
|||
|
||||
# Create template
|
||||
cd ./.github/pr-deployments/template
|
||||
coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }} kubernetes
|
||||
coder templates push -y --variable namespace=pr${{ env.PR_NUMBER }} kubernetes
|
||||
|
||||
# Create workspace
|
||||
coder create --template="kubernetes" kube --parameter cpu=2 --parameter memory=4 --parameter home_disk_size=2 -y
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -6373,6 +6373,7 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
|
|||
tpl.DisplayName = arg.DisplayName
|
||||
tpl.Description = arg.Description
|
||||
tpl.Icon = arg.Icon
|
||||
tpl.GroupACL = arg.GroupACL
|
||||
q.templates[idx] = tpl
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6075,19 +6075,21 @@ SET
|
|||
name = $4,
|
||||
icon = $5,
|
||||
display_name = $6,
|
||||
allow_user_cancel_workspace_jobs = $7
|
||||
allow_user_cancel_workspace_jobs = $7,
|
||||
group_acl = $8
|
||||
WHERE
|
||||
id = $1
|
||||
`
|
||||
|
||||
type UpdateTemplateMetaByIDParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
Description string `db:"description" json:"description"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Icon string `db:"icon" json:"icon"`
|
||||
DisplayName string `db:"display_name" json:"display_name"`
|
||||
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"`
|
||||
GroupACL TemplateACL `db:"group_acl" json:"group_acl"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error {
|
||||
|
@ -6099,6 +6101,7 @@ func (q *sqlQuerier) UpdateTemplateMetaByID(ctx context.Context, arg UpdateTempl
|
|||
arg.Icon,
|
||||
arg.DisplayName,
|
||||
arg.AllowUserCancelWorkspaceJobs,
|
||||
arg.GroupACL,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -115,7 +115,8 @@ SET
|
|||
name = $4,
|
||||
icon = $5,
|
||||
display_name = $6,
|
||||
allow_user_cancel_workspace_jobs = $7
|
||||
allow_user_cancel_workspace_jobs = $7,
|
||||
group_acl = $8
|
||||
WHERE
|
||||
id = $1
|
||||
;
|
||||
|
|
|
@ -667,6 +667,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
|||
name = template.Name
|
||||
}
|
||||
|
||||
groupACL := template.GroupACL
|
||||
if req.DisableEveryoneGroupAccess {
|
||||
groupACL = database.TemplateACL{}
|
||||
}
|
||||
|
||||
var err error
|
||||
err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
|
||||
ID: template.ID,
|
||||
|
@ -676,6 +681,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
|
|||
Description: req.Description,
|
||||
Icon: req.Icon,
|
||||
AllowUserCancelWorkspaceJobs: req.AllowUserCancelWorkspaceJobs,
|
||||
GroupACL: groupACL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update template metadata: %w", err)
|
||||
|
|
|
@ -241,6 +241,12 @@ type UpdateTemplateMeta struct {
|
|||
// If passed an empty string, will remove the deprecated message, making
|
||||
// the template usable for new workspaces again.
|
||||
DeprecationMessage *string `json:"deprecation_message"`
|
||||
// DisableEveryoneGroupAccess allows optionally disabling the default
|
||||
// behavior of granting the 'everyone' group access to use the template.
|
||||
// If this is set to true, the template will not be available to all users,
|
||||
// and must be explicitly granted to users or groups in the permissions settings
|
||||
// of the template.
|
||||
DisableEveryoneGroupAccess bool `json:"disable_everyone_group_access"`
|
||||
}
|
||||
|
||||
type TemplateExample struct {
|
||||
|
|
|
@ -64,11 +64,11 @@ the [Helm example](#example-running-an-external-provisioner-with-helm) below.
|
|||
|
||||
# In another terminal, create/push
|
||||
# a template that requires this provisioner
|
||||
coder templates create on-prem \
|
||||
coder templates push on-prem \
|
||||
--provisioner-tag environment=on_prem
|
||||
|
||||
# Or, match the provisioner exactly
|
||||
coder templates create on-prem-chicago \
|
||||
coder templates push on-prem-chicago \
|
||||
--provisioner-tag environment=on_prem \
|
||||
--provisioner-tag data_center=chicago
|
||||
```
|
||||
|
@ -88,7 +88,7 @@ the [Helm example](#example-running-an-external-provisioner-with-helm) below.
|
|||
|
||||
# In another terminal, create/push
|
||||
# a template that requires user provisioners
|
||||
coder templates create on-prem \
|
||||
coder templates push on-prem \
|
||||
--provisioner-tag scope=user
|
||||
```
|
||||
|
||||
|
|
|
@ -18,29 +18,26 @@ coder templates
|
|||
|
||||
```console
|
||||
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 workspaces:
|
||||
- Create or push an update to the template. Your developers can update their
|
||||
workspaces:
|
||||
|
||||
$ coder templates push my-template
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
| 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>delete</code>](./templates_delete.md) | Delete templates |
|
||||
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
|
||||
| [<code>init</code>](./templates_init.md) | Get started with a templated template. |
|
||||
| [<code>list</code>](./templates_list.md) | List all the templates available for the organization |
|
||||
| [<code>pull</code>](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. |
|
||||
| [<code>push</code>](./templates_push.md) | Push a new template version from the current directory or as specified by flag |
|
||||
| [<code>versions</code>](./templates_versions.md) | Manage different versions of the specified template |
|
||||
| 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) | DEPRECATED: Create a template from the current directory or as specified by flag |
|
||||
| [<code>delete</code>](./templates_delete.md) | Delete templates |
|
||||
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
|
||||
| [<code>init</code>](./templates_init.md) | Get started with a templated template. |
|
||||
| [<code>list</code>](./templates_list.md) | List all the templates available for the organization |
|
||||
| [<code>pull</code>](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. |
|
||||
| [<code>push</code>](./templates_push.md) | Create or update a template from the current directory or as specified by flag |
|
||||
| [<code>versions</code>](./templates_versions.md) | Manage different versions of the specified template |
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# templates create
|
||||
|
||||
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
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
|
@ -130,6 +130,15 @@ Edit the template maximum time before shutdown - workspaces created from this te
|
|||
|
||||
Edit the template name.
|
||||
|
||||
### --private
|
||||
|
||||
| | |
|
||||
| ------- | ------------------ |
|
||||
| Type | <code>bool</code> |
|
||||
| Default | <code>false</code> |
|
||||
|
||||
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
|
||||
|
||||
| | |
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# templates push
|
||||
|
||||
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
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -29,15 +29,6 @@ Whether the new template will be marked active.
|
|||
|
||||
Always prompt all parameters. Does not pull parameter values from active template version.
|
||||
|
||||
### --create
|
||||
|
||||
| | |
|
||||
| ------- | ------------------ |
|
||||
| Type | <code>bool</code> |
|
||||
| Default | <code>false</code> |
|
||||
|
||||
Create the template if it does not exist.
|
||||
|
||||
### -d, --directory
|
||||
|
||||
| | |
|
||||
|
|
|
@ -322,7 +322,7 @@ Edit `main.tf` and update the following fields of the Kubernetes pod resource:
|
|||
Finally, create the template:
|
||||
|
||||
```console
|
||||
coder template create kubernetes -d .
|
||||
coder template push kubernetes -d .
|
||||
```
|
||||
|
||||
This template should be ready to use straight away.
|
||||
|
|
|
@ -892,7 +892,7 @@
|
|||
},
|
||||
{
|
||||
"title": "templates create",
|
||||
"description": "Create a template from the current directory or as specified by flag",
|
||||
"description": "DEPRECATED: Create a template from the current directory or as specified by flag",
|
||||
"path": "cli/templates_create.md"
|
||||
},
|
||||
{
|
||||
|
@ -922,7 +922,7 @@
|
|||
},
|
||||
{
|
||||
"title": "templates push",
|
||||
"description": "Push a new template version from the current directory or as specified by flag",
|
||||
"description": "Create or update a template from the current directory or as specified by flag",
|
||||
"path": "cli/templates_push.md"
|
||||
},
|
||||
{
|
||||
|
|
|
@ -128,7 +128,7 @@ Navigate to the `./azure-linux` folder where you created your template and run
|
|||
the following command to put the template on your Coder instance.
|
||||
|
||||
```shell
|
||||
coder templates create
|
||||
coder templates push
|
||||
```
|
||||
|
||||
Congrats! You can now navigate to your Coder dashboard and use this Linux on
|
||||
|
|
|
@ -52,7 +52,7 @@ Coder with Docker has the following advantages:
|
|||
cd docker
|
||||
```
|
||||
|
||||
1. Push up the template with `coder templates create`
|
||||
1. Push up the template with `coder templates push`
|
||||
|
||||
1. Open the dashboard in your browser to create your first workspace:
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ export CLUSTER_SERVICEACCOUNT_TOKEN=$(kubectl get secrets coder-v2 -n coder-work
|
|||
Create the template with these values:
|
||||
|
||||
```shell
|
||||
coder templates create \
|
||||
coder templates push \
|
||||
--variable host=$CLUSTER_ADDRESS \
|
||||
--variable cluster_ca_certificate=$CLUSTER_CA_CERTIFICATE \
|
||||
--variable token=$CLUSTER_SERVICEACCOUNT_TOKEN \
|
||||
|
@ -228,7 +228,7 @@ kubectl cluster-info
|
|||
# Get cluster CA and token (base64 encoded)
|
||||
kubectl get secrets coder-service-account-token -n coder-workspaces -o jsonpath="{.data}"
|
||||
|
||||
coder templates create \
|
||||
coder templates push \
|
||||
--variable host=API_ADDRESS \
|
||||
--variable cluster_ca_certificate=CLUSTER_CA_CERTIFICATE \
|
||||
--variable token=CLUSTER_SERVICEACCOUNT_TOKEN \
|
||||
|
|
|
@ -808,6 +808,39 @@ func TestTemplateACL(t *testing.T) {
|
|||
require.Equal(t, http.StatusNotFound, cerr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("DisableEveryoneGroupAccess", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
client, admin := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
|
||||
Features: license.Features{
|
||||
codersdk.FeatureTemplateRBAC: 1,
|
||||
},
|
||||
}})
|
||||
version := coderdtest.CreateTemplateVersion(t, client, admin.OrganizationID, nil)
|
||||
template := coderdtest.CreateTemplate(t, client, admin.OrganizationID, version.ID)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
//nolint:gocritic // non-template-admin cannot get template acl
|
||||
acl, err := client.TemplateACL(ctx, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(acl.Groups))
|
||||
_, err = client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{
|
||||
Name: template.Name,
|
||||
DisplayName: template.DisplayName,
|
||||
Description: template.Description,
|
||||
Icon: template.Icon,
|
||||
AllowUserCancelWorkspaceJobs: template.AllowUserCancelWorkspaceJobs,
|
||||
DisableEveryoneGroupAccess: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
acl, err = client.TemplateACL(ctx, template.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(acl.Groups), acl.Groups)
|
||||
})
|
||||
|
||||
// Test that we do not return deleted users.
|
||||
t.Run("FilterDeletedUsers", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
|
|
@ -155,6 +155,6 @@
|
|||
"nomad",
|
||||
"container"
|
||||
],
|
||||
"markdown": "\n# Remote Development on Nomad\n\nProvision Nomad Jobs as [Coder workspaces](https://coder.com/docs/coder-v2/latest) with this example template. This example shows how to use Nomad service tasks to be used as a development environment using docker and host csi volumes.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Prerequisites\n\n- [Nomad](https://www.nomadproject.io/downloads)\n- [Docker](https://docs.docker.com/get-docker/)\n\n## Setup\n\n### 1. Start the CSI Host Volume Plugin\n\nThe CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This is useful for development environments where you want to mount persistent volumes into your container workspace.\n\n1. Login to the Nomad server using SSH.\n\n2. Append the following stanza to your Nomad server configuration file and restart the nomad service.\n\n ```hcl\n plugin \"docker\" {\n config {\n allow_privileged = true\n }\n }\n ```\n\n ```shell\n sudo systemctl restart nomad\n ```\n\n3. Create a file `hostpath.nomad` with following content:\n\n ```hcl\n job \"hostpath-csi-plugin\" {\n datacenters = [\"dc1\"]\n type = \"system\"\n\n group \"csi\" {\n task \"plugin\" {\n driver = \"docker\"\n\n config {\n image = \"registry.k8s.io/sig-storage/hostpathplugin:v1.10.0\"\n\n args = [\n \"--drivername=csi-hostpath\",\n \"--v=5\",\n \"--endpoint=${CSI_ENDPOINT}\",\n \"--nodeid=node-${NOMAD_ALLOC_INDEX}\",\n ]\n\n privileged = true\n }\n\n csi_plugin {\n id = \"hostpath\"\n type = \"monolith\"\n mount_dir = \"/csi\"\n }\n\n resources {\n cpu = 256\n memory = 128\n }\n }\n }\n }\n ```\n\n4. Run the job:\n\n ```shell\n nomad job run hostpath.nomad\n ```\n\n### 2. Setup the Nomad Template\n\n1. Create the template by running the following command:\n\n ```shell\n coder template init nomad-docker\n cd nomad-docker\n coder template create\n ```\n\n2. Set up Nomad server address and optional authentication:\n\n3. Create a new workspace and start developing.\n"
|
||||
"markdown": "\n# Remote Development on Nomad\n\nProvision Nomad Jobs as [Coder workspaces](https://coder.com/docs/coder-v2/latest) with this example template. This example shows how to use Nomad service tasks to be used as a development environment using docker and host csi volumes.\n\n\u003c!-- TODO: Add screenshot --\u003e\n\n\u003e **Note**\n\u003e This template is designed to be a starting point! Edit the Terraform to extend the template to support your use case.\n\n## Prerequisites\n\n- [Nomad](https://www.nomadproject.io/downloads)\n- [Docker](https://docs.docker.com/get-docker/)\n\n## Setup\n\n### 1. Start the CSI Host Volume Plugin\n\nThe CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This is useful for development environments where you want to mount persistent volumes into your container workspace.\n\n1. Login to the Nomad server using SSH.\n\n2. Append the following stanza to your Nomad server configuration file and restart the nomad service.\n\n ```hcl\n plugin \"docker\" {\n config {\n allow_privileged = true\n }\n }\n ```\n\n ```shell\n sudo systemctl restart nomad\n ```\n\n3. Create a file `hostpath.nomad` with following content:\n\n ```hcl\n job \"hostpath-csi-plugin\" {\n datacenters = [\"dc1\"]\n type = \"system\"\n\n group \"csi\" {\n task \"plugin\" {\n driver = \"docker\"\n\n config {\n image = \"registry.k8s.io/sig-storage/hostpathplugin:v1.10.0\"\n\n args = [\n \"--drivername=csi-hostpath\",\n \"--v=5\",\n \"--endpoint=${CSI_ENDPOINT}\",\n \"--nodeid=node-${NOMAD_ALLOC_INDEX}\",\n ]\n\n privileged = true\n }\n\n csi_plugin {\n id = \"hostpath\"\n type = \"monolith\"\n mount_dir = \"/csi\"\n }\n\n resources {\n cpu = 256\n memory = 128\n }\n }\n }\n }\n ```\n\n4. Run the job:\n\n ```shell\n nomad job run hostpath.nomad\n ```\n\n### 2. Setup the Nomad Template\n\n1. Create the template by running the following command:\n\n ```shell\n coder template init nomad-docker\n cd nomad-docker\n coder template push\n ```\n\n2. Set up Nomad server address and optional authentication:\n\n3. Create a new workspace and start developing.\n"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -103,7 +103,7 @@ provision:
|
|||
fi
|
||||
DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}')
|
||||
printf 'docker_arch: "%s"\ndocker_host: "%s"\n' "${DOCKER_ARCH}" "${DOCKER_HOST}" | tee "${temp_template_dir}/params.yaml"
|
||||
coder templates create "docker-${DOCKER_ARCH}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes
|
||||
coder templates push "docker-${DOCKER_ARCH}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes
|
||||
rm -rfv "${temp_template_dir}"
|
||||
probes:
|
||||
- description: "docker to be installed"
|
||||
|
|
|
@ -11,7 +11,7 @@ Clone this repository to create a template from any example listed here:
|
|||
```console
|
||||
git clone https://github.com/coder/coder
|
||||
cd examples/templates/aws-linux
|
||||
coder templates create
|
||||
coder templates push
|
||||
```
|
||||
|
||||
## Community Templates
|
||||
|
|
|
@ -47,7 +47,7 @@ To supply values to existing existing Terraform variables you can specify the
|
|||
`-V` flag. For example
|
||||
|
||||
```bash
|
||||
coder templates create envbox --var namespace="mynamespace" --var max_cpus=2 --var min_cpus=1 --var max_memory=4 --var min_memory=1
|
||||
coder templates push envbox --var namespace="mynamespace" --var max_cpus=2 --var min_cpus=1 --var max_memory=4 --var min_memory=1
|
||||
```
|
||||
|
||||
## Contributions
|
||||
|
|
|
@ -95,7 +95,7 @@ The CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This
|
|||
```shell
|
||||
coder template init nomad-docker
|
||||
cd nomad-docker
|
||||
coder template create
|
||||
coder template push
|
||||
```
|
||||
|
||||
2. Set up Nomad server address and optional authentication:
|
||||
|
|
|
@ -68,7 +68,7 @@ CODER_FIRST_USER_TRIAL="${CODER_FIRST_USER_TRIAL}"
|
|||
EOF
|
||||
|
||||
echo "Importing kubernetes template"
|
||||
DRY_RUN="$DRY_RUN" "$PROJECT_ROOT/scaletest/lib/coder_shim.sh" templates create \
|
||||
DRY_RUN="$DRY_RUN" "$PROJECT_ROOT/scaletest/lib/coder_shim.sh" templates push \
|
||||
--global-config="${CONFIG_DIR}" \
|
||||
--directory "${CONFIG_DIR}/templates/kubernetes" \
|
||||
--yes kubernetes
|
||||
|
|
|
@ -177,7 +177,7 @@ fatal() {
|
|||
DOCKER_HOST="$(docker context inspect --format '{{ .Endpoints.docker.Host }}')"
|
||||
printf 'docker_arch: "%s"\ndocker_host: "%s"\n' "${GOARCH}" "${DOCKER_HOST}" >"${temp_template_dir}/params.yaml"
|
||||
(
|
||||
"${CODER_DEV_SHIM}" templates create "${template_name}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes
|
||||
"${CODER_DEV_SHIM}" templates push "${template_name}" --directory "${temp_template_dir}" --variables-file "${temp_template_dir}/params.yaml" --yes
|
||||
rm -rfv "${temp_template_dir}" # Only delete template dir if template creation succeeds
|
||||
) || echo "Failed to create a template. The template files are in ${temp_template_dir}"
|
||||
fi
|
||||
|
|
|
@ -1265,6 +1265,7 @@ export interface UpdateTemplateMeta {
|
|||
readonly update_workspace_dormant_at: boolean;
|
||||
readonly require_active_version: boolean;
|
||||
readonly deprecation_message?: string;
|
||||
readonly disable_everyone_group_access: boolean;
|
||||
}
|
||||
|
||||
// From codersdk/users.go
|
||||
|
|
|
@ -77,6 +77,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
|
|||
update_workspace_dormant_at: false,
|
||||
require_active_version: template.require_active_version,
|
||||
deprecation_message: template.deprecation_message,
|
||||
disable_everyone_group_access: false,
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit,
|
||||
|
|
|
@ -47,6 +47,7 @@ const validFormValues: FormValues = {
|
|||
update_workspace_last_used_at: false,
|
||||
update_workspace_dormant_at: false,
|
||||
require_active_version: false,
|
||||
disable_everyone_group_access: false,
|
||||
};
|
||||
|
||||
const renderTemplateSettingsPage = async () => {
|
||||
|
|
|
@ -118,6 +118,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
update_workspace_last_used_at: false,
|
||||
update_workspace_dormant_at: false,
|
||||
require_active_version: false,
|
||||
disable_everyone_group_access: false,
|
||||
},
|
||||
validationSchema,
|
||||
onSubmit: () => {
|
||||
|
@ -238,6 +239,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
|
|||
update_workspace_last_used_at: form.values.update_workspace_last_used_at,
|
||||
update_workspace_dormant_at: form.values.update_workspace_dormant_at,
|
||||
require_active_version: false,
|
||||
disable_everyone_group_access: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ const validFormValues: TemplateScheduleFormValues = {
|
|||
"saturday",
|
||||
"sunday",
|
||||
],
|
||||
disable_everyone_group_access: false,
|
||||
};
|
||||
|
||||
const renderTemplateSchedulePage = async () => {
|
||||
|
|
Loading…
Reference in New Issue