chore: deprecate template create command in favor of template push (#11390)

This commit is contained in:
Garrett Delfosse 2024-01-05 16:04:14 -05:00 committed by GitHub
parent 3d54bc06f6
commit b21da38bea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 498 additions and 420 deletions

View File

@ -416,7 +416,7 @@ jobs:
# Create template # Create template
cd ./.github/pr-deployments/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 # Create workspace
coder create --template="kubernetes" kube --parameter cpu=2 --parameter memory=4 --parameter home_disk_size=2 -y coder create --template="kubernetes" kube --parameter cpu=2 --parameter memory=4 --parameter home_disk_size=2 -y

21
cli/cliui/deprecation.go Normal file
View File

@ -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)
}
}
}

View File

@ -1,15 +1,11 @@
package cli package cli
import ( import (
"errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"strings"
"time" "time"
"unicode/utf8" "unicode/utf8"
"github.com/google/uuid"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/pretty" "github.com/coder/pretty"
@ -40,9 +36,13 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
client := new(codersdk.Client) client := new(codersdk.Client)
cmd := &clibase.Cmd{ cmd := &clibase.Cmd{
Use: "create [name]", 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( Middleware: clibase.Chain(
clibase.RequireRangeArgs(0, 1), 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), r.InitClient(client),
), ),
Handler: func(inv *clibase.Invocation) error { Handler: func(inv *clibase.Invocation) error {
@ -253,107 +253,3 @@ func (r *RootCmd) templateCreate() *clibase.Cmd {
cmd.Options = append(cmd.Options, uploadFlags.options()...) cmd.Options = append(cmd.Options, uploadFlags.options()...)
return cmd 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
}

View File

@ -19,54 +19,6 @@ import (
"github.com/coder/coder/v2/testutil" "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) { func TestTemplateCreate(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("Create", func(t *testing.T) { 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") 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)
}
})
}

View File

@ -35,6 +35,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
allowUserAutostop bool allowUserAutostop bool
requireActiveVersion bool requireActiveVersion bool
deprecationMessage string deprecationMessage string
disableEveryone bool
) )
client := new(codersdk.Client) client := new(codersdk.Client)
@ -162,6 +163,7 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
AllowUserAutostop: allowUserAutostop, AllowUserAutostop: allowUserAutostop,
RequireActiveVersion: requireActiveVersion, RequireActiveVersion: requireActiveVersion,
DeprecationMessage: deprecated, DeprecationMessage: deprecated,
DisableEveryoneGroupAccess: disableEveryone,
} }
_, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req) _, err = client.UpdateTemplateMeta(inv.Context(), template.ID, req)
@ -292,6 +294,13 @@ func (r *RootCmd) templateEdit() *clibase.Cmd {
Value: clibase.BoolOf(&requireActiveVersion), Value: clibase.BoolOf(&requireActiveVersion),
Default: "false", 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(), cliui.SkipPromptOption(),
} }

View File

