diff --git a/.github/workflows/pr-deploy.yaml b/.github/workflows/pr-deploy.yaml index 9c657b43ba699..f5045f0bb202a 100644 --- a/.github/workflows/pr-deploy.yaml +++ b/.github/workflows/pr-deploy.yaml @@ -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 diff --git a/cli/cliui/deprecation.go b/cli/cliui/deprecation.go new file mode 100644 index 0000000000000..7673e19fbe11d --- /dev/null +++ b/cli/cliui/deprecation.go @@ -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) + } + } +} diff --git a/cli/templatecreate.go b/cli/templatecreate.go index 51a4c33cfa226..3d52b236fd299 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -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 -} diff --git a/cli/templatecreate_test.go b/cli/templatecreate_test.go index 02174f59f7f5a..0eaf1344ea298 100644 --- a/cli/templatecreate_test.go +++ b/cli/templatecreate_test.go @@ -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) - } - }) -} diff --git a/cli/templateedit.go b/cli/templateedit.go index 9cbcefc88730f..099f31027ac8a 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -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(), } diff --git a/cli/templateinit.go b/cli/templateinit.go index a9577733bc0fb..db9e3780f1c39 100644 --- a/cli/templateinit.go +++ b/cli/templateinit.go @@ -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 diff --git a/cli/templatelist.go b/cli/templatelist.go index 6d95521dad321..6e18f8462555e 100644 --- a/cli/templatelist.go +++ b/cli/templatelist.go @@ -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 \n")) + _, _ = fmt.Fprintln(inv.Stderr, color.HiMagentaString(" $ coder templates push \n")) return nil } diff --git a/cli/templatepush.go b/cli/templatepush.go index 4c903ef7ca4d9..26e3aa9472b1a 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -2,158 +2,27 @@ 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`. -type templateUploadFlags struct { - directory string - ignoreLockfile bool - message string -} - -func (pf *templateUploadFlags) options() []clibase.Option { - return []clibase.Option{{ - Flag: "directory", - FlagShorthand: "d", - Description: "Specify the directory to create from, use '-' to read tar from stdin.", - Default: ".", - Value: clibase.StringOf(&pf.directory), - }, { - Flag: "ignore-lockfile", - Description: "Ignore warnings about not having a .terraform.lock.hcl file present in the template.", - Default: "false", - Value: clibase.BoolOf(&pf.ignoreLockfile), - }, { - Flag: "message", - FlagShorthand: "m", - Description: "Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.", - Value: clibase.StringOf(&pf.message), - }} -} - -func (pf *templateUploadFlags) setWorkdir(wd string) { - if wd == "" { - return - } - if pf.directory == "" || pf.directory == "." { - pf.directory = wd - } else if !filepath.IsAbs(pf.directory) { - pf.directory = filepath.Join(wd, pf.directory) - } -} - -func (pf *templateUploadFlags) stdin() bool { - return pf.directory == "-" -} - -func (pf *templateUploadFlags) upload(inv *clibase.Invocation, client *codersdk.Client) (*codersdk.UploadResponse, error) { - var content io.Reader - if pf.stdin() { - content = inv.Stdin - } else { - prettyDir := prettyDirectoryPath(pf.directory) - _, err := cliui.Prompt(inv, cliui.PromptOptions{ - Text: fmt.Sprintf("Upload %q?", prettyDir), - IsConfirm: true, - Default: cliui.ConfirmYes, - }) - if err != nil { - return nil, err - } - - pipeReader, pipeWriter := io.Pipe() - go func() { - err := provisionersdk.Tar(pipeWriter, inv.Logger, pf.directory, provisionersdk.TemplateArchiveLimit) - _ = pipeWriter.CloseWithError(err) - }() - defer pipeReader.Close() - content = pipeReader - } - - spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond) - spin.Writer = inv.Stdout - spin.Suffix = pretty.Sprint(cliui.DefaultStyles.Keyword, " Uploading directory...") - spin.Start() - defer spin.Stop() - - resp, err := client.Upload(inv.Context(), codersdk.ContentTypeTar, bufio.NewReader(content)) - if err != nil { - return nil, xerrors.Errorf("upload: %w", err) - } - return &resp, nil -} - -func (pf *templateUploadFlags) checkForLockfile(inv *clibase.Invocation) error { - if pf.stdin() || pf.ignoreLockfile { - // Just assume there's a lockfile if reading from stdin. - return nil - } - - hasLockfile, err := provisionersdk.DirHasLockfile(pf.directory) - if err != nil { - return xerrors.Errorf("dir has lockfile: %w", err) - } - - if !hasLockfile { - cliui.Warn(inv.Stdout, "No .terraform.lock.hcl file found", - "When provisioning, Coder will be unable to cache providers without a lockfile and must download them from the internet each time.", - "Create one by running "+pretty.Sprint(cliui.DefaultStyles.Code, "terraform init")+" in your template directory.", - ) - } - return nil -} - -func (pf *templateUploadFlags) templateMessage(inv *clibase.Invocation) string { - title := strings.SplitN(pf.message, "\n", 2)[0] - if len(title) > 72 { - cliui.Warn(inv.Stdout, "Template message is longer than 72 characters, it will be displayed as truncated.") - } - if title != pf.message { - cliui.Warn(inv.Stdout, "Template message contains newlines, only the first line will be displayed.") - } - if pf.message != "" { - return pf.message - } - return "Uploaded from the CLI" -} - -func (pf *templateUploadFlags) templateName(args []string) (string, error) { - if pf.stdin() { - // Can't infer name from directory if none provided. - if len(args) == 0 { - return "", xerrors.New("template name argument must be provided") - } - return args[0], nil - } - - if len(args) > 0 { - return args[0], nil - } - // Have to take absPath to resolve "." and "..". - absPath, err := filepath.Abs(pf.directory) - if err != nil { - return "", err - } - // If no name is provided, use the directory name. - return filepath.Base(absPath), nil -} - func (r *RootCmd) templatePush() *clibase.Cmd { var ( versionName string @@ -165,12 +34,11 @@ func (r *RootCmd) templatePush() *clibase.Cmd { 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", + 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), @@ -188,12 +56,18 @@ func (r *RootCmd) templatePush() *clibase.Cmd { 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 { - if !create { + var apiError *codersdk.Error + if errors.As(err, &apiError) && apiError.StatusCode() != http.StatusNotFound { return err } + // Template doesn't exist, create it. createTemplate = true } @@ -326,18 +200,249 @@ func (r *RootCmd) templatePush() *clibase.Cmd { 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(), } cmd.Options = append(cmd.Options, uploadFlags.options()...) return cmd } +type templateUploadFlags struct { + directory string + ignoreLockfile bool + message string +} + +func (pf *templateUploadFlags) options() []clibase.Option { + return []clibase.Option{{ + Flag: "directory", + FlagShorthand: "d", + Description: "Specify the directory to create from, use '-' to read tar from stdin.", + Default: ".", + Value: clibase.StringOf(&pf.directory), + }, { + Flag: "ignore-lockfile", + Description: "Ignore warnings about not having a .terraform.lock.hcl file present in the template.", + Default: "false", + Value: clibase.BoolOf(&pf.ignoreLockfile), + }, { + Flag: "message", + FlagShorthand: "m", + Description: "Specify a message describing the changes in this version of the template. Messages longer than 72 characters will be displayed as truncated.", + Value: clibase.StringOf(&pf.message), + }} +} + +func (pf *templateUploadFlags) setWorkdir(wd string) { + if wd == "" { + return + } + if pf.directory == "" || pf.directory == "." { + pf.directory = wd + } else if !filepath.IsAbs(pf.directory) { + pf.directory = filepath.Join(wd, pf.directory) + } +} + +func (pf *templateUploadFlags) stdin() bool { + return pf.directory == "-" +} + +func (pf *templateUploadFlags) upload(inv *clibase.Invocation, client *codersdk.Client) (*codersdk.UploadResponse, error) { + var content io.Reader + if pf.stdin() { + content = inv.Stdin + } else { + prettyDir := prettyDirectoryPath(pf.directory) + _, err := cliui.Prompt(inv, cliui.PromptOptions{ + Text: fmt.Sprintf("Upload %q?", prettyDir), + IsConfirm: true, + Default: cliui.ConfirmYes, + }) + if err != nil { + return nil, err + } + + pipeReader, pipeWriter := io.Pipe() + go func() { + err := provisionersdk.Tar(pipeWriter, inv.Logger, pf.directory, provisionersdk.TemplateArchiveLimit) + _ = pipeWriter.CloseWithError(err) + }() + defer pipeReader.Close() + content = pipeReader + } + + spin := spinner.New(spinner.CharSets[5], 100*time.Millisecond) + spin.Writer = inv.Stdout + spin.Suffix = pretty.Sprint(cliui.DefaultStyles.Keyword, " Uploading directory...") + spin.Start() + defer spin.Stop() + + resp, err := client.Upload(inv.Context(), codersdk.ContentTypeTar, bufio.NewReader(content)) + if err != nil { + return nil, xerrors.Errorf("upload: %w", err) + } + return &resp, nil +} + +func (pf *templateUploadFlags) checkForLockfile(inv *clibase.Invocation) error { + if pf.stdin() || pf.ignoreLockfile { + // Just assume there's a lockfile if reading from stdin. + return nil + } + + hasLockfile, err := provisionersdk.DirHasLockfile(pf.directory) + if err != nil { + return xerrors.Errorf("dir has lockfile: %w", err) + } + + if !hasLockfile { + cliui.Warn(inv.Stdout, "No .terraform.lock.hcl file found", + "When provisioning, Coder will be unable to cache providers without a lockfile and must download them from the internet each time.", + "Create one by running "+pretty.Sprint(cliui.DefaultStyles.Code, "terraform init")+" in your template directory.", + ) + } + return nil +} + +func (pf *templateUploadFlags) templateMessage(inv *clibase.Invocation) string { + title := strings.SplitN(pf.message, "\n", 2)[0] + if len(title) > 72 { + cliui.Warn(inv.Stdout, "Template message is longer than 72 characters, it will be displayed as truncated.") + } + if title != pf.message { + cliui.Warn(inv.Stdout, "Template message contains newlines, only the first line will be displayed.") + } + if pf.message != "" { + return pf.message + } + return "Uploaded from the CLI" +} + +func (pf *templateUploadFlags) templateName(args []string) (string, error) { + if pf.stdin() { + // Can't infer name from directory if none provided. + if len(args) == 0 { + return "", xerrors.New("template name argument must be provided") + } + return args[0], nil + } + + if len(args) > 0 { + return args[0], nil + } + // Have to take absPath to resolve "." and "..". + absPath, err := filepath.Abs(pf.directory) + if err != nil { + return "", err + } + // If no name is provided, use the directory name. + return filepath.Base(absPath), nil +} + +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 +} + // prettyDirectoryPath returns a prettified path when inside the users // home directory. Falls back to dir if the users home directory cannot // discerned. This function calls filepath.Clean on the result. diff --git a/cli/templatepush_test.go b/cli/templatepush_test.go index 5736df8cc2edf..13c9fbc1f35c4 100644 --- a/cli/templatepush_test.go +++ b/cli/templatepush_test.go @@ -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) + } + }) +} diff --git a/cli/templates.go b/cli/templates.go index 4f5b4f8f36d0b..71688c04a470e 100644 --- a/cli/templates.go +++ b/cli/templates.go @@ -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", }, ), diff --git a/cli/testdata/coder_templates_--help.golden b/cli/testdata/coder_templates_--help.golden index f9ce76a9ff2c5..7feaa09e5f429 100644 --- a/cli/testdata/coder_templates_--help.golden +++ b/cli/testdata/coder_templates_--help.golden @@ -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 diff --git a/cli/testdata/coder_templates_create_--help.golden b/cli/testdata/coder_templates_create_--help.golden index ea896d944288b..4fb6512cbab27 100644 --- a/cli/testdata/coder_templates_create_--help.golden +++ b/cli/testdata/coder_templates_create_--help.golden @@ -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) diff --git a/cli/testdata/coder_templates_edit_--help.golden b/cli/testdata/coder_templates_edit_--help.golden index 94fa1ac45276c..52ef47d363326 100644 --- a/cli/testdata/coder_templates_edit_--help.golden +++ b/cli/testdata/coder_templates_edit_--help.golden @@ -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 diff --git a/cli/testdata/coder_templates_push_--help.golden b/cli/testdata/coder_templates_push_--help.golden index 9d255c1f8bc23..092e16f897bee 100644 --- a/cli/testdata/coder_templates_push_--help.golden +++ b/cli/testdata/coder_templates_push_--help.golden @@ -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. diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index c533eaaba4589..1ed0d1f73692c 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -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 } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2a1f3b316c650..81bbe52386cf9 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -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 } diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index af8c3fe80f420..ca031bb0bd839 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -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 ; diff --git a/coderd/templates.go b/coderd/templates.go index 5e6d9644a782f..d4c33a454ce16 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -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) diff --git a/codersdk/templates.go b/codersdk/templates.go index 8164843ad0c66..1be4d931ad7a2 100644 --- a/codersdk/templates.go +++ b/codersdk/templates.go @@ -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 { diff --git a/docs/admin/provisioners.md b/docs/admin/provisioners.md index 62a35c1ede1ad..948eba65763f0 100644 --- a/docs/admin/provisioners.md +++ b/docs/admin/provisioners.md @@ -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 ``` diff --git a/docs/cli/templates.md b/docs/cli/templates.md index 4a5b60161114f..0226bd5a60d92 100644 --- a/docs/cli/templates.md +++ b/docs/cli/templates.md @@ -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 | -| ------------------------------------------------ | ------------------------------------------------------------------------------ | -| [archive](./templates_archive.md) | Archive unused or failed template versions from a given template(s) | -| [create](./templates_create.md) | Create a template from the current directory or as specified by flag | -| [delete](./templates_delete.md) | Delete templates | -| [edit](./templates_edit.md) | Edit the metadata of a template by name. | -| [init](./templates_init.md) | Get started with a templated template. | -| [list](./templates_list.md) | List all the templates available for the organization | -| [pull](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. | -| [push](./templates_push.md) | Push a new template version from the current directory or as specified by flag | -| [versions](./templates_versions.md) | Manage different versions of the specified template | +| Name | Purpose | +| ------------------------------------------------ | -------------------------------------------------------------------------------- | +| [archive](./templates_archive.md) | Archive unused or failed template versions from a given template(s) | +| [create](./templates_create.md) | DEPRECATED: Create a template from the current directory or as specified by flag | +| [delete](./templates_delete.md) | Delete templates | +| [edit](./templates_edit.md) | Edit the metadata of a template by name. | +| [init](./templates_init.md) | Get started with a templated template. | +| [list](./templates_list.md) | List all the templates available for the organization | +| [pull](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. | +| [push](./templates_push.md) | Create or update a template from the current directory or as specified by flag | +| [versions](./templates_versions.md) | Manage different versions of the specified template | diff --git a/docs/cli/templates_create.md b/docs/cli/templates_create.md index 9535e2f12e6da..eacac108501db 100644 --- a/docs/cli/templates_create.md +++ b/docs/cli/templates_create.md @@ -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 diff --git a/docs/cli/templates_edit.md b/docs/cli/templates_edit.md index 12577cbcaba23..ff73c2828eb83 100644 --- a/docs/cli/templates_edit.md +++ b/docs/cli/templates_edit.md @@ -130,6 +130,15 @@ Edit the template maximum time before shutdown - workspaces created from this te Edit the template name. +### --private + +| | | +| ------- | ------------------ | +| Type | 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 | | | diff --git a/docs/cli/templates_push.md b/docs/cli/templates_push.md index bfa73fdad1151..d7a6cb7043989 100644 --- a/docs/cli/templates_push.md +++ b/docs/cli/templates_push.md @@ -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 | bool | -| Default | false | - -Create the template if it does not exist. - ### -d, --directory | | | diff --git a/docs/install/openshift.md b/docs/install/openshift.md index 7d7440978da24..19e122e47f0a9 100644 --- a/docs/install/openshift.md +++ b/docs/install/openshift.md @@ -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. diff --git a/docs/manifest.json b/docs/manifest.json index 131f0a03f2896..bef7dc89f5511 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -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" }, { diff --git a/docs/platforms/azure.md b/docs/platforms/azure.md index 72fab874d3322..df5bb64a5b5fb 100644 --- a/docs/platforms/azure.md +++ b/docs/platforms/azure.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 diff --git a/docs/platforms/docker.md b/docs/platforms/docker.md index 7784e455da570..09e8fc7a4e949 100644 --- a/docs/platforms/docker.md +++ b/docs/platforms/docker.md @@ -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: diff --git a/docs/platforms/kubernetes/additional-clusters.md b/docs/platforms/kubernetes/additional-clusters.md index 0a27ecb061b35..c3bcd42d18cfe 100644 --- a/docs/platforms/kubernetes/additional-clusters.md +++ b/docs/platforms/kubernetes/additional-clusters.md @@ -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 \ diff --git a/enterprise/coderd/templates_test.go b/enterprise/coderd/templates_test.go index 3c141542fdfc7..b340f90ece7ad 100644 --- a/enterprise/coderd/templates_test.go +++ b/enterprise/coderd/templates_test.go @@ -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() diff --git a/examples/examples.gen.json b/examples/examples.gen.json index d216581c7c116..ea2ec0abc10d8 100644 --- a/examples/examples.gen.json +++ b/examples/examples.gen.json @@ -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" } ] diff --git a/examples/lima/coder.yaml b/examples/lima/coder.yaml index bb0f1528b8cfc..f9b8a1176e347 100644 --- a/examples/lima/coder.yaml +++ b/examples/lima/coder.yaml @@ -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" diff --git a/examples/templates/README.md b/examples/templates/README.md index 38ade2345d70f..3ab46a52ad41f 100644 --- a/examples/templates/README.md +++ b/examples/templates/README.md @@ -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 diff --git a/examples/templates/envbox/README.md b/examples/templates/envbox/README.md index d5632294d63d1..ad97f7777edad 100644 --- a/examples/templates/envbox/README.md +++ b/examples/templates/envbox/README.md @@ -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 diff --git a/examples/templates/nomad-docker/README.md b/examples/templates/nomad-docker/README.md index b5ce5344837da..17310ae2e9852 100644 --- a/examples/templates/nomad-docker/README.md +++ b/examples/templates/nomad-docker/README.md @@ -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: diff --git a/scaletest/lib/coder_init.sh b/scaletest/lib/coder_init.sh index f8c905958ece4..4b8ea10986b7c 100755 --- a/scaletest/lib/coder_init.sh +++ b/scaletest/lib/coder_init.sh @@ -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 diff --git a/scripts/develop.sh b/scripts/develop.sh index 39f81c2951bc4..ba5116f5a7735 100755 --- a/scripts/develop.sh +++ b/scripts/develop.sh @@ -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 diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 17b3091cfe2a5..b38c1b48298eb 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -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 diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx index b402e92a947f2..39f722f59bdb1 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsForm.tsx @@ -77,6 +77,7 @@ export const TemplateSettingsForm: FC = ({ update_workspace_dormant_at: false, require_active_version: template.require_active_version, deprecation_message: template.deprecation_message, + disable_everyone_group_access: false, }, validationSchema, onSubmit, diff --git a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx index be0d593c9e13e..ee6153646584a 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateGeneralSettingsPage/TemplateSettingsPage.test.tsx @@ -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 () => { diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx index f1f0af511ec9b..89f26cc5d451e 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateScheduleForm.tsx @@ -118,6 +118,7 @@ export const TemplateScheduleForm: FC = ({ 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 = ({ 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, }); }; diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx index 77e50d73f0657..ab33f72560be3 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/TemplateSchedulePage.test.tsx @@ -37,6 +37,7 @@ const validFormValues: TemplateScheduleFormValues = { "saturday", "sunday", ], + disable_everyone_group_access: false, }; const renderTemplateSchedulePage = async () => {