From b21da38bea6816fded08e41b9c5e5a573399de14 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Fri, 5 Jan 2024 16:04:14 -0500 Subject: [PATCH] chore: deprecate template create command in favor of template push (#11390) --- .github/workflows/pr-deploy.yaml | 2 +- cli/cliui/deprecation.go | 21 + cli/templatecreate.go | 114 +---- cli/templatecreate_test.go | 60 --- cli/templateedit.go | 9 + cli/templateinit.go | 2 +- cli/templatelist.go | 2 +- cli/templatepush.go | 457 +++++++++++------- cli/templatepush_test.go | 61 ++- cli/templates.go | 6 +- cli/testdata/coder_templates_--help.golden | 12 +- .../coder_templates_create_--help.golden | 3 +- .../coder_templates_edit_--help.golden | 5 + .../coder_templates_push_--help.golden | 5 +- coderd/database/dbmem/dbmem.go | 1 + coderd/database/queries.sql.go | 19 +- coderd/database/queries/templates.sql | 3 +- coderd/templates.go | 6 + codersdk/templates.go | 6 + docs/admin/provisioners.md | 6 +- docs/cli/templates.md | 29 +- docs/cli/templates_create.md | 2 +- docs/cli/templates_edit.md | 9 + docs/cli/templates_push.md | 11 +- docs/install/openshift.md | 2 +- docs/manifest.json | 4 +- docs/platforms/azure.md | 2 +- docs/platforms/docker.md | 2 +- .../kubernetes/additional-clusters.md | 4 +- enterprise/coderd/templates_test.go | 33 ++ examples/examples.gen.json | 2 +- examples/lima/coder.yaml | 2 +- examples/templates/README.md | 2 +- examples/templates/envbox/README.md | 2 +- examples/templates/nomad-docker/README.md | 2 +- scaletest/lib/coder_init.sh | 2 +- scripts/develop.sh | 2 +- site/src/api/typesGenerated.ts | 1 + .../TemplateSettingsForm.tsx | 1 + .../TemplateSettingsPage.test.tsx | 1 + .../TemplateScheduleForm.tsx | 2 + .../TemplateSchedulePage.test.tsx | 1 + 42 files changed, 498 insertions(+), 420 deletions(-) create mode 100644 cli/cliui/deprecation.go diff --git a/.github/workflows/pr-deploy.yaml b/.github/workflows/pr-deploy.yaml index 9c657b43ba..f5045f0bb2 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 0000000000..7673e19fbe --- /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 51a4c33cfa..3d52b236fd 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 02174f59f7..0eaf1344ea 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 9cbcefc887..099f31027a 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 a9577733bc..db9e3780f1 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 6d95521dad..6e18f84625 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 4c903ef7ca..26e3aa9472 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -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 diff --git a/cli/templatepush_test.go b/cli/templatepush_test.go index 5736df8cc2..13c9fbc1f3 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 4f5b4f8f36..71688c04a4 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 f9ce76a9ff..7feaa09e5f 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 ea896d9442..4fb6512cba 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 94fa1ac452..52ef47d363 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 9d255c1f8b..092e16f897 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 c533eaaba4..1ed0d1f736 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 2a1f3b316c..81bbe52386 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 af8c3fe80f..ca031bb0bd 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 5e6d9644a7..d4c33a454c 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 8164843ad0..1be4d931ad 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 62a35c1ede..948eba6576 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 4a5b601611..0226bd5a60 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 9535e2f12e..eacac10850 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 12577cbcab..ff73c2828e 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 bfa73fdad1..d7a6cb7043 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 7d7440978d..19e122e47f 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 131f0a03f2..bef7dc89f5 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 72fab874d3..df5bb64a5b 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 7784e455da..09e8fc7a4e 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 0a27ecb061..c3bcd42d18 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 3c141542fd..b340f90ece 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 d216581c7c..ea2ec0abc1 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 bb0f1528b8..f9b8a1176e 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 38ade2345d..3ab46a52ad 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 d5632294d6..ad97f7777e 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 b5ce534483..17310ae2e9 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 f8c905958e..4b8ea10986 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 39f81c2951..ba5116f5a7 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 17b3091cfe..b38c1b4829 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 b402e92a94..39f722f59b 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 be0d593c9e..ee61536465 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 f1f0af511e..89f26cc5d4 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 77e50d73f0..ab33f72560 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 () => {