@ -113,7 +113,7 @@ func (*RootCmd) templateInit() *clibase.Cmd {
inv.Stdout, inv.Stdout,
pretty.Sprint( pretty.Sprint(
cliui.DefaultStyles.Code, 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! 🎨")) _, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(cliui.DefaultStyles.Wrap, "\nExamples provide a starting point and are expected to be edited! 🎨"))
return nil return nil

View File

@ -36,7 +36,7 @@ func (r *RootCmd) templateList() *clibase.Cmd {
if len(templates) == 0 { if len(templates) == 0 {
_, _ = fmt.Fprintf(inv.Stderr, "%s No templates found in %s! Create one:\n\n", Caret, color.HiWhiteString(organization.Name)) _, _ = fmt.Fprintf(inv.Stderr, "%s No templates found in %s! Create one:\n\n", Caret, color.HiWhiteString(organization.Name))
_, _ = fmt.Fprintln(inv.Stderr, color.HiMagentaString(" $ coder templates create <directory>\n")) _, _ = fmt.Fprintln(inv.Stderr, color.HiMagentaString(" $ coder templates push <directory>\n"))
return nil return nil
} }

View File

@ -2,25 +2,210 @@ package cli
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
"unicode/utf8"
"github.com/briandowns/spinner" "github.com/briandowns/spinner"
"github.com/google/uuid"
"golang.org/x/xerrors" "golang.org/x/xerrors"
"github.com/coder/pretty"
"github.com/coder/coder/v2/cli/clibase" "github.com/coder/coder/v2/cli/clibase"
"github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/provisionersdk" "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 { type templateUploadFlags struct {
directory string directory string
ignoreLockfile bool ignoreLockfile bool
@ -154,188 +339,108 @@ func (pf *templateUploadFlags) templateName(args []string) (string, error) {
return filepath.Base(absPath), nil return filepath.Base(absPath), nil
} }
func (r *RootCmd) templatePush() *clibase.Cmd { type createValidTemplateVersionArgs struct {
var ( Name string
versionName string Message string
provisioner string Client *codersdk.Client
workdir string Organization codersdk.Organization
variablesFile string Provisioner codersdk.ProvisionerType
commandLineVariables []string FileID uuid.UUID
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)
organization, err := CurrentOrganization(inv, client) // Template is only required if updating a template's active version.
if err != nil { Template *codersdk.Template
return err // 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) func createValidTemplateVersion(inv *clibase.Invocation, args createValidTemplateVersionArgs) (*codersdk.TemplateVersion, error) {
if err != nil { client := args.Client
return err
}
var createTemplate bool req := codersdk.CreateTemplateVersionRequest{
template, err := client.TemplateByName(inv.Context(), organization.ID, name) Name: args.Name,
if err != nil { Message: args.Message,
if !create { StorageMethod: codersdk.ProvisionerStorageMethodFile,
return err FileID: args.FileID,
} Provisioner: args.Provisioner,
createTemplate = true ProvisionerTags: args.ProvisionerTags,
} UserVariableValues: args.UserVariableValues,
}
err = uploadFlags.checkForLockfile(inv) if args.Template != nil {
if err != nil { req.TemplateID = args.Template.ID
return xerrors.Errorf("check for lockfile: %w", err) }
} version, err := client.CreateTemplateVersion(inv.Context(), args.Organization.ID, req)
if err != nil {
message := uploadFlags.templateMessage(inv) return nil, err
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{ err = cliui.ProvisionerJob(inv.Context(), inv.Stdout, cliui.ProvisionerJobOptions{
{ Fetch: func() (codersdk.ProvisionerJob, error) {
Flag: "test.provisioner", version, err := client.TemplateVersion(inv.Context(), version.ID)
Description: "Customize the provisioner backend.", return version.Job, err
Default: "terraform",
Value: clibase.StringOf(&provisioner),
// This is for testing!
Hidden: true,
}, },
{ Cancel: func() error {
Flag: "test.workdir", return client.CancelTemplateVersion(inv.Context(), version.ID)
Description: "Customize the working directory.",
Default: "",
Value: clibase.StringOf(&workdir),
// This is for testing!
Hidden: true,
}, },
{ Logs: func() (<-chan codersdk.ProvisionerJobLog, io.Closer, error) {
Flag: "variables-file", return client.TemplateVersionLogsAfter(inv.Context(), version.ID, 0)
Description: "Specify a file path with values for Terraform-managed variables.",
Value: clibase.StringOf(&variablesFile),
}, },
{ })
Flag: "variable", if err != nil {
Description: "Specify a set of values for Terraform-managed variables.", var jobErr *cliui.ProvisionerJobError
Value: clibase.StringArrayOf(&commandLineVariables), if errors.As(err, &jobErr) && !codersdk.JobIsMissingParameterErrorCode(jobErr.Code) {
}, return nil, err
{ }
Flag: "var", if err != nil {
Description: "Alias of --variable.", return nil, err
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(),
} }
cmd.Options = append(cmd.Options, uploadFlags.options()...) version, err = client.TemplateVersion(inv.Context(), version.ID)
return cmd 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 // prettyDirectoryPath returns a prettified path when inside the users

View File

@ -679,7 +679,6 @@ func TestTemplatePush(t *testing.T) {
templateName, templateName,
"--directory", source, "--directory", source,
"--test.provisioner", string(database.ProvisionerTypeEcho), "--test.provisioner", string(database.ProvisionerTypeEcho),
"--create",
} }
inv, root := clitest.New(t, args...) inv, root := clitest.New(t, args...)
clitest.SetupConfig(t, templateAdmin, root) clitest.SetupConfig(t, templateAdmin, root)
@ -726,3 +725,63 @@ func createEchoResponsesWithTemplateVariables(templateVariables []*proto.Templat
ProvisionApply: echo.ApplyComplete, 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)
}
})
}

View File

@ -17,16 +17,12 @@ func (r *RootCmd) templates() *clibase.Cmd {
Use: "templates", Use: "templates",
Short: "Manage templates", Short: "Manage templates",
Long: "Templates are written in standard Terraform and describe the infrastructure for workspaces\n" + formatExamples( 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{ example{
Description: "Make changes to your template, and plan the changes", Description: "Make changes to your template, and plan the changes",
Command: "coder templates plan my-template", Command: "coder templates plan my-template",
}, },
example{ 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", Command: "coder templates push my-template",
}, },
), ),

View File

@ -9,15 +9,11 @@ USAGE:
Templates are written in standard Terraform and describe the infrastructure Templates are written in standard Terraform and describe the infrastructure
for workspaces for workspaces
- Create a template for developers to create workspaces:
$ coder templates create
- Make changes to your template, and plan the changes: - Make changes to your template, and plan the changes:
$ coder templates plan my-template $ 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: workspaces:
$ coder templates push my-template $ coder templates push my-template
@ -25,15 +21,15 @@ USAGE:
SUBCOMMANDS: SUBCOMMANDS:
archive Archive unused or failed template versions from a given archive Archive unused or failed template versions from a given
template(s) template(s)
create Create a template from the current directory or as specified by create DEPRECATED: Create a template from the current directory or as
flag specified by flag
delete Delete templates delete Delete templates
edit Edit the metadata of a template by name. edit Edit the metadata of a template by name.
init Get started with a templated template. init Get started with a templated template.
list List all the templates available for the organization list List all the templates available for the organization
pull Download the active, latest, or specified version of a template pull Download the active, latest, or specified version of a template
to a path. 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 specified by flag
versions Manage different versions of the specified template versions Manage different versions of the specified template

View File

@ -3,7 +3,8 @@ coder v0.0.0-devel
USAGE: USAGE:
coder templates create [flags] [name] 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: OPTIONS:
--default-ttl duration (default: 24h) --default-ttl duration (default: 24h)

View File

@ -66,6 +66,11 @@ OPTIONS:
--name string --name string
Edit the template name. 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) --require-active-version bool (default: false)
Requires workspace builds to use the active template version. This Requires workspace builds to use the active template version. This
setting does not apply to template admins. This is an enterprise-only setting does not apply to template admins. This is an enterprise-only

View File

@ -3,7 +3,7 @@ coder v0.0.0-devel
USAGE: USAGE:
coder templates push [flags] [template] 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: OPTIONS:
--activate bool (default: true) --activate bool (default: true)
@ -13,9 +13,6 @@ OPTIONS:
Always prompt all parameters. Does not pull parameter values from Always prompt all parameters. Does not pull parameter values from
active template version. active template version.
--create bool (default: false)
Create the template if it does not exist.
-d, --directory string (default: .) -d, --directory string (default: .)
Specify the directory to create from, use '-' to read tar from stdin. Specify the directory to create from, use '-' to read tar from stdin.

View File

@ -6373,6 +6373,7 @@ func (q *FakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
tpl.DisplayName = arg.DisplayName tpl.DisplayName = arg.DisplayName
tpl.Description = arg.Description tpl.Description = arg.Description
tpl.Icon = arg.Icon tpl.Icon = arg.Icon
tpl.GroupACL = arg.GroupACL
q.templates[idx] = tpl q.templates[idx] = tpl
return nil return nil
} }

View File

@ -6075,19 +6075,21 @@ SET
name = $4, name = $4,
icon = $5, icon = $5,
display_name = $6, display_name = $6,
allow_user_cancel_workspace_jobs = $7 allow_user_cancel_workspace_jobs = $7,
group_acl = $8
WHERE WHERE
id = $1 id = $1
` `
type UpdateTemplateMetaByIDParams struct { type UpdateTemplateMetaByIDParams struct {
ID uuid.UUID `db:"id" json:"id"` ID uuid.UUID `db:"id" json:"id"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"` UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
Description string `db:"description" json:"description"` Description string `db:"description" json:"description"`
Name string `db:"name" json:"name"` Name string `db:"name" json:"name"`
Icon string `db:"icon" json:"icon"` Icon string `db:"icon" json:"icon"`
DisplayName string `db:"display_name" json:"display_name"` DisplayName string `db:"display_name" json:"display_name"`
AllowUserCancelWorkspaceJobs bool `db:"allow_user_cancel_workspace_jobs" json:"allow_user_cancel_workspace_jobs"` 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 { 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.Icon,
arg.DisplayName, arg.DisplayName,
arg.AllowUserCancelWorkspaceJobs, arg.AllowUserCancelWorkspaceJobs,
arg.GroupACL,
) )
return err return err
} }

View File

@ -115,7 +115,8 @@ SET
name = $4, name = $4,
icon = $5, icon = $5,
display_name = $6, display_name = $6,
allow_user_cancel_workspace_jobs = $7 allow_user_cancel_workspace_jobs = $7,
group_acl = $8
WHERE WHERE
id = $1 id = $1
; ;

View File

@ -667,6 +667,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
name = template.Name name = template.Name
} }
groupACL := template.GroupACL
if req.DisableEveryoneGroupAccess {
groupACL = database.TemplateACL{}
}
var err error var err error
err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{ err = tx.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{
ID: template.ID, ID: template.ID,
@ -676,6 +681,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
Description: req.Description, Description: req.Description,
Icon: req.Icon, Icon: req.Icon,
AllowUserCancelWorkspaceJobs: req.AllowUserCancelWorkspaceJobs, AllowUserCancelWorkspaceJobs: req.AllowUserCancelWorkspaceJobs,
GroupACL: groupACL,
}) })
if err != nil { if err != nil {
return xerrors.Errorf("update template metadata: %w", err) return xerrors.Errorf("update template metadata: %w", err)

View File

@ -241,6 +241,12 @@ type UpdateTemplateMeta struct {
// If passed an empty string, will remove the deprecated message, making // If passed an empty string, will remove the deprecated message, making
// the template usable for new workspaces again. // the template usable for new workspaces again.
DeprecationMessage *string `json:"deprecation_message"` 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 { type TemplateExample struct {

View File

@ -64,11 +64,11 @@ the [Helm example](#example-running-an-external-provisioner-with-helm) below.
# In another terminal, create/push # In another terminal, create/push
# a template that requires this provisioner # a template that requires this provisioner
coder templates create on-prem \ coder templates push on-prem \
--provisioner-tag environment=on_prem --provisioner-tag environment=on_prem
# Or, match the provisioner exactly # Or, match the provisioner exactly
coder templates create on-prem-chicago \ coder templates push on-prem-chicago \
--provisioner-tag environment=on_prem \ --provisioner-tag environment=on_prem \
--provisioner-tag data_center=chicago --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 # In another terminal, create/push
# a template that requires user provisioners # a template that requires user provisioners
coder templates create on-prem \ coder templates push on-prem \
--provisioner-tag scope=user --provisioner-tag scope=user
``` ```

29
docs/cli/templates.md generated
View File

@ -18,29 +18,26 @@ coder templates
```console ```console
Templates are written in standard Terraform and describe the infrastructure for workspaces 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: - Make changes to your template, and plan the changes:
$ coder templates plan my-template $ 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 $ coder templates push my-template
``` ```
## Subcommands ## Subcommands
| Name | Purpose | | Name | Purpose |
| ------------------------------------------------ | ------------------------------------------------------------------------------ | | ------------------------------------------------ | -------------------------------------------------------------------------------- |
| [<code>archive</code>](./templates_archive.md) | Archive unused or failed template versions from a given template(s) | | [<code>archive</code>](./templates_archive.md) | Archive unused or failed template versions from a given template(s) |
| [<code>create</code>](./templates_create.md) | Create a template from the current directory or as specified by flag | | [<code>create</code>](./templates_create.md) | DEPRECATED: Create a template from the current directory or as specified by flag |
| [<code>delete</code>](./templates_delete.md) | Delete templates | | [<code>delete</code>](./templates_delete.md) | Delete templates |
| [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. | | [<code>edit</code>](./templates_edit.md) | Edit the metadata of a template by name. |
| [<code>init</code>](./templates_init.md) | Get started with a templated template. | | [<code>init</code>](./templates_init.md) | Get started with a templated template. |
| [<code>list</code>](./templates_list.md) | List all the templates available for the organization | | [<code>list</code>](./templates_list.md) | List all the templates available for the organization |
| [<code>pull</code>](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. | | [<code>pull</code>](./templates_pull.md) | Download the active, latest, or specified version of a template to a path. |
| [<code>push</code>](./templates_push.md) | Push a new template version from the current directory or as specified by flag | | [<code>push</code>](./templates_push.md) | Create or update a template from the current directory or as specified by flag |
| [<code>versions</code>](./templates_versions.md) | Manage different versions of the specified template | | [<code>versions</code>](./templates_versions.md) | Manage different versions of the specified template |

View File

@ -2,7 +2,7 @@
# templates create # 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 ## Usage

View File

@ -130,6 +130,15 @@ Edit the template maximum time before shutdown - workspaces created from this te
Edit the template name. Edit the template name.
### --private
| | |
| ------- | ------------------ |
| Type | <code>bool</code> |
| Default | <code>false</code> |
Disable the default behavior of granting template access to the 'everyone' group. The template permissions must be updated to allow non-admin users to use this template.
### --require-active-version ### --require-active-version
| | | | | |

View File

@ -2,7 +2,7 @@
# templates push # 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 ## 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. Always prompt all parameters. Does not pull parameter values from active template version.
### --create
| | |
| ------- | ------------------ |
| Type | <code>bool</code> |
| Default | <code>false</code> |
Create the template if it does not exist.
### -d, --directory ### -d, --directory
| | | | | |

View File

@ -322,7 +322,7 @@ Edit `main.tf` and update the following fields of the Kubernetes pod resource:
Finally, create the template: Finally, create the template:
```console ```console
coder template create kubernetes -d . coder template push kubernetes -d .
``` ```
This template should be ready to use straight away. This template should be ready to use straight away.

View File

@ -892,7 +892,7 @@
}, },
{ {
"title": "templates create", "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" "path": "cli/templates_create.md"
}, },
{ {
@ -922,7 +922,7 @@
}, },
{ {
"title": "templates push", "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" "path": "cli/templates_push.md"
}, },
{ {

View File

@ -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. the following command to put the template on your Coder instance.
```shell ```shell
coder templates create coder templates push
``` ```
Congrats! You can now navigate to your Coder dashboard and use this Linux on Congrats! You can now navigate to your Coder dashboard and use this Linux on

View File

@ -52,7 +52,7 @@ Coder with Docker has the following advantages:
cd docker 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: 1. Open the dashboard in your browser to create your first workspace:

View File

@ -211,7 +211,7 @@ export CLUSTER_SERVICEACCOUNT_TOKEN=$(kubectl get secrets coder-v2 -n coder-work
Create the template with these values: Create the template with these values:
```shell ```shell
coder templates create \ coder templates push \
--variable host=$CLUSTER_ADDRESS \ --variable host=$CLUSTER_ADDRESS \
--variable cluster_ca_certificate=$CLUSTER_CA_CERTIFICATE \ --variable cluster_ca_certificate=$CLUSTER_CA_CERTIFICATE \
--variable token=$CLUSTER_SERVICEACCOUNT_TOKEN \ --variable token=$CLUSTER_SERVICEACCOUNT_TOKEN \
@ -228,7 +228,7 @@ kubectl cluster-info
# Get cluster CA and token (base64 encoded) # Get cluster CA and token (base64 encoded)
kubectl get secrets coder-service-account-token -n coder-workspaces -o jsonpath="{.data}" kubectl get secrets coder-service-account-token -n coder-workspaces -o jsonpath="{.data}"
coder templates create \ coder templates push \
--variable host=API_ADDRESS \ --variable host=API_ADDRESS \
--variable cluster_ca_certificate=CLUSTER_CA_CERTIFICATE \ --variable cluster_ca_certificate=CLUSTER_CA_CERTIFICATE \
--variable token=CLUSTER_SERVICEACCOUNT_TOKEN \ --variable token=CLUSTER_SERVICEACCOUNT_TOKEN \

View File

@ -808,6 +808,39 @@ func TestTemplateACL(t *testing.T) {
require.Equal(t, http.StatusNotFound, cerr.StatusCode()) 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. // Test that we do not return deleted users.
t.Run("FilterDeletedUsers", func(t *testing.T) { t.Run("FilterDeletedUsers", func(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -155,6 +155,6 @@
"nomad", "nomad",
"container" "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"
} }
] ]

View File

@ -103,7 +103,7 @@ provision:
fi fi
DOCKER_HOST=$(docker context inspect --format '{{.Endpoints.docker.Host}}') 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" 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}" rm -rfv "${temp_template_dir}"
probes: probes:
- description: "docker to be installed" - description: "docker to be installed"

View File

@ -11,7 +11,7 @@ Clone this repository to create a template from any example listed here:
```console ```console
git clone https://github.com/coder/coder git clone https://github.com/coder/coder
cd examples/templates/aws-linux cd examples/templates/aws-linux
coder templates create coder templates push
``` ```
## Community Templates ## Community Templates

View File

@ -47,7 +47,7 @@ To supply values to existing existing Terraform variables you can specify the
`-V` flag. For example `-V` flag. For example
```bash ```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 ## Contributions

View File

@ -95,7 +95,7 @@ The CSI Host Volume plugin is used to mount host volumes into Nomad tasks. This
```shell ```shell
coder template init nomad-docker coder template init nomad-docker
cd nomad-docker cd nomad-docker
coder template create coder template push
``` ```
2. Set up Nomad server address and optional authentication: 2. Set up Nomad server address and optional authentication:

View File

@ -68,7 +68,7 @@ CODER_FIRST_USER_TRIAL="${CODER_FIRST_USER_TRIAL}"
EOF EOF
echo "Importing kubernetes template" 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}" \ --global-config="${CONFIG_DIR}" \
--directory "${CONFIG_DIR}/templates/kubernetes" \ --directory "${CONFIG_DIR}/templates/kubernetes" \
--yes kubernetes --yes kubernetes

View File

@ -177,7 +177,7 @@ fatal() {
DOCKER_HOST="$(docker context inspect --format '{{ .Endpoints.docker.Host }}')" 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" 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 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}" ) || echo "Failed to create a template. The template files are in ${temp_template_dir}"
fi fi

View File

@ -1265,6 +1265,7 @@ export interface UpdateTemplateMeta {
readonly update_workspace_dormant_at: boolean; readonly update_workspace_dormant_at: boolean;
readonly require_active_version: boolean; readonly require_active_version: boolean;
readonly deprecation_message?: string; readonly deprecation_message?: string;
readonly disable_everyone_group_access: boolean;
} }
// From codersdk/users.go // From codersdk/users.go

View File

@ -77,6 +77,7 @@ export const TemplateSettingsForm: FC<TemplateSettingsForm> = ({
update_workspace_dormant_at: false, update_workspace_dormant_at: false,
require_active_version: template.require_active_version, require_active_version: template.require_active_version,
deprecation_message: template.deprecation_message, deprecation_message: template.deprecation_message,
disable_everyone_group_access: false,
}, },
validationSchema, validationSchema,
onSubmit, onSubmit,

View File

@ -47,6 +47,7 @@ const validFormValues: FormValues = {
update_workspace_last_used_at: false, update_workspace_last_used_at: false,
update_workspace_dormant_at: false, update_workspace_dormant_at: false,
require_active_version: false, require_active_version: false,
disable_everyone_group_access: false,
}; };
const renderTemplateSettingsPage = async () => { const renderTemplateSettingsPage = async () => {

View File

@ -118,6 +118,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
update_workspace_last_used_at: false, update_workspace_last_used_at: false,
update_workspace_dormant_at: false, update_workspace_dormant_at: false,
require_active_version: false, require_active_version: false,
disable_everyone_group_access: false,
}, },
validationSchema, validationSchema,
onSubmit: () => { onSubmit: () => {
@ -238,6 +239,7 @@ export const TemplateScheduleForm: FC<TemplateScheduleForm> = ({
update_workspace_last_used_at: form.values.update_workspace_last_used_at, update_workspace_last_used_at: form.values.update_workspace_last_used_at,
update_workspace_dormant_at: form.values.update_workspace_dormant_at, update_workspace_dormant_at: form.values.update_workspace_dormant_at,
require_active_version: false, require_active_version: false,
disable_everyone_group_access: false,
}); });
}; };

View File

@ -37,6 +37,7 @@ const validFormValues: TemplateScheduleFormValues = {
"saturday", "saturday",
"sunday", "sunday",
], ],
disable_everyone_group_access: false,
}; };
const renderTemplateSchedulePage = async () => { const renderTemplateSchedulePage = async () => {