diff --git a/.golangci.yaml b/.golangci.yaml index 658dab717f..424f575bd3 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -215,7 +215,6 @@ linters: - asciicheck - bidichk - bodyclose - - deadcode - dogsled - errcheck - errname @@ -259,4 +258,3 @@ linters: - typecheck - unconvert - unused - - varcheck diff --git a/Makefile b/Makefile index aff1704c28..03a4959249 100644 --- a/Makefile +++ b/Makefile @@ -501,7 +501,8 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me yarn run format:write:only ../docs/admin/prometheus.md docs/cli.md: scripts/clidocgen/main.go $(GO_SRC_FILES) docs/manifest.json - rm -rf ./docs/cli/*.md + # TODO(@ammario): re-enable server.md once we finish clibase migration. + ls ./docs/cli/*.md | grep -vP "\/coder_server" | xargs rm BASE_PATH="." go run ./scripts/clidocgen cd site yarn run format:write:only ../docs/cli.md ../docs/cli/*.md ../docs/manifest.json diff --git a/cli/clibase/clibase.go b/cli/clibase/clibase.go new file mode 100644 index 0000000000..692ea5734c --- /dev/null +++ b/cli/clibase/clibase.go @@ -0,0 +1,86 @@ +// Package clibase offers an all-in-one solution for a highly configurable CLI +// application. Within Coder, we use it for our `server` subcommand, which +// demands more functionality than cobra/viper can offer. +// +// We will extend its usage to the rest of our application, completely replacing +// cobra/viper. It's also a candidate to be broken out into its own open-source +// library, so we avoid deep coupling with Coder concepts. +package clibase + +import ( + "strings" + + "golang.org/x/exp/maps" +) + +// Group describes a hierarchy of groups that an option or command belongs to. +type Group struct { + Parent *Group `json:"parent,omitempty"` + Name string `json:"name,omitempty"` + Children []Group `json:"children,omitempty"` + Description string `json:"description,omitempty"` +} + +func (g *Group) AddChild(child Group) { + child.Parent = g + g.Children = append(g.Children, child) +} + +// Ancestry returns the group and all of its parents, in order. +func (g *Group) Ancestry() []Group { + if g == nil { + return nil + } + + groups := []Group{*g} + for p := g.Parent; p != nil; p = p.Parent { + // Prepend to the slice so that the order is correct. + groups = append([]Group{*p}, groups...) + } + return groups +} + +func (g *Group) FullName() string { + var names []string + for _, g := range g.Ancestry() { + names = append(names, g.Name) + } + return strings.Join(names, " / ") +} + +// Annotations is an arbitrary key-mapping used to extend the Option and Command types. +// Its methods won't panic if the map is nil. +type Annotations map[string]string + +// Mark sets a value on the annotations map, creating one +// if it doesn't exist. Mark does not mutate the original and +// returns a copy. It is suitable for chaining. +func (a Annotations) Mark(key string, value string) Annotations { + var aa Annotations + if a != nil { + aa = maps.Clone(a) + } else { + aa = make(Annotations) + } + aa[key] = value + return aa +} + +// IsSet returns true if the key is set in the annotations map. +func (a Annotations) IsSet(key string) bool { + if a == nil { + return false + } + _, ok := a[key] + return ok +} + +// Get retrieves a key from the map, returning false if the key is not found +// or the map is nil. +func (a Annotations) Get(key string) (string, bool) { + if a == nil { + return "", false + } + v, ok := a[key] + return v, ok +} diff --git a/cli/clibase/cmd.go b/cli/clibase/cmd.go new file mode 100644 index 0000000000..d268e3b0bf --- /dev/null +++ b/cli/clibase/cmd.go @@ -0,0 +1,48 @@ +package clibase + +import "strings" + +// Cmd describes an executable command. +type Cmd struct { + // Parent is the direct parent of the command. + Parent *Cmd + // Children is a list of direct descendants. + Children []*Cmd + // Use is provided in form "command [flags] [args...]". + Use string + // Short is a one-line description of the command. + Short string + // Long is a detailed description of the command, + // presented on its help page. It may contain examples. + Long string + Options OptionSet + Annotations Annotations +} + +// Name returns the first word in the Use string. +func (c *Cmd) Name() string { + return strings.Split(c.Use, " ")[0] +} + +// FullName returns the full invocation name of the command, +// as seen on the command line. +func (c *Cmd) FullName() string { + var names []string + + if c.Parent != nil { + names = append(names, c.Parent.FullName()) + } + names = append(names, c.Name()) + return strings.Join(names, " ") +} + +// FullName returns usage of the command, preceded +// by the usage of its parents. +func (c *Cmd) FullUsage() string { + var uses []string + if c.Parent != nil { + uses = append(uses, c.Parent.FullUsage()) + } + uses = append(uses, c.Use) + return strings.Join(uses, " ") +} diff --git a/cli/clibase/env.go b/cli/clibase/env.go new file mode 100644 index 0000000000..0a5e19e0ff --- /dev/null +++ b/cli/clibase/env.go @@ -0,0 +1,42 @@ +package clibase + +import "strings" + +// name returns the name of the environment variable. +func envName(line string) string { + return strings.ToUpper( + strings.SplitN(line, "=", 2)[0], + ) +} + +// value returns the value of the environment variable. +func envValue(line string) string { + tokens := strings.SplitN(line, "=", 2) + if len(tokens) < 2 { + return "" + } + return tokens[1] +} + +// Var represents a single environment variable of form +// NAME=VALUE. +type EnvVar struct { + Name string + Value string +} + +// EnvsWithPrefix returns all environment variables starting with +// prefix without said prefix. +func EnvsWithPrefix(environ []string, prefix string) []EnvVar { + var filtered []EnvVar + for _, line := range environ { + name := envName(line) + if strings.HasPrefix(name, prefix) { + filtered = append(filtered, EnvVar{ + Name: strings.TrimPrefix(name, prefix), + Value: envValue(line), + }) + } + } + return filtered +} diff --git a/cli/clibase/env_test.go b/cli/clibase/env_test.go new file mode 100644 index 0000000000..55fc0c6efd --- /dev/null +++ b/cli/clibase/env_test.go @@ -0,0 +1,44 @@ +package clibase_test + +import ( + "reflect" + "testing" + + "github.com/coder/coder/cli/clibase" +) + +func TestFilterNamePrefix(t *testing.T) { + t.Parallel() + type args struct { + environ []string + prefix string + } + tests := []struct { + name string + args args + want []clibase.EnvVar + }{ + {"empty", args{[]string{}, "SHIRE"}, nil}, + { + "ONE", + args{ + []string{ + "SHIRE_BRANDYBUCK=hmm", + }, + "SHIRE_", + }, + []clibase.EnvVar{ + {Name: "BRANDYBUCK", Value: "hmm"}, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := clibase.EnvsWithPrefix(tt.args.environ, tt.args.prefix); !reflect.DeepEqual(got, tt.want) { + t.Errorf("EnvsWithPrefix() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cli/clibase/option.go b/cli/clibase/option.go new file mode 100644 index 0000000000..1c3c05fda2 --- /dev/null +++ b/cli/clibase/option.go @@ -0,0 +1,149 @@ +package clibase + +import ( + "os" + + "github.com/hashicorp/go-multierror" + "github.com/spf13/pflag" + "golang.org/x/xerrors" +) + +// Option is a configuration option for a CLI application. +type Option struct { + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + + // Flag is the long name of the flag used to configure this option. If unset, + // flag configuring is disabled. + Flag string `json:"flag,omitempty"` + // FlagShorthand is the one-character shorthand for the flag. If unset, no + // shorthand is used. + FlagShorthand string `json:"flag_shorthand,omitempty"` + + // Env is the environment variable used to configure this option. If unset, + // environment configuring is disabled. + Env string `json:"env,omitempty"` + + // YAML is the YAML key used to configure this option. If unset, YAML + // configuring is disabled. + YAML string `json:"yaml,omitempty"` + + // Default is parsed into Value if set. + Default string `json:"default,omitempty"` + // Value includes the types listed in values.go. + Value pflag.Value `json:"value,omitempty"` + + // Annotations enable extensions to clibase higher up in the stack. It's useful for + // help formatting and documentation generation. + Annotations Annotations `json:"annotations,omitempty"` + + // Group is a group hierarchy that helps organize this option in help, configs + // and other documentation. + Group *Group `json:"group,omitempty"` + + // UseInstead is a list of options that should be used instead of this one. + // The field is used to generate a deprecation warning. + UseInstead []Option `json:"use_instead,omitempty"` + + Hidden bool `json:"hidden,omitempty"` +} + +// OptionSet is a group of options that can be applied to a command. +type OptionSet []Option + +// Add adds the given Options to the OptionSet. +func (s *OptionSet) Add(opts ...Option) { + *s = append(*s, opts...) +} + +// FlagSet returns a pflag.FlagSet for the OptionSet. +func (s *OptionSet) FlagSet() *pflag.FlagSet { + fs := pflag.NewFlagSet("", pflag.ContinueOnError) + for _, opt := range *s { + if opt.Flag == "" { + continue + } + var noOptDefValue string + { + no, ok := opt.Value.(NoOptDefValuer) + if ok { + noOptDefValue = no.NoOptDefValue() + } + } + + fs.AddFlag(&pflag.Flag{ + Name: opt.Flag, + Shorthand: opt.FlagShorthand, + Usage: opt.Description, + Value: opt.Value, + DefValue: "", + Changed: false, + Deprecated: "", + NoOptDefVal: noOptDefValue, + Hidden: opt.Hidden, + }) + } + fs.Usage = func() { + _, _ = os.Stderr.WriteString("Override (*FlagSet).Usage() to print help text.\n") + } + return fs +} + +// ParseEnv parses the given environment variables into the OptionSet. +func (s *OptionSet) ParseEnv(globalPrefix string, environ []string) error { + var merr *multierror.Error + + // We parse environment variables first instead of using a nested loop to + // avoid N*M complexity when there are a lot of options and environment + // variables. + envs := make(map[string]string) + for _, v := range EnvsWithPrefix(environ, globalPrefix) { + envs[v.Name] = v.Value + } + + for _, opt := range *s { + if opt.Env == "" { + continue + } + + envVal, ok := envs[opt.Env] + if !ok { + continue + } + + if err := opt.Value.Set(envVal); err != nil { + merr = multierror.Append( + merr, xerrors.Errorf("parse %q: %w", opt.Name, err), + ) + } + } + + return merr.ErrorOrNil() +} + +// SetDefaults sets the default values for each Option. +// It should be called before all parsing (e.g. ParseFlags, ParseEnv). +func (s *OptionSet) SetDefaults() error { + var merr *multierror.Error + for _, opt := range *s { + if opt.Default == "" { + continue + } + if opt.Value == nil { + merr = multierror.Append( + merr, + xerrors.Errorf( + "parse %q: no Value field set\nFull opt: %+v", + opt.Name, opt, + ), + ) + continue + } + if err := opt.Value.Set(opt.Default); err != nil { + merr = multierror.Append( + merr, xerrors.Errorf("parse %q: %w", opt.Name, err), + ) + } + } + return merr.ErrorOrNil() +} diff --git a/cli/clibase/option_test.go b/cli/clibase/option_test.go new file mode 100644 index 0000000000..00a133b712 --- /dev/null +++ b/cli/clibase/option_test.go @@ -0,0 +1,94 @@ +package clibase_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/cli/clibase" +) + +func TestOptionSet_ParseFlags(t *testing.T) { + t.Parallel() + + t.Run("SimpleString", func(t *testing.T) { + t.Parallel() + + var workspaceName clibase.String + + os := clibase.OptionSet{ + clibase.Option{ + Name: "Workspace Name", + Value: &workspaceName, + Flag: "workspace-name", + FlagShorthand: "n", + }, + } + + var err error + err = os.FlagSet().Parse([]string{"--workspace-name", "foo"}) + require.NoError(t, err) + require.EqualValues(t, "foo", workspaceName) + + err = os.FlagSet().Parse([]string{"-n", "f"}) + require.NoError(t, err) + require.EqualValues(t, "f", workspaceName) + }) + + t.Run("Strings", func(t *testing.T) { + t.Parallel() + + var names clibase.Strings + + os := clibase.OptionSet{ + clibase.Option{ + Name: "name", + Value: &names, + Flag: "name", + FlagShorthand: "n", + }, + } + + err := os.FlagSet().Parse([]string{"--name", "foo", "--name", "bar"}) + require.NoError(t, err) + require.EqualValues(t, []string{"foo", "bar"}, names) + }) + + t.Run("ExtraFlags", func(t *testing.T) { + t.Parallel() + + var workspaceName clibase.String + + os := clibase.OptionSet{ + clibase.Option{ + Name: "Workspace Name", + Value: &workspaceName, + }, + } + + err := os.FlagSet().Parse([]string{"--some-unknown", "foo"}) + require.Error(t, err) + }) +} + +func TestOptionSet_ParseEnv(t *testing.T) { + t.Parallel() + + t.Run("SimpleString", func(t *testing.T) { + t.Parallel() + + var workspaceName clibase.String + + os := clibase.OptionSet{ + clibase.Option{ + Name: "Workspace Name", + Value: &workspaceName, + Env: "WORKSPACE_NAME", + }, + } + + err := os.ParseEnv("CODER_", []string{"CODER_WORKSPACE_NAME=foo"}) + require.NoError(t, err) + require.EqualValues(t, "foo", workspaceName) + }) +} diff --git a/cli/clibase/values.go b/cli/clibase/values.go new file mode 100644 index 0000000000..80113712c8 --- /dev/null +++ b/cli/clibase/values.go @@ -0,0 +1,335 @@ +package clibase + +import ( + "encoding/csv" + "encoding/json" + "fmt" + "net" + "net/url" + "strconv" + "strings" + "time" + + "github.com/spf13/pflag" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" +) + +// NoOptDefValuer describes behavior when no +// option is passed into the flag. +// +// This is useful for boolean or otherwise binary flags. +type NoOptDefValuer interface { + NoOptDefValue() string +} + +// values.go contains a standard set of value types that can be used as +// Option Values. + +type Int64 int64 + +func (i *Int64) Set(s string) error { + ii, err := strconv.ParseInt(s, 10, 64) + *i = Int64(ii) + return err +} + +func (i Int64) Value() int64 { + return int64(i) +} + +func (i Int64) String() string { + return strconv.Itoa(int(i)) +} + +func (Int64) Type() string { + return "int" +} + +type Bool bool + +func (b *Bool) Set(s string) error { + if s == "" { + *b = Bool(false) + return nil + } + bb, err := strconv.ParseBool(s) + *b = Bool(bb) + return err +} + +func (*Bool) NoOptDefValue() string { + return "true" +} + +func (b Bool) String() string { + return strconv.FormatBool(bool(b)) +} + +func (b Bool) Value() bool { + return bool(b) +} + +func (Bool) Type() string { + return "bool" +} + +type String string + +func (*String) NoOptDefValue() string { + return "" +} + +func (s *String) Set(v string) error { + *s = String(v) + return nil +} + +func (s String) String() string { + return string(s) +} + +func (s String) Value() string { + return string(s) +} + +func (String) Type() string { + return "string" +} + +var _ pflag.SliceValue = &Strings{} + +// Strings is a slice of strings that implements pflag.Value and pflag.SliceValue. +type Strings []string + +func (s *Strings) Append(v string) error { + *s = append(*s, v) + return nil +} + +func (s *Strings) Replace(vals []string) error { + *s = vals + return nil +} + +func (s *Strings) GetSlice() []string { + return *s +} + +func readAsCSV(v string) ([]string, error) { + return csv.NewReader(strings.NewReader(v)).Read() +} + +func writeAsCSV(vals []string) string { + var sb strings.Builder + err := csv.NewWriter(&sb).Write(vals) + if err != nil { + return fmt.Sprintf("error: %s", err) + } + return sb.String() +} + +func (s *Strings) Set(v string) error { + ss, err := readAsCSV(v) + if err != nil { + return err + } + *s = append(*s, ss...) + return nil +} + +func (s Strings) String() string { + return writeAsCSV([]string(s)) +} + +func (s Strings) Value() []string { + return []string(s) +} + +func (Strings) Type() string { + return "strings" +} + +type Duration time.Duration + +func (d *Duration) Set(v string) error { + dd, err := time.ParseDuration(v) + *d = Duration(dd) + return err +} + +func (d *Duration) Value() time.Duration { + return time.Duration(*d) +} + +func (d *Duration) String() string { + return time.Duration(*d).String() +} + +func (d *Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + return d.Set(s) +} + +func (Duration) Type() string { + return "duration" +} + +type URL url.URL + +func (u *URL) Set(v string) error { + uu, err := url.Parse(v) + if err != nil { + return err + } + *u = URL(*uu) + return nil +} + +func (u *URL) String() string { + uu := url.URL(*u) + return uu.String() +} + +func (u *URL) MarshalJSON() ([]byte, error) { + return json.Marshal(u.String()) +} + +func (u *URL) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + return u.Set(s) +} + +func (*URL) Type() string { + return "url" +} + +func (u *URL) Value() *url.URL { + return (*url.URL)(u) +} + +// HostPort is a host:port pair. +type HostPort struct { + Host string + Port string +} + +func (hp *HostPort) Set(v string) error { + if v == "" { + return xerrors.Errorf("must not be empty") + } + var err error + hp.Host, hp.Port, err = net.SplitHostPort(v) + return err +} + +func (hp *HostPort) String() string { + if hp.Host == "" && hp.Port == "" { + return "" + } + // Warning: net.JoinHostPort must be used over concatenation to support + // IPv6 addresses. + return net.JoinHostPort(hp.Host, hp.Port) +} + +func (hp *HostPort) MarshalJSON() ([]byte, error) { + return json.Marshal(hp.String()) +} + +func (hp *HostPort) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + if s == "" { + hp.Host = "" + hp.Port = "" + return nil + } + return hp.Set(s) +} + +func (*HostPort) Type() string { + return "bind-address" +} + +var ( + _ yaml.Marshaler = new(Struct[struct{}]) + _ yaml.Unmarshaler = new(Struct[struct{}]) +) + +// Struct is a special value type that encodes an arbitrary struct. +// It implements the flag.Value interface, but in general these values should +// only be accepted via config for ergonomics. +// +// The string encoding type is YAML. +type Struct[T any] struct { + Value T +} + +func (s *Struct[T]) Set(v string) error { + return yaml.Unmarshal([]byte(v), &s.Value) +} + +func (s *Struct[T]) String() string { + byt, err := yaml.Marshal(s.Value) + if err != nil { + return "decode failed: " + err.Error() + } + return string(byt) +} + +func (s *Struct[T]) MarshalYAML() (interface{}, error) { + var n yaml.Node + err := n.Encode(s.Value) + if err != nil { + return nil, err + } + return n, nil +} + +func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error { + return n.Decode(&s.Value) +} + +func (s *Struct[T]) Type() string { + return fmt.Sprintf("struct[%T]", s.Value) +} + +func (s *Struct[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.Value) +} + +func (s *Struct[T]) UnmarshalJSON(b []byte) error { + return json.Unmarshal(b, &s.Value) +} + +// DiscardValue does nothing but implements the pflag.Value interface. +// It's useful in cases where you want to accept an option, but access the +// underlying value directly instead of through the Option methods. +type DiscardValue struct{} + +func (DiscardValue) Set(string) error { + return nil +} + +func (DiscardValue) String() string { + return "" +} + +func (DiscardValue) Type() string { + return "discard" +} diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go new file mode 100644 index 0000000000..82abee342f --- /dev/null +++ b/cli/clibase/yaml.go @@ -0,0 +1,105 @@ +package clibase + +import ( + "github.com/iancoleman/strcase" + "github.com/mitchellh/go-wordwrap" + "golang.org/x/xerrors" + "gopkg.in/yaml.v3" +) + +// deepMapNode returns the mapping node at the given path, +// creating it if it doesn't exist. +func deepMapNode(n *yaml.Node, path []string, headComment string) *yaml.Node { + if len(path) == 0 { + return n + } + + // Name is every two nodes. + for i := 0; i < len(n.Content)-1; i += 2 { + if n.Content[i].Value == path[0] { + // Found matching name, recurse. + return deepMapNode(n.Content[i+1], path[1:], headComment) + } + } + + // Not found, create it. + nameNode := yaml.Node{ + Kind: yaml.ScalarNode, + Value: path[0], + HeadComment: headComment, + } + valueNode := yaml.Node{ + Kind: yaml.MappingNode, + } + n.Content = append(n.Content, &nameNode) + n.Content = append(n.Content, &valueNode) + return deepMapNode(&valueNode, path[1:], headComment) +} + +// ToYAML converts the option set to a YAML node, that can be +// converted into bytes via yaml.Marshal. +// +// The node is returned to enable post-processing higher up in +// the stack. +func (s OptionSet) ToYAML() (*yaml.Node, error) { + root := yaml.Node{ + Kind: yaml.MappingNode, + } + + for _, opt := range s { + if opt.YAML == "" { + continue + } + nameNode := yaml.Node{ + Kind: yaml.ScalarNode, + Value: opt.YAML, + HeadComment: wordwrap.WrapString(opt.Description, 80), + } + var valueNode yaml.Node + if m, ok := opt.Value.(yaml.Marshaler); ok { + v, err := m.MarshalYAML() + if err != nil { + return nil, xerrors.Errorf( + "marshal %q: %w", opt.Name, err, + ) + } + valueNode, ok = v.(yaml.Node) + if !ok { + return nil, xerrors.Errorf( + "marshal %q: unexpected underlying type %T", + opt.Name, v, + ) + } + } else { + valueNode = yaml.Node{ + Kind: yaml.ScalarNode, + Value: opt.Value.String(), + } + } + var group []string + for _, g := range opt.Group.Ancestry() { + if g.Name == "" { + return nil, xerrors.Errorf( + "group name is empty for %q, groups: %+v", + opt.Name, + opt.Group, + ) + } + group = append(group, strcase.ToLowerCamel(g.Name)) + } + var groupDesc string + if opt.Group != nil { + groupDesc = wordwrap.WrapString(opt.Group.Description, 80) + } + parentValueNode := deepMapNode( + &root, group, + groupDesc, + ) + parentValueNode.Content = append( + parentValueNode.Content, + &nameNode, + &valueNode, + ) + } + return &root, nil +} diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go new file mode 100644 index 0000000000..1e14873881 --- /dev/null +++ b/cli/clibase/yaml_test.go @@ -0,0 +1,57 @@ +package clibase_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" + + "github.com/coder/coder/cli/clibase" +) + +func TestOption_ToYAML(t *testing.T) { + t.Parallel() + + t.Run("RequireKey", func(t *testing.T) { + t.Parallel() + var workspaceName clibase.String + os := clibase.OptionSet{ + clibase.Option{ + Name: "Workspace Name", + Value: &workspaceName, + Default: "billie", + }, + } + + node, err := os.ToYAML() + require.NoError(t, err) + require.Len(t, node.Content, 0) + }) + + t.Run("SimpleString", func(t *testing.T) { + t.Parallel() + + var workspaceName clibase.String + + os := clibase.OptionSet{ + clibase.Option{ + Name: "Workspace Name", + Value: &workspaceName, + Default: "billie", + Description: "The workspace's name", + Group: &clibase.Group{Name: "Names"}, + YAML: "workspaceName", + }, + } + + err := os.SetDefaults() + require.NoError(t, err) + + n, err := os.ToYAML() + require.NoError(t, err) + // Visually inspect for now. + byt, err := yaml.Marshal(n) + require.NoError(t, err) + t.Logf("Raw YAML:\n%s", string(byt)) + }) +} diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index a28471e0c1..a0e235df4f 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -3,6 +3,7 @@ package clitest import ( "archive/tar" "bytes" + "context" "errors" "io" "io/ioutil" @@ -10,14 +11,17 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/coder/coder/cli" "github.com/coder/coder/cli/config" "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/echo" + "github.com/coder/coder/testutil" ) // New creates a CLI instance with a configuration pointed to a @@ -26,6 +30,22 @@ func New(t *testing.T, args ...string) (*cobra.Command, config.Root) { return NewWithSubcommands(t, cli.AGPL(), args...) } +type logWriter struct { + prefix string + t *testing.T +} + +func (l *logWriter) Write(p []byte) (n int, err error) { + trimmed := strings.TrimSpace(string(p)) + if trimmed == "" { + return len(p), nil + } + l.t.Log( + l.prefix + ": " + trimmed, + ) + return len(p), nil +} + func NewWithSubcommands( t *testing.T, subcommands []*cobra.Command, args ...string, ) (*cobra.Command, config.Root) { @@ -34,10 +54,9 @@ func NewWithSubcommands( root := config.Root(dir) cmd.SetArgs(append([]string{"--global-config", dir}, args...)) - // We could consider using writers - // that log via t.Log here instead. - cmd.SetOut(io.Discard) - cmd.SetErr(io.Discard) + // These can be overridden by the test. + cmd.SetOut(&logWriter{prefix: "stdout", t: t}) + cmd.SetErr(&logWriter{prefix: "stderr", t: t}) return cmd, root } @@ -98,3 +117,34 @@ func extractTar(t *testing.T, data []byte, directory string) { } } } + +// Start runs the command in a goroutine and cleans it up when +// the test completed. +func Start(ctx context.Context, t *testing.T, cmd *cobra.Command) { + t.Helper() + + closeCh := make(chan struct{}) + + deadline, hasDeadline := ctx.Deadline() + if !hasDeadline { + // We don't want to wait the full 5 minutes for a test to time out. + deadline = time.Now().Add(testutil.WaitMedium) + } + + ctx, cancel := context.WithDeadline(ctx, deadline) + + go func() { + defer cancel() + defer close(closeCh) + err := cmd.ExecuteContext(ctx) + if ctx.Err() == nil { + assert.NoError(t, err) + } + }() + + // Don't exit test routine until server is done. + t.Cleanup(func() { + cancel() + <-closeCh + }) +} diff --git a/cli/config/file.go b/cli/config/file.go index 6ec0f28874..b3707b3c2a 100644 --- a/cli/config/file.go +++ b/cli/config/file.go @@ -4,6 +4,8 @@ import ( "io" "os" "path/filepath" + + "github.com/kirsle/configdir" ) const ( @@ -46,10 +48,6 @@ func (r Root) PostgresPort() File { return File(filepath.Join(r.PostgresPath(), "port")) } -func (r Root) DeploymentConfigPath() string { - return filepath.Join(string(r), "server.yaml") -} - // File provides convenience methods for interacting with *os.File. type File string @@ -98,3 +96,7 @@ func read(path string) ([]byte, error) { defer fi.Close() return io.ReadAll(fi) } + +func DefaultDir() string { + return configdir.LocalConfig("coderv2") +} diff --git a/cli/deployment/config.go b/cli/deployment/config.go deleted file mode 100644 index 68cbb6acab..0000000000 --- a/cli/deployment/config.go +++ /dev/null @@ -1,896 +0,0 @@ -package deployment - -import ( - "flag" - "fmt" - "os" - "path/filepath" - "reflect" - "strings" - "time" - - "github.com/coreos/go-oidc/v3/oidc" - "github.com/spf13/pflag" - "github.com/spf13/viper" - "golang.org/x/xerrors" - - "github.com/coder/coder/buildinfo" - "github.com/coder/coder/cli/cliui" - "github.com/coder/coder/cli/config" - "github.com/coder/coder/codersdk" -) - -func newConfig() *codersdk.DeploymentConfig { - return &codersdk.DeploymentConfig{ - AccessURL: &codersdk.DeploymentConfigField[string]{ - Name: "Access URL", - Usage: "External URL to access your deployment. This must be accessible by all provisioned workspaces.", - Flag: "access-url", - }, - WildcardAccessURL: &codersdk.DeploymentConfigField[string]{ - Name: "Wildcard Access URL", - Usage: "Specifies the wildcard hostname to use for workspace applications in the form \"*.example.com\".", - Flag: "wildcard-access-url", - }, - RedirectToAccessURL: &codersdk.DeploymentConfigField[bool]{ - Name: "Redirect to Access URL", - Usage: "Specifies whether to redirect requests that do not match the access URL host.", - Flag: "redirect-to-access-url", - }, - // DEPRECATED: Use HTTPAddress or TLS.Address instead. - Address: &codersdk.DeploymentConfigField[string]{ - Name: "Address", - Usage: "Bind address of the server.", - Flag: "address", - Shorthand: "a", - // Deprecated, so we don't have a default. If set, it will overwrite - // HTTPAddress and TLS.Address and print a warning. - Hidden: true, - Default: "", - }, - HTTPAddress: &codersdk.DeploymentConfigField[string]{ - Name: "Address", - Usage: "HTTP bind address of the server. Unset to disable the HTTP endpoint.", - Flag: "http-address", - Default: "127.0.0.1:3000", - }, - AutobuildPollInterval: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Autobuild Poll Interval", - Usage: "Interval to poll for scheduled workspace builds.", - Flag: "autobuild-poll-interval", - Hidden: true, - Default: time.Minute, - }, - DERP: &codersdk.DERP{ - Server: &codersdk.DERPServerConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "DERP Server Enable", - Usage: "Whether to enable or disable the embedded DERP relay server.", - Flag: "derp-server-enable", - Default: true, - }, - RegionID: &codersdk.DeploymentConfigField[int]{ - Name: "DERP Server Region ID", - Usage: "Region ID to use for the embedded DERP server.", - Flag: "derp-server-region-id", - Default: 999, - }, - RegionCode: &codersdk.DeploymentConfigField[string]{ - Name: "DERP Server Region Code", - Usage: "Region code to use for the embedded DERP server.", - Flag: "derp-server-region-code", - Default: "coder", - }, - RegionName: &codersdk.DeploymentConfigField[string]{ - Name: "DERP Server Region Name", - Usage: "Region name that for the embedded DERP server.", - Flag: "derp-server-region-name", - Default: "Coder Embedded Relay", - }, - STUNAddresses: &codersdk.DeploymentConfigField[[]string]{ - Name: "DERP Server STUN Addresses", - Usage: "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", - Flag: "derp-server-stun-addresses", - Default: []string{"stun.l.google.com:19302"}, - }, - RelayURL: &codersdk.DeploymentConfigField[string]{ - Name: "DERP Server Relay URL", - Usage: "An HTTP URL that is accessible by other replicas to relay DERP traffic. Required for high availability.", - Flag: "derp-server-relay-url", - Enterprise: true, - }, - }, - Config: &codersdk.DERPConfig{ - URL: &codersdk.DeploymentConfigField[string]{ - Name: "DERP Config URL", - Usage: "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", - Flag: "derp-config-url", - }, - Path: &codersdk.DeploymentConfigField[string]{ - Name: "DERP Config Path", - Usage: "Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/", - Flag: "derp-config-path", - }, - }, - }, - GitAuth: &codersdk.DeploymentConfigField[[]codersdk.GitAuthConfig]{ - Name: "Git Auth", - Usage: "Automatically authenticate Git inside workspaces.", - Flag: "gitauth", - Default: []codersdk.GitAuthConfig{}, - }, - Prometheus: &codersdk.PrometheusConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "Prometheus Enable", - Usage: "Serve prometheus metrics on the address defined by prometheus address.", - Flag: "prometheus-enable", - }, - Address: &codersdk.DeploymentConfigField[string]{ - Name: "Prometheus Address", - Usage: "The bind address to serve prometheus metrics.", - Flag: "prometheus-address", - Default: "127.0.0.1:2112", - }, - }, - Pprof: &codersdk.PprofConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "Pprof Enable", - Usage: "Serve pprof metrics on the address defined by pprof address.", - Flag: "pprof-enable", - }, - Address: &codersdk.DeploymentConfigField[string]{ - Name: "Pprof Address", - Usage: "The bind address to serve pprof.", - Flag: "pprof-address", - Default: "127.0.0.1:6060", - }, - }, - ProxyTrustedHeaders: &codersdk.DeploymentConfigField[[]string]{ - Name: "Proxy Trusted Headers", - Flag: "proxy-trusted-headers", - Usage: "Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, True-Client-Ip, X-Forwarded-For", - }, - ProxyTrustedOrigins: &codersdk.DeploymentConfigField[[]string]{ - Name: "Proxy Trusted Origins", - Flag: "proxy-trusted-origins", - Usage: "Origin addresses to respect \"proxy-trusted-headers\". e.g. 192.168.1.0/24", - }, - CacheDirectory: &codersdk.DeploymentConfigField[string]{ - Name: "Cache Directory", - Usage: "The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.", - Flag: "cache-dir", - Default: DefaultCacheDir(), - }, - InMemoryDatabase: &codersdk.DeploymentConfigField[bool]{ - Name: "In Memory Database", - Usage: "Controls whether data will be stored in an in-memory database.", - Flag: "in-memory", - Hidden: true, - }, - PostgresURL: &codersdk.DeploymentConfigField[string]{ - Name: "Postgres Connection URL", - Usage: "URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\".", - Flag: "postgres-url", - Secret: true, - }, - OAuth2: &codersdk.OAuth2Config{ - Github: &codersdk.OAuth2GithubConfig{ - ClientID: &codersdk.DeploymentConfigField[string]{ - Name: "OAuth2 GitHub Client ID", - Usage: "Client ID for Login with GitHub.", - Flag: "oauth2-github-client-id", - }, - ClientSecret: &codersdk.DeploymentConfigField[string]{ - Name: "OAuth2 GitHub Client Secret", - Usage: "Client secret for Login with GitHub.", - Flag: "oauth2-github-client-secret", - Secret: true, - }, - AllowedOrgs: &codersdk.DeploymentConfigField[[]string]{ - Name: "OAuth2 GitHub Allowed Orgs", - Usage: "Organizations the user must be a member of to Login with GitHub.", - Flag: "oauth2-github-allowed-orgs", - }, - AllowedTeams: &codersdk.DeploymentConfigField[[]string]{ - Name: "OAuth2 GitHub Allowed Teams", - Usage: "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", - Flag: "oauth2-github-allowed-teams", - }, - AllowSignups: &codersdk.DeploymentConfigField[bool]{ - Name: "OAuth2 GitHub Allow Signups", - Usage: "Whether new users can sign up with GitHub.", - Flag: "oauth2-github-allow-signups", - }, - AllowEveryone: &codersdk.DeploymentConfigField[bool]{ - Name: "OAuth2 GitHub Allow Everyone", - Usage: "Allow all logins, setting this option means allowed orgs and teams must be empty.", - Flag: "oauth2-github-allow-everyone", - }, - EnterpriseBaseURL: &codersdk.DeploymentConfigField[string]{ - Name: "OAuth2 GitHub Enterprise Base URL", - Usage: "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", - Flag: "oauth2-github-enterprise-base-url", - }, - }, - }, - OIDC: &codersdk.OIDCConfig{ - AllowSignups: &codersdk.DeploymentConfigField[bool]{ - Name: "OIDC Allow Signups", - Usage: "Whether new users can sign up with OIDC.", - Flag: "oidc-allow-signups", - Default: true, - }, - ClientID: &codersdk.DeploymentConfigField[string]{ - Name: "OIDC Client ID", - Usage: "Client ID to use for Login with OIDC.", - Flag: "oidc-client-id", - }, - ClientSecret: &codersdk.DeploymentConfigField[string]{ - Name: "OIDC Client Secret", - Usage: "Client secret to use for Login with OIDC.", - Flag: "oidc-client-secret", - Secret: true, - }, - EmailDomain: &codersdk.DeploymentConfigField[[]string]{ - Name: "OIDC Email Domain", - Usage: "Email domains that clients logging in with OIDC must match.", - Flag: "oidc-email-domain", - }, - IssuerURL: &codersdk.DeploymentConfigField[string]{ - Name: "OIDC Issuer URL", - Usage: "Issuer URL to use for Login with OIDC.", - Flag: "oidc-issuer-url", - }, - Scopes: &codersdk.DeploymentConfigField[[]string]{ - Name: "OIDC Scopes", - Usage: "Scopes to grant when authenticating with OIDC.", - Flag: "oidc-scopes", - Default: []string{oidc.ScopeOpenID, "profile", "email"}, - }, - IgnoreEmailVerified: &codersdk.DeploymentConfigField[bool]{ - Name: "OIDC Ignore Email Verified", - Usage: "Ignore the email_verified claim from the upstream provider.", - Flag: "oidc-ignore-email-verified", - Default: false, - }, - UsernameField: &codersdk.DeploymentConfigField[string]{ - Name: "OIDC Username Field", - Usage: "OIDC claim field to use as the username.", - Flag: "oidc-username-field", - Default: "preferred_username", - }, - SignInText: &codersdk.DeploymentConfigField[string]{ - Name: "OpenID Connect sign in text", - Usage: "The text to show on the OpenID Connect sign in button", - Flag: "oidc-sign-in-text", - Default: "OpenID Connect", - }, - IconURL: &codersdk.DeploymentConfigField[string]{ - Name: "OpenID connect icon URL", - Usage: "URL pointing to the icon to use on the OepnID Connect login button", - Flag: "oidc-icon-url", - }, - }, - - Telemetry: &codersdk.TelemetryConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "Telemetry Enable", - Usage: "Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.", - Flag: "telemetry", - Default: flag.Lookup("test.v") == nil, - }, - Trace: &codersdk.DeploymentConfigField[bool]{ - Name: "Telemetry Trace", - Usage: "Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option.", - Flag: "telemetry-trace", - Default: flag.Lookup("test.v") == nil, - }, - URL: &codersdk.DeploymentConfigField[string]{ - Name: "Telemetry URL", - Usage: "URL to send telemetry.", - Flag: "telemetry-url", - Hidden: true, - Default: "https://telemetry.coder.com", - }, - }, - TLS: &codersdk.TLSConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "TLS Enable", - Usage: "Whether TLS will be enabled.", - Flag: "tls-enable", - }, - Address: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Address", - Usage: "HTTPS bind address of the server.", - Flag: "tls-address", - Default: "127.0.0.1:3443", - }, - // DEPRECATED: Use RedirectToAccessURL instead. - RedirectHTTP: &codersdk.DeploymentConfigField[bool]{ - Name: "Redirect HTTP to HTTPS", - Usage: "Whether HTTP requests will be redirected to the access URL (if it's a https URL and TLS is enabled). Requests to local IP addresses are never redirected regardless of this setting.", - Flag: "tls-redirect-http-to-https", - Default: true, - Hidden: true, - }, - CertFiles: &codersdk.DeploymentConfigField[[]string]{ - Name: "TLS Certificate Files", - Usage: "Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file.", - Flag: "tls-cert-file", - }, - ClientCAFile: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Client CA Files", - Usage: "PEM-encoded Certificate Authority file used for checking the authenticity of client", - Flag: "tls-client-ca-file", - }, - ClientAuth: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Client Auth", - Usage: "Policy the server will follow for TLS Client Authentication. Accepted values are \"none\", \"request\", \"require-any\", \"verify-if-given\", or \"require-and-verify\".", - Flag: "tls-client-auth", - Default: "none", - }, - KeyFiles: &codersdk.DeploymentConfigField[[]string]{ - Name: "TLS Key Files", - Usage: "Paths to the private keys for each of the certificates. It requires a PEM-encoded file.", - Flag: "tls-key-file", - }, - MinVersion: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Minimum Version", - Usage: "Minimum supported version of TLS. Accepted values are \"tls10\", \"tls11\", \"tls12\" or \"tls13\"", - Flag: "tls-min-version", - Default: "tls12", - }, - ClientCertFile: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Client Cert File", - Usage: "Path to certificate for client TLS authentication. It requires a PEM-encoded file.", - Flag: "tls-client-cert-file", - }, - ClientKeyFile: &codersdk.DeploymentConfigField[string]{ - Name: "TLS Client Key File", - Usage: "Path to key for client TLS authentication. It requires a PEM-encoded file.", - Flag: "tls-client-key-file", - }, - }, - Trace: &codersdk.TraceConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "Trace Enable", - Usage: "Whether application tracing data is collected. It exports to a backend configured by environment variables. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md", - Flag: "trace", - }, - HoneycombAPIKey: &codersdk.DeploymentConfigField[string]{ - Name: "Trace Honeycomb API Key", - Usage: "Enables trace exporting to Honeycomb.io using the provided API Key.", - Flag: "trace-honeycomb-api-key", - Secret: true, - }, - CaptureLogs: &codersdk.DeploymentConfigField[bool]{ - Name: "Capture Logs in Traces", - Usage: "Enables capturing of logs as events in traces. This is useful for debugging, but may result in a very large amount of events being sent to the tracing backend which may incur significant costs. If the verbose flag was supplied, debug-level logs will be included.", - Flag: "trace-logs", - }, - }, - SecureAuthCookie: &codersdk.DeploymentConfigField[bool]{ - Name: "Secure Auth Cookie", - Usage: "Controls if the 'Secure' property is set on browser session cookies.", - Flag: "secure-auth-cookie", - }, - StrictTransportSecurity: &codersdk.DeploymentConfigField[int]{ - Name: "Strict-Transport-Security", - Usage: "Controls if the 'Strict-Transport-Security' header is set on all static file responses. " + - "This header should only be set if the server is accessed via HTTPS. This value is the MaxAge in seconds of " + - "the header.", - Default: 0, - Flag: "strict-transport-security", - }, - StrictTransportSecurityOptions: &codersdk.DeploymentConfigField[[]string]{ - Name: "Strict-Transport-Security Options", - Usage: "Two optional fields can be set in the Strict-Transport-Security header; 'includeSubDomains' and 'preload'. " + - "The 'strict-transport-security' flag must be set to a non-zero value for these options to be used.", - Flag: "strict-transport-security-options", - }, - SSHKeygenAlgorithm: &codersdk.DeploymentConfigField[string]{ - Name: "SSH Keygen Algorithm", - Usage: "The algorithm to use for generating ssh keys. Accepted values are \"ed25519\", \"ecdsa\", or \"rsa4096\".", - Flag: "ssh-keygen-algorithm", - Default: "ed25519", - }, - MetricsCacheRefreshInterval: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Metrics Cache Refresh Interval", - Usage: "How frequently metrics are refreshed", - Flag: "metrics-cache-refresh-interval", - Hidden: true, - Default: time.Hour, - }, - AgentStatRefreshInterval: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Agent Stat Refresh Interval", - Usage: "How frequently agent stats are recorded", - Flag: "agent-stats-refresh-interval", - Hidden: true, - Default: 10 * time.Minute, - }, - AgentFallbackTroubleshootingURL: &codersdk.DeploymentConfigField[string]{ - Name: "Agent Fallback Troubleshooting URL", - Usage: "URL to use for agent troubleshooting when not set in the template", - Flag: "agent-fallback-troubleshooting-url", - Hidden: true, - Default: "https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates", - }, - AuditLogging: &codersdk.DeploymentConfigField[bool]{ - Name: "Audit Logging", - Usage: "Specifies whether audit logging is enabled.", - Flag: "audit-logging", - Default: true, - Enterprise: true, - }, - BrowserOnly: &codersdk.DeploymentConfigField[bool]{ - Name: "Browser Only", - Usage: "Whether Coder only allows connections to workspaces via the browser.", - Flag: "browser-only", - Enterprise: true, - }, - SCIMAPIKey: &codersdk.DeploymentConfigField[string]{ - Name: "SCIM API Key", - Usage: "Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication.", - Flag: "scim-auth-header", - Enterprise: true, - Secret: true, - }, - Provisioner: &codersdk.ProvisionerConfig{ - Daemons: &codersdk.DeploymentConfigField[int]{ - Name: "Provisioner Daemons", - Usage: "Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this.", - Flag: "provisioner-daemons", - Default: 3, - }, - DaemonPollInterval: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Poll Interval", - Usage: "Time to wait before polling for a new job.", - Flag: "provisioner-daemon-poll-interval", - Default: time.Second, - }, - DaemonPollJitter: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Poll Jitter", - Usage: "Random jitter added to the poll interval.", - Flag: "provisioner-daemon-poll-jitter", - Default: 100 * time.Millisecond, - }, - ForceCancelInterval: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Force Cancel Interval", - Usage: "Time to force cancel provisioning tasks that are stuck.", - Flag: "provisioner-force-cancel-interval", - Default: 10 * time.Minute, - }, - }, - RateLimit: &codersdk.RateLimitConfig{ - DisableAll: &codersdk.DeploymentConfigField[bool]{ - Name: "Disable All Rate Limits", - Usage: "Disables all rate limits. This is not recommended in production.", - Flag: "dangerous-disable-rate-limits", - Default: false, - }, - API: &codersdk.DeploymentConfigField[int]{ - Name: "API Rate Limit", - Usage: "Maximum number of requests per minute allowed to the API per user, or per IP address for unauthenticated users. Negative values mean no rate limit. Some API endpoints have separate strict rate limits regardless of this value to prevent denial-of-service or brute force attacks.", - // Change the env from the auto-generated CODER_RATE_LIMIT_API to the - // old value to avoid breaking existing deployments. - EnvOverride: "CODER_API_RATE_LIMIT", - Flag: "api-rate-limit", - Default: 512, - }, - }, - // DEPRECATED: use Experiments instead. - Experimental: &codersdk.DeploymentConfigField[bool]{ - Name: "Experimental", - Usage: "Enable experimental features. Experimental features are not ready for production.", - Flag: "experimental", - Default: false, - Hidden: true, - }, - Experiments: &codersdk.DeploymentConfigField[[]string]{ - Name: "Experiments", - Usage: "Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments.", - Flag: "experiments", - Default: []string{}, - }, - UpdateCheck: &codersdk.DeploymentConfigField[bool]{ - Name: "Update Check", - Usage: "Periodically check for new releases of Coder and inform the owner. The check is performed once per day.", - Flag: "update-check", - Default: flag.Lookup("test.v") == nil && !buildinfo.IsDev(), - }, - MaxTokenLifetime: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Max Token Lifetime", - Usage: "The maximum lifetime duration users can specify when creating an API token.", - Flag: "max-token-lifetime", - // max time.Duration is 290 years - Default: 290 * 365 * 24 * time.Hour, - }, - Swagger: &codersdk.SwaggerConfig{ - Enable: &codersdk.DeploymentConfigField[bool]{ - Name: "Enable swagger endpoint", - Usage: "Expose the swagger endpoint via /swagger.", - Flag: "swagger-enable", - Default: false, - }, - }, - Logging: &codersdk.LoggingConfig{ - Human: &codersdk.DeploymentConfigField[string]{ - Name: "Human Log Location", - Usage: "Output human-readable logs to a given file.", - Flag: "log-human", - Default: "/dev/stderr", - }, - JSON: &codersdk.DeploymentConfigField[string]{ - Name: "JSON Log Location", - Usage: "Output JSON logs to a given file.", - Flag: "log-json", - Default: "", - }, - Stackdriver: &codersdk.DeploymentConfigField[string]{ - Name: "Stackdriver Log Location", - Usage: "Output Stackdriver compatible logs to a given file.", - Flag: "log-stackdriver", - Default: "", - }, - }, - Dangerous: &codersdk.DangerousConfig{ - AllowPathAppSharing: &codersdk.DeploymentConfigField[bool]{ - Name: "DANGEROUS: Allow Path App Sharing", - Usage: "Allow workspace apps that are not served from subdomains to be shared. Path-based app sharing is DISABLED by default for security purposes. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security.", - Flag: "dangerous-allow-path-app-sharing", - Default: false, - }, - AllowPathAppSiteOwnerAccess: &codersdk.DeploymentConfigField[bool]{ - Name: "DANGEROUS: Allow Site Owners to Access Path Apps", - Usage: "Allow site-owners to access workspace apps from workspaces they do not own. Owners cannot access path-based apps they do not own by default. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security.", - Flag: "dangerous-allow-path-app-site-owner-access", - Default: false, - }, - }, - DisablePathApps: &codersdk.DeploymentConfigField[bool]{ - Name: "Disable Path Apps", - Usage: "Disable workspace apps that are not served from subdomains. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. This is recommended for security purposes if a --wildcard-access-url is configured.", - Flag: "disable-path-apps", - Default: false, - }, - SessionDuration: &codersdk.DeploymentConfigField[time.Duration]{ - Name: "Session Duration", - Usage: "The token expiry duration for browser sessions. Sessions may last longer if they are actively making requests, but this functionality can be disabled via --disable-session-expiry-refresh.", - Flag: "session-duration", - Default: 24 * time.Hour, - }, - DisableSessionExpiryRefresh: &codersdk.DeploymentConfigField[bool]{ - Name: "Disable Session Expiry Refresh", - Usage: "Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached.", - Flag: "disable-session-expiry-refresh", - Default: false, - }, - DisablePasswordAuth: &codersdk.DeploymentConfigField[bool]{ - Name: "Disable Password Authentication", - Usage: "Disable password authentication. This is recommended for security purposes in production deployments that rely on an identity provider. Any user with the owner role will be able to sign in with their password regardless of this setting to avoid potential lock out. If you are locked out of your account, you can use the `coder server create-admin` command to create a new admin user directly in the database.", - Flag: "disable-password-auth", - Default: false, - }, - Support: &codersdk.SupportConfig{ - Links: &codersdk.DeploymentConfigField[[]codersdk.LinkConfig]{ - Name: "Support links", - Usage: "Use custom support links", - Flag: "support-links", - Default: []codersdk.LinkConfig{}, - Enterprise: true, - }, - }, - } -} - -//nolint:revive -func Config(flagset *pflag.FlagSet, vip *viper.Viper) (*codersdk.DeploymentConfig, error) { - dc := newConfig() - flg, err := flagset.GetString(config.FlagName) - if err != nil { - return nil, xerrors.Errorf("get global config from flag: %w", err) - } - vip.SetEnvPrefix("coder") - - if flg != "" { - vip.SetConfigFile(flg + "/server.yaml") - err = vip.ReadInConfig() - if err != nil && !xerrors.Is(err, os.ErrNotExist) { - return dc, xerrors.Errorf("reading deployment config: %w", err) - } - } - - setConfig("", vip, &dc) - - return dc, nil -} - -func setConfig(prefix string, vip *viper.Viper, target interface{}) { - val := reflect.Indirect(reflect.ValueOf(target)) - typ := val.Type() - if typ.Kind() != reflect.Struct { - val = val.Elem() - typ = val.Type() - } - - // Ensure that we only bind env variables to proper fields, - // otherwise Viper will get confused if the parent struct is - // assigned a value. - if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") { - value := val.FieldByName("Value").Interface() - - env, ok := val.FieldByName("EnvOverride").Interface().(string) - if !ok { - panic("DeploymentConfigField[].EnvOverride must be a string") - } - if env == "" { - env = formatEnv(prefix) - } - - switch value.(type) { - case string: - vip.MustBindEnv(prefix, env) - val.FieldByName("Value").SetString(vip.GetString(prefix)) - case bool: - vip.MustBindEnv(prefix, env) - val.FieldByName("Value").SetBool(vip.GetBool(prefix)) - case int: - vip.MustBindEnv(prefix, env) - val.FieldByName("Value").SetInt(int64(vip.GetInt(prefix))) - case time.Duration: - vip.MustBindEnv(prefix, env) - val.FieldByName("Value").SetInt(int64(vip.GetDuration(prefix))) - case []string: - vip.MustBindEnv(prefix, env) - // As of October 21st, 2022 we supported delimiting a string - // with a comma, but Viper only supports with a space. This - // is a small hack around it! - rawSlice := reflect.ValueOf(vip.GetStringSlice(prefix)).Interface() - stringSlice, ok := rawSlice.([]string) - if !ok { - panic(fmt.Sprintf("string slice is of type %T", rawSlice)) - } - value := make([]string, 0, len(stringSlice)) - for _, entry := range stringSlice { - value = append(value, strings.Split(entry, ",")...) - } - val.FieldByName("Value").Set(reflect.ValueOf(value)) - case []codersdk.GitAuthConfig: - // Do not bind to CODER_GITAUTH, instead bind to CODER_GITAUTH_0_*, etc. - values := readSliceFromViper[codersdk.GitAuthConfig](vip, prefix, value) - val.FieldByName("Value").Set(reflect.ValueOf(values)) - case []codersdk.LinkConfig: - // Do not bind to CODER_SUPPORT_LINKS, instead bind to CODER_SUPPORT_LINKS_0_*, etc. - values := readSliceFromViper[codersdk.LinkConfig](vip, prefix, value) - val.FieldByName("Value").Set(reflect.ValueOf(values)) - default: - panic(fmt.Sprintf("unsupported type %T", value)) - } - return - } - - for i := 0; i < typ.NumField(); i++ { - fv := val.Field(i) - ft := fv.Type() - tag := typ.Field(i).Tag.Get("json") - var key string - if prefix == "" { - key = tag - } else { - key = fmt.Sprintf("%s.%s", prefix, tag) - } - switch ft.Kind() { - case reflect.Ptr: - setConfig(key, vip, fv.Interface()) - case reflect.Slice: - for j := 0; j < fv.Len(); j++ { - key := fmt.Sprintf("%s.%d", key, j) - setConfig(key, vip, fv.Index(j).Interface()) - } - default: - panic(fmt.Sprintf("unsupported type %T", ft)) - } - } -} - -// readSliceFromViper reads a typed mapping from the key provided. -// This enables environment variables like CODER_GITAUTH__CLIENT_ID. -func readSliceFromViper[T any](vip *viper.Viper, key string, value any) []T { - elementType := reflect.TypeOf(value).Elem() - returnValues := make([]T, 0) - for entry := 0; true; entry++ { - // Only create an instance when the entry exists in viper... - // otherwise we risk - var instance *reflect.Value - for i := 0; i < elementType.NumField(); i++ { - fve := elementType.Field(i) - prop := fve.Tag.Get("json") - // For fields that are omitted in JSON, we use a YAML tag. - if prop == "-" { - prop = fve.Tag.Get("yaml") - } - configKey := fmt.Sprintf("%s.%d.%s", key, entry, prop) - - // Ensure the env entry for this key is registered - // before checking value. - // - // We don't support DeploymentConfigField[].EnvOverride for array flags so - // this is fine to just use `formatEnv` here. - vip.MustBindEnv(configKey, formatEnv(configKey)) - - value := vip.Get(configKey) - if value == nil { - continue - } - if instance == nil { - newType := reflect.Indirect(reflect.New(elementType)) - instance = &newType - } - switch v := instance.Field(i).Type().String(); v { - case "[]string": - value = vip.GetStringSlice(configKey) - case "bool": - value = vip.GetBool(configKey) - default: - } - instance.Field(i).Set(reflect.ValueOf(value)) - } - if instance == nil { - break - } - value, ok := instance.Interface().(T) - if !ok { - continue - } - returnValues = append(returnValues, value) - } - return returnValues -} - -func NewViper() *viper.Viper { - dc := newConfig() - vip := viper.New() - vip.SetEnvPrefix("coder") - vip.SetEnvKeyReplacer(strings.NewReplacer("-", "_", ".", "_")) - - setViperDefaults("", vip, dc) - - return vip -} - -func setViperDefaults(prefix string, vip *viper.Viper, target interface{}) { - val := reflect.ValueOf(target).Elem() - val = reflect.Indirect(val) - typ := val.Type() - if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") { - value := val.FieldByName("Default").Interface() - vip.SetDefault(prefix, value) - return - } - - for i := 0; i < typ.NumField(); i++ { - fv := val.Field(i) - ft := fv.Type() - tag := typ.Field(i).Tag.Get("json") - var key string - if prefix == "" { - key = tag - } else { - key = fmt.Sprintf("%s.%s", prefix, tag) - } - switch ft.Kind() { - case reflect.Ptr: - setViperDefaults(key, vip, fv.Interface()) - case reflect.Slice: - // we currently don't support default values on structured slices - continue - default: - panic(fmt.Sprintf("unsupported type %T", ft)) - } - } -} - -//nolint:revive -func AttachFlags(flagset *pflag.FlagSet, vip *viper.Viper, enterprise bool) { - setFlags("", flagset, vip, newConfig(), enterprise) -} - -//nolint:revive -func setFlags(prefix string, flagset *pflag.FlagSet, vip *viper.Viper, target interface{}, enterprise bool) { - val := reflect.Indirect(reflect.ValueOf(target)) - typ := val.Type() - if strings.HasPrefix(typ.Name(), "DeploymentConfigField[") { - isEnt := val.FieldByName("Enterprise").Bool() - if enterprise != isEnt { - return - } - flg := val.FieldByName("Flag").String() - if flg == "" { - return - } - - env, ok := val.FieldByName("EnvOverride").Interface().(string) - if !ok { - panic("DeploymentConfigField[].EnvOverride must be a string") - } - if env == "" { - env = formatEnv(prefix) - } - - usage := val.FieldByName("Usage").String() - usage = fmt.Sprintf("%s\n%s", usage, cliui.Styles.Placeholder.Render("Consumes $"+env)) - shorthand := val.FieldByName("Shorthand").String() - hidden := val.FieldByName("Hidden").Bool() - value := val.FieldByName("Default").Interface() - - // Allow currently set environment variables - // to override default values in help output. - vip.MustBindEnv(prefix, env) - - switch value.(type) { - case string: - _ = flagset.StringP(flg, shorthand, vip.GetString(prefix), usage) - case bool: - _ = flagset.BoolP(flg, shorthand, vip.GetBool(prefix), usage) - case int: - _ = flagset.IntP(flg, shorthand, vip.GetInt(prefix), usage) - case time.Duration: - _ = flagset.DurationP(flg, shorthand, vip.GetDuration(prefix), usage) - case []string: - _ = flagset.StringSliceP(flg, shorthand, vip.GetStringSlice(prefix), usage) - case []codersdk.LinkConfig: - // Ignore this one! - case []codersdk.GitAuthConfig: - // Ignore this one! - default: - panic(fmt.Sprintf("unsupported type %T", typ)) - } - - _ = vip.BindPFlag(prefix, flagset.Lookup(flg)) - if hidden { - _ = flagset.MarkHidden(flg) - } - - return - } - - for i := 0; i < typ.NumField(); i++ { - fv := val.Field(i) - ft := fv.Type() - tag := typ.Field(i).Tag.Get("json") - var key string - if prefix == "" { - key = tag - } else { - key = fmt.Sprintf("%s.%s", prefix, tag) - } - switch ft.Kind() { - case reflect.Ptr: - setFlags(key, flagset, vip, fv.Interface(), enterprise) - case reflect.Slice: - for j := 0; j < fv.Len(); j++ { - key := fmt.Sprintf("%s.%d", key, j) - setFlags(key, flagset, vip, fv.Index(j).Interface(), enterprise) - } - default: - panic(fmt.Sprintf("unsupported type %T", ft)) - } - } -} - -func formatEnv(key string) string { - return "CODER_" + strings.ToUpper(strings.NewReplacer("-", "_", ".", "_").Replace(key)) -} - -func DefaultCacheDir() string { - defaultCacheDir, err := os.UserCacheDir() - if err != nil { - defaultCacheDir = os.TempDir() - } - if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" { - // For compatibility with systemd. - defaultCacheDir = dir - } - - return filepath.Join(defaultCacheDir, "coder") -} diff --git a/cli/deployment/config_test.go b/cli/deployment/config_test.go deleted file mode 100644 index 37dbc9a9e4..0000000000 --- a/cli/deployment/config_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package deployment_test - -import ( - "testing" - "time" - - "github.com/spf13/pflag" - "github.com/stretchr/testify/require" - - "github.com/coder/coder/cli/config" - "github.com/coder/coder/cli/deployment" - "github.com/coder/coder/codersdk" -) - -// nolint:paralleltest -func TestConfig(t *testing.T) { - viper := deployment.NewViper() - flagSet := pflag.NewFlagSet("", pflag.ContinueOnError) - flagSet.String(config.FlagName, "", "") - deployment.AttachFlags(flagSet, viper, true) - - for _, tc := range []struct { - Name string - Env map[string]string - Valid func(config *codersdk.DeploymentConfig) - }{{ - Name: "Deployment", - Env: map[string]string{ - "CODER_ADDRESS": "0.0.0.0:8443", - "CODER_ACCESS_URL": "https://dev.coder.com", - "CODER_PG_CONNECTION_URL": "some-url", - "CODER_PPROF_ADDRESS": "something", - "CODER_PPROF_ENABLE": "true", - "CODER_PROMETHEUS_ADDRESS": "hello-world", - "CODER_PROMETHEUS_ENABLE": "true", - "CODER_PROVISIONER_DAEMONS": "5", - "CODER_PROVISIONER_DAEMON_POLL_INTERVAL": "5s", - "CODER_PROVISIONER_DAEMON_POLL_JITTER": "1s", - "CODER_SECURE_AUTH_COOKIE": "true", - "CODER_SSH_KEYGEN_ALGORITHM": "potato", - "CODER_TELEMETRY": "false", - "CODER_TELEMETRY_TRACE": "false", - "CODER_WILDCARD_ACCESS_URL": "something-wildcard.com", - "CODER_UPDATE_CHECK": "false", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.Address.Value, "0.0.0.0:8443") - require.Equal(t, config.AccessURL.Value, "https://dev.coder.com") - require.Equal(t, config.PostgresURL.Value, "some-url") - require.Equal(t, config.Pprof.Address.Value, "something") - require.Equal(t, config.Pprof.Enable.Value, true) - require.Equal(t, config.Prometheus.Address.Value, "hello-world") - require.Equal(t, config.Prometheus.Enable.Value, true) - require.Equal(t, config.Provisioner.Daemons.Value, 5) - require.Equal(t, config.Provisioner.DaemonPollInterval.Value, 5*time.Second) - require.Equal(t, config.Provisioner.DaemonPollJitter.Value, 1*time.Second) - require.Equal(t, config.SecureAuthCookie.Value, true) - require.Equal(t, config.SSHKeygenAlgorithm.Value, "potato") - require.Equal(t, config.Telemetry.Enable.Value, false) - require.Equal(t, config.Telemetry.Trace.Value, false) - require.Equal(t, config.WildcardAccessURL.Value, "something-wildcard.com") - require.Equal(t, config.UpdateCheck.Value, false) - }, - }, { - Name: "DERP", - Env: map[string]string{ - "CODER_DERP_CONFIG_PATH": "/example/path", - "CODER_DERP_CONFIG_URL": "https://google.com", - "CODER_DERP_SERVER_ENABLE": "false", - "CODER_DERP_SERVER_REGION_CODE": "something", - "CODER_DERP_SERVER_REGION_ID": "123", - "CODER_DERP_SERVER_REGION_NAME": "Code-Land", - "CODER_DERP_SERVER_RELAY_URL": "1.1.1.1", - "CODER_DERP_SERVER_STUN_ADDRESSES": "google.org", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.DERP.Config.Path.Value, "/example/path") - require.Equal(t, config.DERP.Config.URL.Value, "https://google.com") - require.Equal(t, config.DERP.Server.Enable.Value, false) - require.Equal(t, config.DERP.Server.RegionCode.Value, "something") - require.Equal(t, config.DERP.Server.RegionID.Value, 123) - require.Equal(t, config.DERP.Server.RegionName.Value, "Code-Land") - require.Equal(t, config.DERP.Server.RelayURL.Value, "1.1.1.1") - require.Equal(t, config.DERP.Server.STUNAddresses.Value, []string{"google.org"}) - }, - }, { - Name: "Enterprise", - Env: map[string]string{ - "CODER_AUDIT_LOGGING": "false", - "CODER_BROWSER_ONLY": "true", - "CODER_SCIM_API_KEY": "some-key", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.AuditLogging.Value, false) - require.Equal(t, config.BrowserOnly.Value, true) - require.Equal(t, config.SCIMAPIKey.Value, "some-key") - }, - }, { - Name: "TLS", - Env: map[string]string{ - "CODER_TLS_CERT_FILE": "/etc/acme-sh/dev.coder.com,/etc/acme-sh/*.dev.coder.com", - "CODER_TLS_KEY_FILE": "/etc/acme-sh/dev.coder.com,/etc/acme-sh/*.dev.coder.com", - "CODER_TLS_CLIENT_AUTH": "/some/path", - "CODER_TLS_CLIENT_CA_FILE": "/some/path", - "CODER_TLS_ENABLE": "true", - "CODER_TLS_MIN_VERSION": "tls10", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Len(t, config.TLS.CertFiles.Value, 2) - require.Equal(t, config.TLS.CertFiles.Value[0], "/etc/acme-sh/dev.coder.com") - require.Equal(t, config.TLS.CertFiles.Value[1], "/etc/acme-sh/*.dev.coder.com") - - require.Len(t, config.TLS.KeyFiles.Value, 2) - require.Equal(t, config.TLS.KeyFiles.Value[0], "/etc/acme-sh/dev.coder.com") - require.Equal(t, config.TLS.KeyFiles.Value[1], "/etc/acme-sh/*.dev.coder.com") - - require.Equal(t, config.TLS.ClientAuth.Value, "/some/path") - require.Equal(t, config.TLS.ClientCAFile.Value, "/some/path") - require.Equal(t, config.TLS.Enable.Value, true) - require.Equal(t, config.TLS.MinVersion.Value, "tls10") - }, - }, { - Name: "Trace", - Env: map[string]string{ - "CODER_TRACE_ENABLE": "true", - "CODER_TRACE_HONEYCOMB_API_KEY": "my-honeycomb-key", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.Trace.Enable.Value, true) - require.Equal(t, config.Trace.HoneycombAPIKey.Value, "my-honeycomb-key") - }, - }, { - Name: "OIDC_Defaults", - Env: map[string]string{}, - Valid: func(config *codersdk.DeploymentConfig) { - require.Empty(t, config.OIDC.IssuerURL.Value) - require.Empty(t, config.OIDC.EmailDomain.Value) - require.Empty(t, config.OIDC.ClientID.Value) - require.Empty(t, config.OIDC.ClientSecret.Value) - require.True(t, config.OIDC.AllowSignups.Value) - require.ElementsMatch(t, config.OIDC.Scopes.Value, []string{"openid", "email", "profile"}) - require.False(t, config.OIDC.IgnoreEmailVerified.Value) - }, - }, { - Name: "OIDC", - Env: map[string]string{ - "CODER_OIDC_ISSUER_URL": "https://accounts.google.com", - "CODER_OIDC_EMAIL_DOMAIN": "coder.com", - "CODER_OIDC_CLIENT_ID": "client", - "CODER_OIDC_CLIENT_SECRET": "secret", - "CODER_OIDC_ALLOW_SIGNUPS": "false", - "CODER_OIDC_SCOPES": "something,here", - "CODER_OIDC_IGNORE_EMAIL_VERIFIED": "true", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.OIDC.IssuerURL.Value, "https://accounts.google.com") - require.Equal(t, config.OIDC.EmailDomain.Value, []string{"coder.com"}) - require.Equal(t, config.OIDC.ClientID.Value, "client") - require.Equal(t, config.OIDC.ClientSecret.Value, "secret") - require.False(t, config.OIDC.AllowSignups.Value) - require.Equal(t, config.OIDC.Scopes.Value, []string{"something", "here"}) - require.True(t, config.OIDC.IgnoreEmailVerified.Value) - }, - }, { - Name: "GitHub", - Env: map[string]string{ - "CODER_OAUTH2_GITHUB_CLIENT_ID": "client", - "CODER_OAUTH2_GITHUB_CLIENT_SECRET": "secret", - "CODER_OAUTH2_GITHUB_ALLOWED_ORGS": "coder", - "CODER_OAUTH2_GITHUB_ALLOWED_TEAMS": "coder", - "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS": "true", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.OAuth2.Github.ClientID.Value, "client") - require.Equal(t, config.OAuth2.Github.ClientSecret.Value, "secret") - require.Equal(t, []string{"coder"}, config.OAuth2.Github.AllowedOrgs.Value) - require.Equal(t, []string{"coder"}, config.OAuth2.Github.AllowedTeams.Value) - require.Equal(t, config.OAuth2.Github.AllowSignups.Value, true) - }, - }, { - Name: "GitAuth", - Env: map[string]string{ - "CODER_GITAUTH_0_ID": "hello", - "CODER_GITAUTH_0_TYPE": "github", - "CODER_GITAUTH_0_CLIENT_ID": "client", - "CODER_GITAUTH_0_CLIENT_SECRET": "secret", - "CODER_GITAUTH_0_AUTH_URL": "https://auth.com", - "CODER_GITAUTH_0_TOKEN_URL": "https://token.com", - "CODER_GITAUTH_0_VALIDATE_URL": "https://validate.com", - "CODER_GITAUTH_0_REGEX": "github.com", - "CODER_GITAUTH_0_SCOPES": "read write", - "CODER_GITAUTH_0_NO_REFRESH": "true", - - "CODER_GITAUTH_1_ID": "another", - "CODER_GITAUTH_1_TYPE": "gitlab", - "CODER_GITAUTH_1_CLIENT_ID": "client-2", - "CODER_GITAUTH_1_CLIENT_SECRET": "secret-2", - "CODER_GITAUTH_1_AUTH_URL": "https://auth-2.com", - "CODER_GITAUTH_1_TOKEN_URL": "https://token-2.com", - "CODER_GITAUTH_1_REGEX": "gitlab.com", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Len(t, config.GitAuth.Value, 2) - require.Equal(t, []codersdk.GitAuthConfig{{ - ID: "hello", - Type: "github", - ClientID: "client", - ClientSecret: "secret", - AuthURL: "https://auth.com", - TokenURL: "https://token.com", - ValidateURL: "https://validate.com", - Regex: "github.com", - Scopes: []string{"read", "write"}, - NoRefresh: true, - }, { - ID: "another", - Type: "gitlab", - ClientID: "client-2", - ClientSecret: "secret-2", - AuthURL: "https://auth-2.com", - TokenURL: "https://token-2.com", - Regex: "gitlab.com", - }}, config.GitAuth.Value) - }, - }, { - Name: "Support links", - Env: map[string]string{ - "CODER_SUPPORT_LINKS_0_NAME": "First link", - "CODER_SUPPORT_LINKS_0_TARGET": "http://target-link-1", - "CODER_SUPPORT_LINKS_0_ICON": "bug", - - "CODER_SUPPORT_LINKS_1_NAME": "Second link", - "CODER_SUPPORT_LINKS_1_TARGET": "http://target-link-2", - "CODER_SUPPORT_LINKS_1_ICON": "chat", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Len(t, config.Support.Links.Value, 2) - require.Equal(t, []codersdk.LinkConfig{{ - Name: "First link", - Target: "http://target-link-1", - Icon: "bug", - }, { - Name: "Second link", - Target: "http://target-link-2", - Icon: "chat", - }}, config.Support.Links.Value) - }, - }, { - Name: "Wrong env must not break default values", - Env: map[string]string{ - "CODER_PROMETHEUS_ENABLE": "true", - "CODER_PROMETHEUS": "true", // Wrong env name, must not break prom addr. - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Equal(t, config.Prometheus.Enable.Value, true) - require.Equal(t, config.Prometheus.Address.Value, config.Prometheus.Address.Default) - }, - }, { - Name: "Experiments - no features", - Env: map[string]string{ - "CODER_EXPERIMENTS": "", - }, - Valid: func(config *codersdk.DeploymentConfig) { - require.Empty(t, config.Experiments.Value) - }, - }, { - Name: "Experiments - multiple features", - Env: map[string]string{ - "CODER_EXPERIMENTS": "foo,bar", - }, - Valid: func(config *codersdk.DeploymentConfig) { - expected := []string{"foo", "bar"} - require.ElementsMatch(t, expected, config.Experiments.Value) - }, - }} { - tc := tc - t.Run(tc.Name, func(t *testing.T) { - t.Helper() - for key, value := range tc.Env { - t.Setenv(key, value) - } - config, err := deployment.Config(flagSet, viper) - require.NoError(t, err) - tc.Valid(config) - }) - } -} diff --git a/cli/resetpassword_test.go b/cli/resetpassword_test.go index 8bcf4777df..3bf45c271b 100644 --- a/cli/resetpassword_test.go +++ b/cli/resetpassword_test.go @@ -32,7 +32,6 @@ func TestResetPassword(t *testing.T) { const newPassword = "MyNewPassword!" // start postgres and coder server processes - connectionURL, closeFunc, err := postgres.Open() require.NoError(t, err) defer closeFunc() diff --git a/cli/root.go b/cli/root.go index 4fc7958772..f3db7af279 100644 --- a/cli/root.go +++ b/cli/root.go @@ -22,7 +22,6 @@ import ( "cdr.dev/slog" "github.com/charmbracelet/lipgloss" - "github.com/kirsle/configdir" "github.com/mattn/go-isatty" "github.com/spf13/cobra" @@ -30,7 +29,6 @@ import ( "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" "github.com/coder/coder/cli/config" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/gitauth" "github.com/coder/coder/codersdk" @@ -110,7 +108,7 @@ func Core() []*cobra.Command { } func AGPL() []*cobra.Command { - all := append(Core(), Server(deployment.NewViper(), func(_ context.Context, o *coderd.Options) (*coderd.API, io.Closer, error) { + all := append(Core(), Server(func(_ context.Context, o *coderd.Options) (*coderd.API, io.Closer, error) { api := coderd.New(o) return api, api, nil })) @@ -160,7 +158,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command { cmd.AddCommand(subcommands...) fixUnknownSubcommandError(cmd.Commands()) - cmd.SetUsageTemplate(usageTemplate()) + cmd.SetUsageTemplate(usageTemplateCobra()) cliflag.String(cmd.PersistentFlags(), varURL, "", envURL, "", "URL to a deployment.") cliflag.Bool(cmd.PersistentFlags(), varNoVersionCheck, "", envNoVersionCheck, false, "Suppress warning when client and server versions do not match.") @@ -170,7 +168,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command { _ = cmd.PersistentFlags().MarkHidden(varAgentToken) cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "URL for an agent to access your deployment.") _ = cmd.PersistentFlags().MarkHidden(varAgentURL) - cliflag.String(cmd.PersistentFlags(), config.FlagName, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Path to the global `coder` config directory.") + cliflag.String(cmd.PersistentFlags(), config.FlagName, "", "CODER_CONFIG_DIR", config.DefaultDir(), "Path to the global `coder` config directory.") cliflag.StringArray(cmd.PersistentFlags(), varHeader, "", "CODER_HEADER", []string{}, "HTTP headers added to all requests. Provide as \"Key=Value\"") cmd.PersistentFlags().Bool(varForceTty, false, "Force the `coder` command to run as if connected to a TTY.") _ = cmd.PersistentFlags().MarkHidden(varForceTty) @@ -481,7 +479,10 @@ func isWorkspaceCommand(cmd *cobra.Command) bool { return ws } -func usageTemplate() string { +// We will eventually replace this with the clibase template describedc +// in usage.go. We don't want to continue working around +// Cobra's feature-set. +func usageTemplateCobra() string { // usageHeader is defined in init(). return `{{usageHeader "Usage:"}} {{- if .Runnable}} diff --git a/cli/root_test.go b/cli/root_test.go index 525b305e1b..0f76782707 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -53,13 +53,14 @@ func TestCommandHelp(t *testing.T) { name: "coder --help", cmd: []string{"--help"}, }, - { - name: "coder server --help", - cmd: []string{"server", "--help"}, - env: map[string]string{ - "CODER_CACHE_DIRECTORY": "~/.cache/coder", - }, - }, + // Re-enable after clibase migrations. + // { + // name: "coder server --help", + // cmd: []string{"server", "--help"}, + // env: map[string]string{ + // "CODER_CACHE_DIRECTORY": "~/.cache/coder", + // }, + // }, { name: "coder agent --help", cmd: []string{"agent", "--help"}, @@ -177,6 +178,10 @@ func extractVisibleCommandPaths(cmdPath []string, cmds []*cobra.Command) [][]str if c.Hidden { continue } + // TODO: re-enable after clibase migration. + if c.Name() == "server" { + continue + } cmdPath := append(cmdPath, c.Name()) cmdPaths = append(cmdPaths, cmdPath) cmdPaths = append(cmdPaths, extractVisibleCommandPaths(cmdPath, c.Commands())...) diff --git a/cli/server.go b/cli/server.go index c46938e71d..c598112d9d 100644 --- a/cli/server.go +++ b/cli/server.go @@ -12,6 +12,7 @@ import ( "database/sql" "encoding/hex" "errors" + "flag" "fmt" "io" "log" @@ -25,6 +26,7 @@ import ( "os/user" "path/filepath" "regexp" + "sort" "strconv" "strings" "sync" @@ -40,7 +42,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/afero" "github.com/spf13/cobra" - "github.com/spf13/viper" "go.opentelemetry.io/otel/trace" "golang.org/x/mod/semver" "golang.org/x/oauth2" @@ -49,6 +50,7 @@ import ( "golang.org/x/xerrors" "google.golang.org/api/idtoken" "google.golang.org/api/option" + "gopkg.in/yaml.v3" "tailscale.com/tailcfg" "cdr.dev/slog" @@ -56,9 +58,9 @@ import ( "cdr.dev/slog/sloggers/slogjson" "cdr.dev/slog/sloggers/slogstackdriver" "github.com/coder/coder/buildinfo" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/cli/cliui" "github.com/coder/coder/cli/config" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/autobuild/executor" "github.com/coder/coder/coderd/database" @@ -84,48 +86,221 @@ import ( "github.com/coder/coder/tailnet" ) +// ReadGitAuthProvidersFromEnv is provided for compatibility purposes with the +// viper CLI. +// DEPRECATED +func ReadGitAuthProvidersFromEnv(environ []string) ([]codersdk.GitAuthConfig, error) { + // The index numbers must be in-order. + sort.Strings(environ) + + var providers []codersdk.GitAuthConfig + for _, v := range clibase.EnvsWithPrefix(environ, envPrefix+"GITAUTH_") { + tokens := strings.SplitN(v.Name, "_", 2) + if len(tokens) != 2 { + return nil, xerrors.Errorf("invalid env var: %s", v.Name) + } + + providerNum, err := strconv.Atoi(tokens[0]) + if err != nil { + return nil, xerrors.Errorf("parse number: %s", v.Name) + } + + var provider codersdk.GitAuthConfig + switch { + case len(providers) < providerNum: + return nil, xerrors.Errorf( + "provider num %v skipped: %s", + len(providers), + v.Name, + ) + case len(providers) == providerNum: + // At the next next provider. + providers = append(providers, provider) + case len(providers) == providerNum+1: + // At the current provider. + provider = providers[providerNum] + } + + key := tokens[1] + switch key { + case "ID": + provider.ID = v.Value + case "TYPE": + provider.Type = v.Value + case "CLIENT_ID": + provider.ClientID = v.Value + case "CLIENT_SECRET": + provider.ClientSecret = v.Value + case "AUTH_URL": + provider.AuthURL = v.Value + case "TOKEN_URL": + provider.TokenURL = v.Value + case "VALIDATE_URL": + provider.ValidateURL = v.Value + case "REGEX": + provider.Regex = v.Value + case "NO_REFRESH": + b, err := strconv.ParseBool(key) + if err != nil { + return nil, xerrors.Errorf("parse bool: %s", v.Value) + } + provider.NoRefresh = b + case "SCOPES": + provider.Scopes = strings.Split(v.Value, " ") + } + providers[providerNum] = provider + } + return providers, nil +} + // nolint:gocyclo -func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { +func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { root := &cobra.Command{ - Use: "server", - Short: "Start a Coder server", + Use: "server", + Short: "Start a Coder server", + DisableFlagParsing: true, RunE: func(cmd *cobra.Command, args []string) error { // Main command context for managing cancellation of running // services. ctx, cancel := context.WithCancel(cmd.Context()) defer cancel() + cfg := &codersdk.DeploymentValues{} + cliOpts := cfg.Options() + var configDir clibase.String + // This is a hack to get around the fact that the Cobra-defined + // flags are not available. + cliOpts.Add(clibase.Option{ + Name: "Global Config", + Flag: config.FlagName, + Description: "Global Config is ignored in server mode.", + Hidden: true, + Default: config.DefaultDir(), + Value: &configDir, + }) + + err := cliOpts.SetDefaults() + if err != nil { + return xerrors.Errorf("set defaults: %w", err) + } + + err = cliOpts.ParseEnv(envPrefix, os.Environ()) + if err != nil { + return xerrors.Errorf("parse env: %w", err) + } + + flagSet := cliOpts.FlagSet() + // These parents and children will be moved once we convert the + // rest of the `cli` package to clibase. + flagSet.Usage = usageFn(cmd.ErrOrStderr(), &clibase.Cmd{ + Parent: &clibase.Cmd{ + Use: "coder", + }, + Children: []*clibase.Cmd{ + { + Use: "postgres-builtin-url", + Short: "Output the connection URL for the built-in PostgreSQL deployment.", + }, + { + Use: "postgres-builtin-serve", + Short: "Run the built-in PostgreSQL deployment.", + }, + }, + Use: "server [flags]", + Short: "Start a Coder server", + Long: ` +The server provides the Coder dashboard, API, and provisioners. +If no options are provided, the server will start with a built-in postgres +and an access URL provided by Coder's cloud service. + +Use the following command to print the built-in postgres URL: + $ coder server postgres-builtin-url + +Use the following command to manually run the built-in postgres: + $ coder server postgres-builtin-serve + +Options may be provided via environment variables prefixed with "CODER_", +flags, and YAML configuration. The precedence is as follows: + 1. Defaults + 2. YAML configuration + 3. Environment variables + 4. Flags + `, + Options: cliOpts, + }) + err = flagSet.Parse(args) + if err != nil { + return xerrors.Errorf("parse flags: %w", err) + } + + if cfg.WriteConfig { + // TODO: this should output to a file. + n, err := cliOpts.ToYAML() + if err != nil { + return xerrors.Errorf("generate yaml: %w", err) + } + enc := yaml.NewEncoder(cmd.ErrOrStderr()) + err = enc.Encode(n) + if err != nil { + return xerrors.Errorf("encode yaml: %w", err) + } + err = enc.Close() + if err != nil { + return xerrors.Errorf("close yaml encoder: %w", err) + } + return nil + } + + // Print deprecation warnings. + for _, opt := range cliOpts { + if opt.UseInstead == nil { + continue + } + + warnStr := opt.Name + " is deprecated, please use " + for i, use := range opt.UseInstead { + warnStr += use.Name + " " + if i != len(opt.UseInstead)-1 { + warnStr += "and " + } + } + warnStr += "instead.\n" + + cmd.PrintErr( + cliui.Styles.Warn.Render("WARN: ") + warnStr, + ) + } + go dumpHandler(ctx) - cfg, err := deployment.Config(cmd.Flags(), vip) - if err != nil { - return xerrors.Errorf("getting deployment config: %w", err) - } - // Validate bind addresses. - if cfg.Address.Value != "" { - cmd.PrintErr(cliui.Styles.Warn.Render("WARN:") + " --address and -a are deprecated, please use --http-address and --tls-address instead") - if cfg.TLS.Enable.Value { - cfg.HTTPAddress.Value = "" - cfg.TLS.Address.Value = cfg.Address.Value + if cfg.Address.String() != "" { + if cfg.TLS.Enable { + cfg.HTTPAddress = "" + cfg.TLS.Address = cfg.Address } else { - cfg.HTTPAddress.Value = cfg.Address.Value - cfg.TLS.Address.Value = "" + _ = cfg.HTTPAddress.Set(cfg.Address.String()) + cfg.TLS.Address.Host = "" + cfg.TLS.Address.Port = "" } } - if cfg.TLS.Enable.Value && cfg.TLS.Address.Value == "" { + if cfg.TLS.Enable && cfg.TLS.Address.String() == "" { return xerrors.Errorf("TLS address must be set if TLS is enabled") } - if !cfg.TLS.Enable.Value && cfg.HTTPAddress.Value == "" { + if !cfg.TLS.Enable && cfg.HTTPAddress.String() == "" { return xerrors.Errorf("TLS is disabled. Enable with --tls-enable or specify a HTTP address") } + if cfg.AccessURL.String() != "" && cfg.AccessURL.Scheme == "" { + return xerrors.Errorf("access-url must include a scheme (e.g. 'http://' or 'https://)") + } + // Disable rate limits if the `--dangerous-disable-rate-limits` flag // was specified. loginRateLimit := 60 filesRateLimit := 12 - if cfg.RateLimit.DisableAll.Value { - cfg.RateLimit.API.Value = -1 + if cfg.RateLimit.DisableAll { + cfg.RateLimit.API = -1 loginRateLimit = -1 filesRateLimit = -1 } @@ -137,6 +312,10 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co } defer logCloser() + // This line is helpful in tests. + logger.Debug(ctx, "started debug logging") + logger.Sync() + // Register signals early on so that graceful shutdown can't // be interrupted by additional signals. Note that we avoid // shadowing cancel() (from above) here because notifyStop() @@ -151,7 +330,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co defer notifyStop() // Ensure we have a unique cache directory for this process. - cacheDir := filepath.Join(cfg.CacheDirectory.Value, uuid.NewString()) + cacheDir := filepath.Join(cfg.CacheDir.String(), uuid.NewString()) err = os.MkdirAll(cacheDir, 0o700) if err != nil { return xerrors.Errorf("create cache directory: %w", err) @@ -170,18 +349,18 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // Coder tracing should be disabled if telemetry is disabled unless // --telemetry-trace was explicitly provided. - shouldCoderTrace := cfg.Telemetry.Enable.Value && !isTest() + shouldCoderTrace := cfg.Telemetry.Enable.Value() && !isTest() // Only override if telemetryTraceEnable was specifically set. // By default we want it to be controlled by telemetryEnable. if cmd.Flags().Changed("telemetry-trace") { - shouldCoderTrace = cfg.Telemetry.Trace.Value + shouldCoderTrace = cfg.Telemetry.Trace.Value() } - if cfg.Trace.Enable.Value || shouldCoderTrace || cfg.Trace.HoneycombAPIKey.Value != "" { + if cfg.Trace.Enable.Value() || shouldCoderTrace || cfg.Trace.HoneycombAPIKey != "" { sdkTracerProvider, closeTracing, err := tracing.TracerProvider(ctx, "coderd", tracing.TracerOpts{ - Default: cfg.Trace.Enable.Value, + Default: cfg.Trace.Enable.Value(), Coder: shouldCoderTrace, - Honeycomb: cfg.Trace.HoneycombAPIKey.Value, + Honeycomb: cfg.Trace.HoneycombAPIKey.String(), }) if err != nil { logger.Warn(ctx, "start telemetry exporter", slog.Error(err)) @@ -202,13 +381,18 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co } } - config := createConfig(cmd) + config := config.Root(configDir) builtinPostgres := false // Only use built-in if PostgreSQL URL isn't specified! - if !cfg.InMemoryDatabase.Value && cfg.PostgresURL.Value == "" { + if !cfg.InMemoryDatabase && cfg.PostgresURL == "" { var closeFunc func() error cmd.Printf("Using built-in PostgreSQL (%s)\n", config.PostgresPath()) - cfg.PostgresURL.Value, closeFunc, err = startBuiltinPostgres(ctx, config, logger) + pgURL, closeFunc, err := startBuiltinPostgres(ctx, config, logger) + if err != nil { + return err + } + + err = cfg.PostgresURL.Set(pgURL) if err != nil { return err } @@ -228,10 +412,10 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co httpListener net.Listener httpURL *url.URL ) - if cfg.HTTPAddress.Value != "" { - httpListener, err = net.Listen("tcp", cfg.HTTPAddress.Value) + if cfg.HTTPAddress.String() != "" { + httpListener, err = net.Listen("tcp", cfg.HTTPAddress.String()) if err != nil { - return xerrors.Errorf("listen %q: %w", cfg.HTTPAddress.Value, err) + return xerrors.Errorf("listen %q: %w", cfg.HTTPAddress.String(), err) } defer httpListener.Close() @@ -240,7 +424,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // httpListener.Addr().String() likes to return it as an ipv6 // address (i.e. [::]:x). If the input ip is 0.0.0.0, try to // coerce the output back to ipv4 to make it less confusing. - if strings.Contains(cfg.HTTPAddress.Value, "0.0.0.0") { + if strings.Contains(cfg.HTTPAddress.String(), "0.0.0.0") { listenAddrStr = strings.ReplaceAll(listenAddrStr, "[::]", "0.0.0.0") } @@ -267,8 +451,8 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co httpsListener net.Listener httpsURL *url.URL ) - if cfg.TLS.Enable.Value { - if cfg.TLS.Address.Value == "" { + if cfg.TLS.Enable { + if cfg.TLS.Address.String() == "" { return xerrors.New("tls address must be set if tls is enabled") } @@ -276,22 +460,22 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // It made more sense to have the redirect be opt-in. if os.Getenv("CODER_TLS_REDIRECT_HTTP") == "true" || cmd.Flags().Changed("tls-redirect-http-to-https") { cmd.PrintErr(cliui.Styles.Warn.Render("WARN:") + " --tls-redirect-http-to-https is deprecated, please use --redirect-to-access-url instead\n") - cfg.RedirectToAccessURL.Value = cfg.TLS.RedirectHTTP.Value + cfg.RedirectToAccessURL = cfg.TLS.RedirectHTTP } tlsConfig, err = configureTLS( - cfg.TLS.MinVersion.Value, - cfg.TLS.ClientAuth.Value, - cfg.TLS.CertFiles.Value, - cfg.TLS.KeyFiles.Value, - cfg.TLS.ClientCAFile.Value, + cfg.TLS.MinVersion.String(), + cfg.TLS.ClientAuth.String(), + cfg.TLS.CertFiles, + cfg.TLS.KeyFiles, + cfg.TLS.ClientCAFile.String(), ) if err != nil { return xerrors.Errorf("configure tls: %w", err) } - httpsListenerInner, err := net.Listen("tcp", cfg.TLS.Address.Value) + httpsListenerInner, err := net.Listen("tcp", cfg.TLS.Address.String()) if err != nil { - return xerrors.Errorf("listen %q: %w", cfg.TLS.Address.Value, err) + return xerrors.Errorf("listen %q: %w", cfg.TLS.Address.String(), err) } defer httpsListenerInner.Close() @@ -304,7 +488,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // an ipv6 address (i.e. [::]:x). If the input ip is 0.0.0.0, // try to coerce the output back to ipv4 to make it less // confusing. - if strings.Contains(cfg.HTTPAddress.Value, "0.0.0.0") { + if strings.Contains(cfg.HTTPAddress.String(), "0.0.0.0") { listenAddrStr = strings.ReplaceAll(listenAddrStr, "[::]", "0.0.0.0") } @@ -340,9 +524,9 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co ctx, httpClient, err := configureHTTPClient( ctx, - cfg.TLS.ClientCertFile.Value, - cfg.TLS.ClientKeyFile.Value, - cfg.TLS.ClientCAFile.Value, + cfg.TLS.ClientCertFile.String(), + cfg.TLS.ClientKeyFile.String(), + cfg.TLS.ClientCAFile.String(), ) if err != nil { return xerrors.Errorf("configure http client: %w", err) @@ -357,53 +541,60 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // If the access URL is empty, we attempt to run a reverse-proxy // tunnel to make the initial setup really simple. - if cfg.AccessURL.Value == "" { + if cfg.AccessURL.String() == "" { cmd.Printf("Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL\n") tunnel, tunnelErr, err = devtunnel.New(ctxTunnel, logger.Named("devtunnel")) if err != nil { return xerrors.Errorf("create tunnel: %w", err) } - cfg.AccessURL.Value = tunnel.URL + err = cfg.AccessURL.Set(tunnel.URL) + if err != nil { + return xerrors.Errorf("set access url: %w", err) + } - if cfg.WildcardAccessURL.Value == "" { + if cfg.WildcardAccessURL.String() == "" { u, err := parseURL(tunnel.URL) if err != nil { return xerrors.Errorf("parse tunnel url: %w", err) } // Suffixed wildcard access URL. - cfg.WildcardAccessURL.Value = fmt.Sprintf("*--%s", u.Hostname()) + u, err = url.Parse(fmt.Sprintf("*--%s", u.Hostname())) + if err != nil { + return xerrors.Errorf("parse wildcard url: %w", err) + } + cfg.WildcardAccessURL = clibase.URL(*u) } } - accessURLParsed, err := parseURL(cfg.AccessURL.Value) - if err != nil { - return xerrors.Errorf("parse URL: %w", err) - } - accessURLPortRaw := accessURLParsed.Port() + _, accessURLPortRaw, _ := net.SplitHostPort(cfg.AccessURL.Host) if accessURLPortRaw == "" { accessURLPortRaw = "80" - if accessURLParsed.Scheme == "https" { + if cfg.AccessURL.Scheme == "https" { accessURLPortRaw = "443" } } + accessURLPort, err := strconv.Atoi(accessURLPortRaw) if err != nil { return xerrors.Errorf("parse access URL port: %w", err) } // Warn the user if the access URL appears to be a loopback address. - isLocal, err := isLocalURL(ctx, accessURLParsed) + isLocal, err := isLocalURL(ctx, cfg.AccessURL.Value()) if isLocal || err != nil { reason := "could not be resolved" if isLocal { reason = "isn't externally reachable" } - cmd.Printf("%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL by not specifying an access URL.\n", cliui.Styles.Warn.Render("Warning:"), cliui.Styles.Field.Render(accessURLParsed.String()), reason) + cmd.Printf( + "%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL by not specifying an access URL.\n", + cliui.Styles.Warn.Render("Warning:"), cliui.Styles.Field.Render(cfg.AccessURL.String()), reason, + ) } // A newline is added before for visibility in terminal output. - cmd.Printf("\nView the Web UI: %s\n", accessURLParsed.String()) + cmd.Printf("\nView the Web UI: %s\n", cfg.AccessURL.String()) // Used for zero-trust instance identity with Google Cloud. googleTokenValidator, err := idtoken.NewValidator(ctx, option.WithoutAuthentication()) @@ -411,34 +602,37 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co return err } - sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(cfg.SSHKeygenAlgorithm.Value) + sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(cfg.SSHKeygenAlgorithm.String()) if err != nil { - return xerrors.Errorf("parse ssh keygen algorithm %s: %w", cfg.SSHKeygenAlgorithm.Value, err) + return xerrors.Errorf("parse ssh keygen algorithm %s: %w", cfg.SSHKeygenAlgorithm, err) } defaultRegion := &tailcfg.DERPRegion{ EmbeddedRelay: true, - RegionID: cfg.DERP.Server.RegionID.Value, - RegionCode: cfg.DERP.Server.RegionCode.Value, - RegionName: cfg.DERP.Server.RegionName.Value, + RegionID: int(cfg.DERP.Server.RegionID.Value()), + RegionCode: cfg.DERP.Server.RegionCode.String(), + RegionName: cfg.DERP.Server.RegionName.String(), Nodes: []*tailcfg.DERPNode{{ - Name: fmt.Sprintf("%db", cfg.DERP.Server.RegionID.Value), - RegionID: cfg.DERP.Server.RegionID.Value, - HostName: accessURLParsed.Hostname(), + Name: fmt.Sprintf("%db", cfg.DERP.Server.RegionID), + RegionID: int(cfg.DERP.Server.RegionID.Value()), + HostName: cfg.AccessURL.Host, DERPPort: accessURLPort, STUNPort: -1, - ForceHTTP: accessURLParsed.Scheme == "http", + ForceHTTP: cfg.AccessURL.Scheme == "http", }}, } - if !cfg.DERP.Server.Enable.Value { + if !cfg.DERP.Server.Enable { defaultRegion = nil } - derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, cfg.DERP.Server.STUNAddresses.Value, cfg.DERP.Config.URL.Value, cfg.DERP.Config.Path.Value) + derpMap, err := tailnet.NewDERPMap( + ctx, defaultRegion, cfg.DERP.Server.STUNAddresses, + cfg.DERP.Config.URL.String(), cfg.DERP.Config.Path.String(), + ) if err != nil { return xerrors.Errorf("create derp map: %w", err) } - appHostname := strings.TrimSpace(cfg.WildcardAccessURL.Value) + appHostname := cfg.WildcardAccessURL.String() var appHostnameRegex *regexp.Regexp if appHostname != "" { appHostnameRegex, err = httpapi.CompileHostnamePattern(appHostname) @@ -447,18 +641,32 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co } } - gitAuthConfigs, err := gitauth.ConvertConfig(cfg.GitAuth.Value, accessURLParsed) + gitAuthEnv, err := ReadGitAuthProvidersFromEnv(os.Environ()) if err != nil { - return xerrors.Errorf("parse git auth config: %w", err) + return xerrors.Errorf("read git auth providers from env: %w", err) } - realIPConfig, err := httpmw.ParseRealIPConfig(cfg.ProxyTrustedHeaders.Value, cfg.ProxyTrustedOrigins.Value) + gitAuthConfigs, err := gitauth.ConvertConfig( + append(cfg.GitAuthProviders.Value, gitAuthEnv...), + cfg.AccessURL.Value(), + ) + if err != nil { + return xerrors.Errorf("convert git auth config: %w", err) + } + for _, c := range gitAuthConfigs { + logger.Debug( + ctx, "loaded git auth config", + slog.F("id", c.ID), + ) + } + + realIPConfig, err := httpmw.ParseRealIPConfig(cfg.ProxyTrustedHeaders, cfg.ProxyTrustedOrigins) if err != nil { return xerrors.Errorf("parse real ip config: %w", err) } options := &coderd.Options{ - AccessURL: accessURLParsed, + AccessURL: cfg.AccessURL.Value(), AppHostname: appHostname, AppHostnameRegex: appHostnameRegex, Logger: logger.Named("coderd"), @@ -469,15 +677,15 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co GoogleTokenValidator: googleTokenValidator, GitAuthConfigs: gitAuthConfigs, RealIPConfig: realIPConfig, - SecureAuthCookie: cfg.SecureAuthCookie.Value, + SecureAuthCookie: cfg.SecureAuthCookie.Value(), SSHKeygenAlgorithm: sshKeygenAlgorithm, TracerProvider: tracerProvider, Telemetry: telemetry.NewNoop(), - MetricsCacheRefreshInterval: cfg.MetricsCacheRefreshInterval.Value, - AgentStatsRefreshInterval: cfg.AgentStatRefreshInterval.Value, - DeploymentConfig: cfg, + MetricsCacheRefreshInterval: cfg.MetricsCacheRefreshInterval.Value(), + AgentStatsRefreshInterval: cfg.AgentStatRefreshInterval.Value(), + DeploymentValues: cfg, PrometheusRegistry: prometheus.NewRegistry(), - APIRateLimit: cfg.RateLimit.API.Value, + APIRateLimit: int(cfg.RateLimit.API.Value()), LoginRateLimit: loginRateLimit, FilesRateLimit: filesRateLimit, HTTPClient: httpClient, @@ -486,14 +694,16 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co options.TLSCertificates = tlsConfig.Certificates } - if cfg.StrictTransportSecurity.Value > 0 { - options.StrictTransportSecurityCfg, err = httpmw.HSTSConfigOptions(cfg.StrictTransportSecurity.Value, cfg.StrictTransportSecurityOptions.Value) + if cfg.StrictTransportSecurity > 0 { + options.StrictTransportSecurityCfg, err = httpmw.HSTSConfigOptions( + int(cfg.StrictTransportSecurity.Value()), cfg.StrictTransportSecurityOptions, + ) if err != nil { - return xerrors.Errorf("coderd: setting hsts header failed (options: %v): %w", cfg.StrictTransportSecurityOptions.Value, err) + return xerrors.Errorf("coderd: setting hsts header failed (options: %v): %w", cfg.StrictTransportSecurityOptions, err) } } - if cfg.UpdateCheck.Value { + if cfg.UpdateCheck { options.UpdateCheckOptions = &updatecheck.Options{ // Avoid spamming GitHub API checking for updates. Interval: 24 * time.Hour, @@ -512,67 +722,69 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co } } - if cfg.OAuth2.Github.ClientSecret.Value != "" { - options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, - cfg.OAuth2.Github.ClientID.Value, - cfg.OAuth2.Github.ClientSecret.Value, - cfg.OAuth2.Github.AllowSignups.Value, - cfg.OAuth2.Github.AllowEveryone.Value, - cfg.OAuth2.Github.AllowedOrgs.Value, - cfg.OAuth2.Github.AllowedTeams.Value, - cfg.OAuth2.Github.EnterpriseBaseURL.Value, + if cfg.OAuth2.Github.ClientSecret != "" { + options.GithubOAuth2Config, err = configureGithubOAuth2(cfg.AccessURL.Value(), + cfg.OAuth2.Github.ClientID.String(), + cfg.OAuth2.Github.ClientSecret.String(), + cfg.OAuth2.Github.AllowSignups.Value(), + cfg.OAuth2.Github.AllowEveryone.Value(), + cfg.OAuth2.Github.AllowedOrgs, + cfg.OAuth2.Github.AllowedTeams, + cfg.OAuth2.Github.EnterpriseBaseURL.String(), ) if err != nil { return xerrors.Errorf("configure github oauth2: %w", err) } } - if cfg.OIDC.ClientSecret.Value != "" { - if cfg.OIDC.ClientID.Value == "" { + if cfg.OIDC.ClientSecret != "" { + if cfg.OIDC.ClientID == "" { return xerrors.Errorf("OIDC client ID be set!") } - if cfg.OIDC.IssuerURL.Value == "" { + if cfg.OIDC.IssuerURL == "" { return xerrors.Errorf("OIDC issuer URL must be set!") } - if cfg.OIDC.IgnoreEmailVerified.Value { + if cfg.OIDC.IgnoreEmailVerified { logger.Warn(ctx, "coder will not check email_verified for OIDC logins") } - oidcProvider, err := oidc.NewProvider(ctx, cfg.OIDC.IssuerURL.Value) + oidcProvider, err := oidc.NewProvider( + ctx, cfg.OIDC.IssuerURL.String(), + ) if err != nil { return xerrors.Errorf("configure oidc provider: %w", err) } - redirectURL, err := accessURLParsed.Parse("/api/v2/users/oidc/callback") + redirectURL, err := cfg.AccessURL.Value().Parse("/api/v2/users/oidc/callback") if err != nil { return xerrors.Errorf("parse oidc oauth callback url: %w", err) } options.OIDCConfig = &coderd.OIDCConfig{ OAuth2Config: &oauth2.Config{ - ClientID: cfg.OIDC.ClientID.Value, - ClientSecret: cfg.OIDC.ClientSecret.Value, + ClientID: cfg.OIDC.ClientID.String(), + ClientSecret: cfg.OIDC.ClientSecret.String(), RedirectURL: redirectURL.String(), Endpoint: oidcProvider.Endpoint(), - Scopes: cfg.OIDC.Scopes.Value, + Scopes: cfg.OIDC.Scopes, }, Provider: oidcProvider, Verifier: oidcProvider.Verifier(&oidc.Config{ - ClientID: cfg.OIDC.ClientID.Value, + ClientID: cfg.OIDC.ClientID.String(), }), - EmailDomain: cfg.OIDC.EmailDomain.Value, - AllowSignups: cfg.OIDC.AllowSignups.Value, - UsernameField: cfg.OIDC.UsernameField.Value, - SignInText: cfg.OIDC.SignInText.Value, - IconURL: cfg.OIDC.IconURL.Value, - IgnoreEmailVerified: cfg.OIDC.IgnoreEmailVerified.Value, + EmailDomain: cfg.OIDC.EmailDomain, + AllowSignups: cfg.OIDC.AllowSignups.Value(), + UsernameField: cfg.OIDC.UsernameField.String(), + SignInText: cfg.OIDC.SignInText.String(), + IconURL: cfg.OIDC.IconURL.String(), + IgnoreEmailVerified: cfg.OIDC.IgnoreEmailVerified.Value(), } } - if cfg.InMemoryDatabase.Value { + if cfg.InMemoryDatabase { options.Database = dbfake.New() options.Pubsub = database.NewPubsubInMemory() } else { - sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, cfg.PostgresURL.Value) + sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, cfg.PostgresURL.String()) if err != nil { return xerrors.Errorf("connect to postgres: %w", err) } @@ -581,7 +793,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co }() options.Database = database.New(sqlDB) - options.Pubsub, err = database.NewPubsub(ctx, sqlDB, cfg.PostgresURL.Value) + options.Pubsub, err = database.NewPubsub(ctx, sqlDB, cfg.PostgresURL.String()) if err != nil { return xerrors.Errorf("create pubsub: %w", err) } @@ -646,21 +858,13 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co return err } - // Disable telemetry if the in-memory database is used unless explicitly defined! - if cfg.InMemoryDatabase.Value && !cmd.Flags().Changed(cfg.Telemetry.Enable.Flag) { - cfg.Telemetry.Enable.Value = false - } - if cfg.Telemetry.Enable.Value { - // Parse the raw telemetry URL! - telemetryURL, err := parseURL(cfg.Telemetry.URL.Value) - if err != nil { - return xerrors.Errorf("parse telemetry url: %w", err) - } - + if cfg.Telemetry.Enable { gitAuth := make([]telemetry.GitAuth, 0) + // TODO: + var gitAuthConfigs []codersdk.GitAuthConfig for _, cfg := range gitAuthConfigs { gitAuth = append(gitAuth, telemetry.GitAuth{ - Type: string(cfg.Type), + Type: cfg.Type, }) } @@ -669,15 +873,15 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co DeploymentID: deploymentID, Database: options.Database, Logger: logger.Named("telemetry"), - URL: telemetryURL, - Wildcard: cfg.WildcardAccessURL.Value != "", - DERPServerRelayURL: cfg.DERP.Server.RelayURL.Value, + URL: cfg.Telemetry.URL.Value(), + Wildcard: cfg.WildcardAccessURL.String() != "", + DERPServerRelayURL: cfg.DERP.Server.RelayURL.String(), GitAuth: gitAuth, - GitHubOAuth: cfg.OAuth2.Github.ClientID.Value != "", - OIDCAuth: cfg.OIDC.ClientID.Value != "", - OIDCIssuerURL: cfg.OIDC.IssuerURL.Value, - Prometheus: cfg.Prometheus.Enable.Value, - STUN: len(cfg.DERP.Server.STUNAddresses.Value) != 0, + GitHubOAuth: cfg.OAuth2.Github.ClientID != "", + OIDCAuth: cfg.OIDC.ClientID != "", + OIDCIssuerURL: cfg.OIDC.IssuerURL.String(), + Prometheus: cfg.Prometheus.Enable.Value(), + STUN: len(cfg.DERP.Server.STUNAddresses) != 0, Tunnel: tunnel != nil, }) if err != nil { @@ -688,11 +892,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // This prevents the pprof import from being accidentally deleted. _ = pprof.Handler - if cfg.Pprof.Enable.Value { + if cfg.Pprof.Enable { //nolint:revive - defer serveHandler(ctx, logger, nil, cfg.Pprof.Address.Value, "pprof")() + defer serveHandler(ctx, logger, nil, cfg.Pprof.Address.String(), "pprof")() } - if cfg.Prometheus.Enable.Value { + if cfg.Prometheus.Enable { options.PrometheusRegistry.MustRegister(collectors.NewGoCollector()) options.PrometheusRegistry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})) @@ -711,11 +915,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co //nolint:revive defer serveHandler(ctx, logger, promhttp.InstrumentMetricHandler( options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}), - ), cfg.Prometheus.Address.Value, "prometheus")() + ), cfg.Prometheus.Address.String(), "prometheus")() } - if cfg.Swagger.Enable.Value { - options.SwaggerEndpoint = cfg.Swagger.Enable.Value + if cfg.Swagger.Enable { + options.SwaggerEndpoint = cfg.Swagger.Enable.Value() } // We use a separate coderAPICloser so the Enterprise API @@ -742,7 +946,10 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // This is helpful for tests, but can be silently ignored. // Coder may be ran as users that don't have permission to write in the homedir, // such as via the systemd service. - _ = config.URL().Write(client.URL.String()) + err = config.URL().Write(client.URL.String()) + if err != nil && flag.Lookup("test.v") != nil { + return xerrors.Errorf("write config url: %w", err) + } // Since errCh only has one buffered slot, all routines // sending on it must be wrapped in a select/default to @@ -759,7 +966,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co } }() provisionerdMetrics := provisionerd.NewMetrics(options.PrometheusRegistry) - for i := 0; i < cfg.Provisioner.Daemons.Value; i++ { + for i := int64(0); i < cfg.Provisioner.Daemons.Value(); i++ { daemonCacheDir := filepath.Join(cacheDir, fmt.Sprintf("provisioner-%d", i)) daemon, err := newProvisionerDaemon(ctx, coderAPI, provisionerdMetrics, logger, cfg, daemonCacheDir, errCh, false) if err != nil { @@ -774,8 +981,8 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co // Wrap the server in middleware that redirects to the access URL if // the request is not to a local IP. var handler http.Handler = coderAPI.RootHandler - if cfg.RedirectToAccessURL.Value { - handler = redirectToAccessURL(handler, accessURLParsed, tunnel != nil, appHostnameRegex) + if cfg.RedirectToAccessURL { + handler = redirectToAccessURL(handler, cfg.AccessURL.Value(), tunnel != nil, appHostnameRegex) } // ReadHeaderTimeout is purposefully not enabled. It caused some @@ -842,7 +1049,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co cmd.Println("\nFailed to check for the first user: " + err.Error()) } else if !hasFirstUser { cmd.Println("\nGet started by creating the first user (in a new terminal):") - cmd.Println(cliui.Styles.Code.Render("coder login " + accessURLParsed.String())) + cmd.Println(cliui.Styles.Code.Render("coder login " + cfg.AccessURL.String())) } cmd.Println("\n==> Logs will stream in below (press ctrl+c to gracefully exit):") @@ -853,7 +1060,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co return xerrors.Errorf("notify systemd: %w", err) } - autobuildPoller := time.NewTicker(cfg.AutobuildPollInterval.Value) + autobuildPoller := time.NewTicker(cfg.AutobuildPollInterval.Value()) defer autobuildPoller.Stop() autobuildExecutor := executor.New(ctx, options.Database, logger, autobuildPoller.C) autobuildExecutor.Run() @@ -1011,10 +1218,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co postgresBuiltinServeCmd.Flags().BoolVar(&pgRawURL, "raw-url", false, "Output the raw connection URL instead of a psql command.") createAdminUserCommand := newCreateAdminUserCommand() + root.SetHelpFunc(func(cmd *cobra.Command, args []string) { + // Help is handled by clibase in command body. + }) root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd, createAdminUserCommand) - deployment.AttachFlags(root.Flags(), vip, false) - return root } @@ -1063,7 +1271,7 @@ func newProvisionerDaemon( coderAPI *coderd.API, metrics provisionerd.Metrics, logger slog.Logger, - cfg *codersdk.DeploymentConfig, + cfg *codersdk.DeploymentValues, cacheDir string, errCh chan error, dev bool, @@ -1140,11 +1348,11 @@ func newProvisionerDaemon( return coderAPI.CreateInMemoryProvisionerDaemon(ctx, debounce) }, &provisionerd.Options{ Logger: logger, - JobPollInterval: cfg.Provisioner.DaemonPollInterval.Value, - JobPollJitter: cfg.Provisioner.DaemonPollJitter.Value, + JobPollInterval: cfg.Provisioner.DaemonPollInterval.Value(), + JobPollJitter: cfg.Provisioner.DaemonPollJitter.Value(), JobPollDebounce: debounce, UpdateInterval: 500 * time.Millisecond, - ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value, + ForceCancelInterval: cfg.Provisioner.ForceCancelInterval.Value(), Provisioners: provisioners, WorkDirectory: tempDir, TracerProvider: coderAPI.TracerProvider, @@ -1172,7 +1380,11 @@ func loadCertificates(tlsCertFiles, tlsKeyFiles []string) ([]tls.Certificate, er certFile, keyFile := tlsCertFiles[i], tlsKeyFiles[i] cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { - return nil, xerrors.Errorf("load TLS key pair %d (%q, %q): %w", i, certFile, keyFile, err) + return nil, xerrors.Errorf( + "load TLS key pair %d (%q, %q): %w\ncertFiles: %+v\nkeyFiles: %+v", + i, certFile, keyFile, err, + tlsCertFiles, tlsKeyFiles, + ) } certs[i] = cert @@ -1554,7 +1766,7 @@ func isLocalhost(host string) bool { return host == "localhost" || host == "127.0.0.1" || host == "::1" } -func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logger, func(), error) { +func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentValues) (slog.Logger, func(), error) { var ( sinks = []slog.Sink{} closers = []func() error{} @@ -1575,32 +1787,31 @@ func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logge if err != nil { return xerrors.Errorf("open log file %q: %w", loc, err) } - closers = append(closers, fi.Close) sinks = append(sinks, sinkFn(fi)) } return nil } - err := addSinkIfProvided(sloghuman.Sink, cfg.Logging.Human.Value) + err := addSinkIfProvided(sloghuman.Sink, cfg.Logging.Human.String()) if err != nil { return slog.Logger{}, nil, xerrors.Errorf("add human sink: %w", err) } - err = addSinkIfProvided(slogjson.Sink, cfg.Logging.JSON.Value) + err = addSinkIfProvided(slogjson.Sink, cfg.Logging.JSON.String()) if err != nil { return slog.Logger{}, nil, xerrors.Errorf("add json sink: %w", err) } - err = addSinkIfProvided(slogstackdriver.Sink, cfg.Logging.Stackdriver.Value) + err = addSinkIfProvided(slogstackdriver.Sink, cfg.Logging.Stackdriver.String()) if err != nil { return slog.Logger{}, nil, xerrors.Errorf("add stackdriver sink: %w", err) } - if cfg.Trace.CaptureLogs.Value { + if cfg.Trace.CaptureLogs { sinks = append(sinks, tracing.SlogSink{}) } level := slog.LevelInfo - if ok, _ := cmd.Flags().GetBool(varVerbose); ok { + if cfg.Verbose { level = slog.LevelDebug } diff --git a/cli/server_slim.go b/cli/server_slim.go index 5b35d7ba4f..b54cf8c88d 100644 --- a/cli/server_slim.go +++ b/cli/server_slim.go @@ -9,14 +9,12 @@ import ( "os" "github.com/spf13/cobra" - "github.com/spf13/viper" "github.com/coder/coder/cli/cliui" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" ) -func Server(vip *viper.Viper, _ func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { +func Server(_ func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { root := &cobra.Command{ Use: "server", Short: "Start a Coder server", @@ -76,8 +74,6 @@ func Server(vip *viper.Viper, _ func(context.Context, *coderd.Options) (*coderd. root.AddCommand(postgresBuiltinURLCmd, postgresBuiltinServeCmd, createAdminUserCommand) - deployment.AttachFlags(root.Flags(), vip, false) - return root } diff --git a/cli/server_test.go b/cli/server_test.go index 17b14bd3dd..cd887c502c 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -30,6 +30,7 @@ import ( "github.com/stretchr/testify/require" "go.uber.org/goleak" + "github.com/coder/coder/cli" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/config" "github.com/coder/coder/coderd/coderdtest" @@ -40,6 +41,62 @@ import ( "github.com/coder/coder/testutil" ) +func TestReadGitAuthProvidersFromEnv(t *testing.T) { + t.Parallel() + t.Run("Empty", func(t *testing.T) { + t.Parallel() + providers, err := cli.ReadGitAuthProvidersFromEnv([]string{ + "HOME=/home/frodo", + }) + require.NoError(t, err) + require.Empty(t, providers) + }) + t.Run("InvalidKey", func(t *testing.T) { + t.Parallel() + providers, err := cli.ReadGitAuthProvidersFromEnv([]string{ + "CODER_GITAUTH_XXX=invalid", + }) + require.Error(t, err, "providers: %+v", providers) + require.Empty(t, providers) + }) + t.Run("SkipKey", func(t *testing.T) { + t.Parallel() + providers, err := cli.ReadGitAuthProvidersFromEnv([]string{ + "CODER_GITAUTH_0_ID=invalid", + "CODER_GITAUTH_2_ID=invalid", + }) + require.Error(t, err, "%+v", providers) + require.Empty(t, providers) + }) + t.Run("Valid", func(t *testing.T) { + t.Parallel() + providers, err := cli.ReadGitAuthProvidersFromEnv([]string{ + "CODER_GITAUTH_0_ID=1", + "CODER_GITAUTH_0_TYPE=gitlab", + "CODER_GITAUTH_1_ID=2", + "CODER_GITAUTH_1_CLIENT_ID=sid", + "CODER_GITAUTH_1_CLIENT_SECRET=hunter12", + "CODER_GITAUTH_1_TOKEN_URL=google.com", + "CODER_GITAUTH_1_VALIDATE_URL=bing.com", + "CODER_GITAUTH_1_SCOPES=repo:read repo:write", + }) + require.NoError(t, err) + require.Len(t, providers, 2) + + // Validate the first provider. + assert.Equal(t, "1", providers[0].ID) + assert.Equal(t, "gitlab", providers[0].Type) + + // Validate the second provider. + assert.Equal(t, "2", providers[1].ID) + assert.Equal(t, "sid", providers[1].ClientID) + assert.Equal(t, "hunter12", providers[1].ClientSecret) + assert.Equal(t, "google.com", providers[1].TokenURL) + assert.Equal(t, "bing.com", providers[1].ValidateURL) + assert.Equal(t, []string{"repo:read", "repo:write"}, providers[1].Scopes) + }) +} + // This cannot be ran in parallel because it uses a signal. // nolint:tparallel,paralleltest func TestServer(t *testing.T) { @@ -327,6 +384,7 @@ func TestServer(t *testing.T) { root, _ := clitest.New(t, args...) err := root.ExecuteContext(ctx) require.Error(t, err) + t.Logf("args: %v", args) require.ErrorContains(t, err, c.errContains) }) } @@ -341,17 +399,14 @@ func TestServer(t *testing.T) { "server", "--in-memory", "--http-address", "", - "--access-url", "http://example.com", + "--access-url", "https://example.com", "--tls-enable", "--tls-address", ":0", "--tls-cert-file", certPath, "--tls-key-file", keyPath, "--cache-dir", t.TempDir(), ) - errC := make(chan error, 1) - go func() { - errC <- root.ExecuteContext(ctx) - }() + clitest.Start(ctx, t, root) // Verify HTTPS accessURL := waitAccessURL(t, cfg) @@ -368,9 +423,6 @@ func TestServer(t *testing.T) { defer client.HTTPClient.CloseIdleConnections() _, err := client.HasFirstUser(ctx) require.NoError(t, err) - - cancelFunc() - require.NoError(t, <-errC) }) t.Run("TLSValidMultiple", func(t *testing.T) { t.Parallel() @@ -383,7 +435,7 @@ func TestServer(t *testing.T) { "server", "--in-memory", "--http-address", "", - "--access-url", "http://example.com", + "--access-url", "https://example.com", "--tls-enable", "--tls-address", ":0", "--tls-cert-file", cert1Path, @@ -392,10 +444,10 @@ func TestServer(t *testing.T) { "--tls-key-file", key2Path, "--cache-dir", t.TempDir(), ) - errC := make(chan error, 1) - go func() { - errC <- root.ExecuteContext(ctx) - }() + pty := ptytest.New(t) + root.SetOut(pty.Output()) + clitest.Start(ctx, t, root) + accessURL := waitAccessURL(t, cfg) require.Equal(t, "https", accessURL.Scheme) originalHost := accessURL.Host @@ -451,9 +503,6 @@ func TestServer(t *testing.T) { _, err = client.HasFirstUser(ctx) require.NoError(t, err) require.EqualValues(t, 2, atomic.LoadInt64(&dials)) - - cancelFunc() - require.NoError(t, <-errC) }) t.Run("TLSAndHTTP", func(t *testing.T) { @@ -715,15 +764,19 @@ func TestServer(t *testing.T) { pty := ptytest.New(t) root.SetOutput(pty.Output()) root.SetErr(pty.Output()) - errC := make(chan error, 1) + serverStop := make(chan error, 1) go func() { - errC <- root.ExecuteContext(ctx) + err := root.ExecuteContext(ctx) + if err != nil { + t.Error(err) + } + close(serverStop) }() pty.ExpectMatch("Started HTTP listener at http://0.0.0.0:") cancelFunc() - require.NoError(t, <-errC) + <-serverStop }) t.Run("CanListenUnspecifiedv6", func(t *testing.T) { @@ -741,15 +794,19 @@ func TestServer(t *testing.T) { pty := ptytest.New(t) root.SetOutput(pty.Output()) root.SetErr(pty.Output()) - errC := make(chan error, 1) + serverClose := make(chan struct{}, 1) go func() { - errC <- root.ExecuteContext(ctx) + err := root.ExecuteContext(ctx) + if err != nil { + t.Error(err) + } + close(serverClose) }() pty.ExpectMatch("Started HTTP listener at http://[::]:") cancelFunc() - require.NoError(t, <-errC) + <-serverClose }) t.Run("NoAddress", func(t *testing.T) { @@ -760,13 +817,13 @@ func TestServer(t *testing.T) { root, _ := clitest.New(t, "server", "--in-memory", - "--http-address", "", + "--http-address", ":80", "--tls-enable=false", "--tls-address", "", ) err := root.ExecuteContext(ctx) require.Error(t, err) - require.ErrorContains(t, err, "TLS is disabled. Enable with --tls-enable or specify a HTTP address") + require.ErrorContains(t, err, "tls-address") }) t.Run("NoTLSAddress", func(t *testing.T) { @@ -782,7 +839,7 @@ func TestServer(t *testing.T) { ) err := root.ExecuteContext(ctx) require.Error(t, err) - require.ErrorContains(t, err, "TLS address must be set if TLS is enabled") + require.ErrorContains(t, err, "must not be empty") }) // DeprecatedAddress is a test for the deprecated --address flag. If @@ -807,21 +864,15 @@ func TestServer(t *testing.T) { pty := ptytest.New(t) root.SetOutput(pty.Output()) root.SetErr(pty.Output()) - errC := make(chan error, 1) - go func() { - errC <- root.ExecuteContext(ctx) - }() + clitest.Start(ctx, t, root) - pty.ExpectMatch("--address and -a are deprecated") + pty.ExpectMatch("is deprecated") accessURL := waitAccessURL(t, cfg) require.Equal(t, "http", accessURL.Scheme) client := codersdk.New(accessURL) _, err := client.HasFirstUser(ctx) require.NoError(t, err) - - cancelFunc() - require.NoError(t, <-errC) }) t.Run("TLS", func(t *testing.T) { @@ -834,7 +885,7 @@ func TestServer(t *testing.T) { "server", "--in-memory", "--address", ":0", - "--access-url", "http://example.com", + "--access-url", "https://example.com", "--tls-enable", "--tls-cert-file", certPath, "--tls-key-file", keyPath, @@ -843,12 +894,9 @@ func TestServer(t *testing.T) { pty := ptytest.New(t) root.SetOutput(pty.Output()) root.SetErr(pty.Output()) - errC := make(chan error, 1) - go func() { - errC <- root.ExecuteContext(ctx) - }() + clitest.Start(ctx, t, root) - pty.ExpectMatch("--address and -a are deprecated") + pty.ExpectMatch("is deprecated") accessURL := waitAccessURL(t, cfg) require.Equal(t, "https", accessURL.Scheme) @@ -864,9 +912,6 @@ func TestServer(t *testing.T) { defer client.HTTPClient.CloseIdleConnections() _, err := client.HasFirstUser(ctx) require.NoError(t, err) - - cancelFunc() - require.NoError(t, <-errC) }) }) @@ -1150,14 +1195,31 @@ func TestServer(t *testing.T) { }) }) + waitFile := func(t *testing.T, fiName string, dur time.Duration) { + var lastStat os.FileInfo + require.Eventually(t, func() bool { + var err error + lastStat, err = os.Stat(fiName) + if err != nil { + if !os.IsNotExist(err) { + t.Fatalf("unexpected error: %v", err) + } + return false + } + return lastStat.Size() > 0 + }, + testutil.WaitShort, + testutil.IntervalFast, + "file at %s should exist, last stat: %+v", + fiName, lastStat, + ) + } + t.Run("Logging", func(t *testing.T) { t.Parallel() t.Run("CreatesFile", func(t *testing.T) { t.Parallel() - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - fiName := testutil.TempFile(t, "", "coder-logging-test-*") root, _ := clitest.New(t, @@ -1168,24 +1230,13 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", "--log-human", fiName, ) - serverErr := make(chan error, 1) - go func() { - serverErr <- root.ExecuteContext(ctx) - }() + clitest.Start(context.Background(), t, root) - assert.Eventually(t, func() bool { - stat, err := os.Stat(fiName) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalFast) - cancelFunc() - <-serverErr + waitFile(t, fiName, testutil.WaitShort) }) t.Run("Human", func(t *testing.T) { t.Parallel() - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - fi := testutil.TempFile(t, "", "coder-logging-test-*") root, _ := clitest.New(t, @@ -1196,24 +1247,13 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", "--log-human", fi, ) - serverErr := make(chan error, 1) - go func() { - serverErr <- root.ExecuteContext(ctx) - }() + clitest.Start(context.Background(), t, root) - assert.Eventually(t, func() bool { - stat, err := os.Stat(fi) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalFast) - cancelFunc() - <-serverErr + waitFile(t, fi, testutil.WaitShort) }) t.Run("JSON", func(t *testing.T) { t.Parallel() - ctx, cancelFunc := context.WithCancel(context.Background()) - defer cancelFunc() - fi := testutil.TempFile(t, "", "coder-logging-test-*") root, _ := clitest.New(t, @@ -1224,17 +1264,9 @@ func TestServer(t *testing.T) { "--access-url", "http://example.com", "--log-json", fi, ) - serverErr := make(chan error, 1) - go func() { - serverErr <- root.ExecuteContext(ctx) - }() + clitest.Start(context.Background(), t, root) - assert.Eventually(t, func() bool { - stat, err := os.Stat(fi) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalFast) - cancelFunc() - <-serverErr + waitFile(t, fi, testutil.WaitShort) }) t.Run("Stackdriver", func(t *testing.T) { @@ -1271,10 +1303,7 @@ func TestServer(t *testing.T) { // starting point for expecting logs. _ = pty.ExpectMatchContext(ctx, "Started HTTP listener at ") - require.Eventually(t, func() bool { - stat, err := os.Stat(fi) - return err == nil && stat.Size() > 0 - }, testutil.WaitLong, testutil.IntervalMedium) + waitFile(t, fi, testutil.WaitSuperLong) }) t.Run("Multiple", func(t *testing.T) { @@ -1306,31 +1335,15 @@ func TestServer(t *testing.T) { root.SetOut(pty.Output()) root.SetErr(pty.Output()) - serverErr := make(chan error, 1) - go func() { - serverErr <- root.ExecuteContext(ctx) - }() - defer func() { - cancelFunc() - <-serverErr - }() + clitest.Start(ctx, t, root) // Wait for server to listen on HTTP, this is a good // starting point for expecting logs. _ = pty.ExpectMatchContext(ctx, "Started HTTP listener at ") - require.Eventually(t, func() bool { - stat, err := os.Stat(fi1) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalMedium, "log human size > 0") - require.Eventually(t, func() bool { - stat, err := os.Stat(fi2) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalMedium, "log json size > 0") - require.Eventually(t, func() bool { - stat, err := os.Stat(fi3) - return err == nil && stat.Size() > 0 - }, testutil.WaitShort, testutil.IntervalMedium, "log stackdriver size > 0") + waitFile(t, fi1, testutil.WaitSuperLong) + waitFile(t, fi2, testutil.WaitSuperLong) + waitFile(t, fi3, testutil.WaitSuperLong) }) }) } diff --git a/cli/templateedit_test.go b/cli/templateedit_test.go index 4361985fea..aa29aa2c3e 100644 --- a/cli/templateedit_test.go +++ b/cli/templateedit_test.go @@ -301,7 +301,6 @@ func TestTemplateEdit(t *testing.T) { HasLicense: true, Trial: true, RequireTelemetry: false, - Experimental: false, } for _, feature := range codersdk.FeatureNames { res.Features[feature] = codersdk.Feature{ @@ -374,7 +373,6 @@ func TestTemplateEdit(t *testing.T) { HasLicense: true, Trial: true, RequireTelemetry: false, - Experimental: false, } for _, feature := range codersdk.FeatureNames { var one int64 = 1 diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden deleted file mode 100644 index beb6994c50..0000000000 --- a/cli/testdata/coder_server_--help.golden +++ /dev/null @@ -1,400 +0,0 @@ -Start a Coder server - -Usage: - coder server [flags] - - coder server [command] - -Commands: - create-admin-user Create a new admin user with the given username, email and password and adds it to every organization. - postgres-builtin-serve Run the built-in PostgreSQL deployment. - postgres-builtin-url Output the connection URL for the built-in PostgreSQL deployment. - -Flags: - --access-url string External URL to access your - deployment. This must be accessible - by all provisioned workspaces. - Consumes $CODER_ACCESS_URL - --api-rate-limit int Maximum number of requests per - minute allowed to the API per user, - or per IP address for - unauthenticated users. Negative - values mean no rate limit. Some API - endpoints have separate strict rate - limits regardless of this value to - prevent denial-of-service or brute - force attacks. - Consumes $CODER_API_RATE_LIMIT - (default 512) - --cache-dir string The directory to cache temporary - files. If unspecified and - $CACHE_DIRECTORY is set, it will be - used for compatibility with systemd. - Consumes $CODER_CACHE_DIRECTORY - (default "~/.cache/coder") - --dangerous-allow-path-app-sharing Allow workspace apps that are not - served from subdomains to be shared. - Path-based app sharing is DISABLED - by default for security purposes. - Path-based apps can make requests to - the Coder API and pose a security - risk when the workspace serves - malicious JavaScript. Path-based - apps can be disabled entirely with - --disable-path-apps for further - security. - Consumes - $CODER_DANGEROUS_ALLOW_PATH_APP_SHARING - --dangerous-allow-path-app-site-owner-access Allow site-owners to access - workspace apps from workspaces they - do not own. Owners cannot access - path-based apps they do not own by - default. Path-based apps can make - requests to the Coder API and pose a - security risk when the workspace - serves malicious JavaScript. - Path-based apps can be disabled - entirely with --disable-path-apps - for further security. - Consumes - $CODER_DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS - --dangerous-disable-rate-limits Disables all rate limits. This is - not recommended in production. - Consumes $CODER_RATE_LIMIT_DISABLE_ALL - --derp-config-path string Path to read a DERP mapping from. - See: - https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_PATH - --derp-config-url string URL to fetch a DERP mapping on - startup. See: - https://tailscale.com/kb/1118/custom-derp-servers/ - Consumes $CODER_DERP_CONFIG_URL - --derp-server-enable Whether to enable or disable the - embedded DERP relay server. - Consumes $CODER_DERP_SERVER_ENABLE - (default true) - --derp-server-region-code string Region code to use for the embedded - DERP server. - Consumes - $CODER_DERP_SERVER_REGION_CODE - (default "coder") - --derp-server-region-id int Region ID to use for the embedded - DERP server. - Consumes - $CODER_DERP_SERVER_REGION_ID - (default 999) - --derp-server-region-name string Region name that for the embedded - DERP server. - Consumes - $CODER_DERP_SERVER_REGION_NAME - (default "Coder Embedded Relay") - --derp-server-stun-addresses strings Addresses for STUN servers to - establish P2P connections. Set empty - to disable P2P connections. - Consumes - $CODER_DERP_SERVER_STUN_ADDRESSES - (default [stun.l.google.com:19302]) - --disable-password-auth coder server create-admin Disable password authentication. - This is recommended for security - purposes in production deployments - that rely on an identity provider. - Any user with the owner role will be - able to sign in with their password - regardless of this setting to avoid - potential lock out. If you are - locked out of your account, you can - use the coder server create-admin - command to create a new admin user - directly in the database. - Consumes $CODER_DISABLE_PASSWORD_AUTH - --disable-path-apps Disable workspace apps that are not - served from subdomains. Path-based - apps can make requests to the Coder - API and pose a security risk when - the workspace serves malicious - JavaScript. This is recommended for - security purposes if a - --wildcard-access-url is configured. - Consumes $CODER_DISABLE_PATH_APPS - --disable-session-expiry-refresh Disable automatic session expiry - bumping due to activity. This forces - all sessions to become invalid after - the session expiry duration has been - reached. - Consumes - $CODER_DISABLE_SESSION_EXPIRY_REFRESH - --experiments strings Enable one or more experiments. - These are not ready for production. - Separate multiple experiments with - commas, or enter '*' to opt-in to - all available experiments. - Consumes $CODER_EXPERIMENTS - -h, --help help for server - --http-address string HTTP bind address of the server. - Unset to disable the HTTP endpoint. - Consumes $CODER_HTTP_ADDRESS - (default "127.0.0.1:3000") - --log-human string Output human-readable logs to a - given file. - Consumes $CODER_LOGGING_HUMAN - (default "/dev/stderr") - --log-json string Output JSON logs to a given file. - Consumes $CODER_LOGGING_JSON - --log-stackdriver string Output Stackdriver compatible logs - to a given file. - Consumes $CODER_LOGGING_STACKDRIVER - --max-token-lifetime duration The maximum lifetime duration users - can specify when creating an API - token. - Consumes $CODER_MAX_TOKEN_LIFETIME - (default 2540400h0m0s) - --oauth2-github-allow-everyone Allow all logins, setting this - option means allowed orgs and teams - must be empty. - Consumes - $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE - --oauth2-github-allow-signups Whether new users can sign up with - GitHub. - Consumes - $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS - --oauth2-github-allowed-orgs strings Organizations the user must be a - member of to Login with GitHub. - Consumes - $CODER_OAUTH2_GITHUB_ALLOWED_ORGS - --oauth2-github-allowed-teams strings Teams inside organizations the user - must be a member of to Login with - GitHub. Structured as: - /. - Consumes - $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS - --oauth2-github-client-id string Client ID for Login with GitHub. - Consumes $CODER_OAUTH2_GITHUB_CLIENT_ID - --oauth2-github-client-secret string Client secret for Login with GitHub. - Consumes - $CODER_OAUTH2_GITHUB_CLIENT_SECRET - --oauth2-github-enterprise-base-url string Base URL of a GitHub Enterprise - deployment to use for Login with - GitHub. - Consumes - $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL - --oidc-allow-signups Whether new users can sign up with - OIDC. - Consumes $CODER_OIDC_ALLOW_SIGNUPS - (default true) - --oidc-client-id string Client ID to use for Login with - OIDC. - Consumes $CODER_OIDC_CLIENT_ID - --oidc-client-secret string Client secret to use for Login with - OIDC. - Consumes $CODER_OIDC_CLIENT_SECRET - --oidc-email-domain strings Email domains that clients logging - in with OIDC must match. - Consumes $CODER_OIDC_EMAIL_DOMAIN - --oidc-icon-url string URL pointing to the icon to use on - the OepnID Connect login button - Consumes $CODER_OIDC_ICON_URL - --oidc-ignore-email-verified Ignore the email_verified claim from - the upstream provider. - Consumes - $CODER_OIDC_IGNORE_EMAIL_VERIFIED - --oidc-issuer-url string Issuer URL to use for Login with - OIDC. - Consumes $CODER_OIDC_ISSUER_URL - --oidc-scopes strings Scopes to grant when authenticating - with OIDC. - Consumes $CODER_OIDC_SCOPES (default - [openid,profile,email]) - --oidc-sign-in-text string The text to show on the OpenID - Connect sign in button - Consumes $CODER_OIDC_SIGN_IN_TEXT - (default "OpenID Connect") - --oidc-username-field string OIDC claim field to use as the - username. - Consumes $CODER_OIDC_USERNAME_FIELD - (default "preferred_username") - --postgres-url string URL of a PostgreSQL database. If - empty, PostgreSQL binaries will be - downloaded from Maven - (https://repo1.maven.org/maven2) and - store all data in the config root. - Access the built-in database with - "coder server postgres-builtin-url". - Consumes $CODER_PG_CONNECTION_URL - --pprof-address string The bind address to serve pprof. - Consumes $CODER_PPROF_ADDRESS - (default "127.0.0.1:6060") - --pprof-enable Serve pprof metrics on the address - defined by pprof address. - Consumes $CODER_PPROF_ENABLE - --prometheus-address string The bind address to serve prometheus - metrics. - Consumes $CODER_PROMETHEUS_ADDRESS - (default "127.0.0.1:2112") - --prometheus-enable Serve prometheus metrics on the - address defined by prometheus - address. - Consumes $CODER_PROMETHEUS_ENABLE - --provisioner-daemon-poll-interval duration Time to wait before polling for a - new job. - Consumes - $CODER_PROVISIONER_DAEMON_POLL_INTERVAL (default 1s) - --provisioner-daemon-poll-jitter duration Random jitter added to the poll - interval. - Consumes - $CODER_PROVISIONER_DAEMON_POLL_JITTER (default 100ms) - --provisioner-daemons int Number of provisioner daemons to - create on start. If builds are stuck - in queued state for a long time, - consider increasing this. - Consumes $CODER_PROVISIONER_DAEMONS - (default 3) - --provisioner-force-cancel-interval duration Time to force cancel provisioning - tasks that are stuck. - Consumes - $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL (default 10m0s) - --proxy-trusted-headers strings Headers to trust for forwarding IP - addresses. e.g. Cf-Connecting-Ip, - True-Client-Ip, X-Forwarded-For - Consumes $CODER_PROXY_TRUSTED_HEADERS - --proxy-trusted-origins strings Origin addresses to respect - "proxy-trusted-headers". e.g. - 192.168.1.0/24 - Consumes $CODER_PROXY_TRUSTED_ORIGINS - --redirect-to-access-url Specifies whether to redirect - requests that do not match the - access URL host. - Consumes $CODER_REDIRECT_TO_ACCESS_URL - --secure-auth-cookie Controls if the 'Secure' property is - set on browser session cookies. - Consumes $CODER_SECURE_AUTH_COOKIE - --session-duration duration The token expiry duration for - browser sessions. Sessions may last - longer if they are actively making - requests, but this functionality can - be disabled via - --disable-session-expiry-refresh. - Consumes $CODER_MAX_SESSION_EXPIRY - (default 24h0m0s) - --ssh-keygen-algorithm string The algorithm to use for generating - ssh keys. Accepted values are - "ed25519", "ecdsa", or "rsa4096". - Consumes $CODER_SSH_KEYGEN_ALGORITHM - (default "ed25519") - --strict-transport-security int Controls if the - 'Strict-Transport-Security' header - is set on all static file responses. - This header should only be set if - the server is accessed via HTTPS. - This value is the MaxAge in seconds - of the header. - Consumes $CODER_STRICT_TRANSPORT_SECURITY - --strict-transport-security-options strings Two optional fields can be set in - the Strict-Transport-Security - header; 'includeSubDomains' and - 'preload'. The - 'strict-transport-security' flag - must be set to a non-zero value for - these options to be used. - Consumes - $CODER_STRICT_TRANSPORT_SECURITY_OPTIONS - --swagger-enable Expose the swagger endpoint via - /swagger. - Consumes $CODER_SWAGGER_ENABLE - --telemetry Whether telemetry is enabled or not. - Coder collects anonymized usage data - to help improve our product. - Consumes $CODER_TELEMETRY_ENABLE - --telemetry-trace Whether Opentelemetry traces are - sent to Coder. Coder collects - anonymized application tracing to - help improve our product. Disabling - telemetry also disables this option. - Consumes $CODER_TELEMETRY_TRACE - --tls-address string HTTPS bind address of the server. - Consumes $CODER_TLS_ADDRESS (default - "127.0.0.1:3443") - --tls-cert-file strings Path to each certificate for TLS. It - requires a PEM-encoded file. To - configure the listener to use a CA - certificate, concatenate the primary - certificate and the CA certificate - together. The primary certificate - should appear first in the combined - file. - Consumes $CODER_TLS_CERT_FILE - --tls-client-auth string Policy the server will follow for - TLS Client Authentication. Accepted - values are "none", "request", - "require-any", "verify-if-given", or - "require-and-verify". - Consumes $CODER_TLS_CLIENT_AUTH - (default "none") - --tls-client-ca-file string PEM-encoded Certificate Authority - file used for checking the - authenticity of client - Consumes $CODER_TLS_CLIENT_CA_FILE - --tls-client-cert-file string Path to certificate for client TLS - authentication. It requires a - PEM-encoded file. - Consumes $CODER_TLS_CLIENT_CERT_FILE - --tls-client-key-file string Path to key for client TLS - authentication. It requires a - PEM-encoded file. - Consumes $CODER_TLS_CLIENT_KEY_FILE - --tls-enable Whether TLS will be enabled. - Consumes $CODER_TLS_ENABLE - --tls-key-file strings Paths to the private keys for each - of the certificates. It requires a - PEM-encoded file. - Consumes $CODER_TLS_KEY_FILE - --tls-min-version string Minimum supported version of TLS. - Accepted values are "tls10", - "tls11", "tls12" or "tls13" - Consumes $CODER_TLS_MIN_VERSION - (default "tls12") - --trace Whether application tracing data is - collected. It exports to a backend - configured by environment variables. - See: - https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md - Consumes $CODER_TRACE_ENABLE - --trace-honeycomb-api-key string Enables trace exporting to - Honeycomb.io using the provided API - Key. - Consumes $CODER_TRACE_HONEYCOMB_API_KEY - --trace-logs Enables capturing of logs as events - in traces. This is useful for - debugging, but may result in a very - large amount of events being sent to - the tracing backend which may incur - significant costs. If the verbose - flag was supplied, debug-level logs - will be included. - Consumes $CODER_TRACE_CAPTURE_LOGS - --update-check Periodically check for new releases - of Coder and inform the owner. The - check is performed once per day. - Consumes $CODER_UPDATE_CHECK - --wildcard-access-url string Specifies the wildcard hostname to - use for workspace applications in - the form "*.example.com". - Consumes $CODER_WILDCARD_ACCESS_URL - -Global Flags: - --global-config coder Path to the global coder config directory. - Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") - --header stringArray HTTP headers added to all requests. Provide as "Key=Value". - Consumes $CODER_HEADER - --no-feature-warning Suppress warnings about unlicensed features. - Consumes $CODER_NO_FEATURE_WARNING - --no-version-warning Suppress warning when client and server versions do not match. - Consumes $CODER_NO_VERSION_WARNING - --token string Specify an authentication token. For security reasons setting - CODER_SESSION_TOKEN is preferred. - Consumes $CODER_SESSION_TOKEN - --url string URL to a deployment. - Consumes $CODER_URL - -v, --verbose Enable verbose output. - Consumes $CODER_VERBOSE - -Use "coder server [command] --help" for more information about a command. diff --git a/cli/testdata/coder_server_create-admin-user_--help.golden b/cli/testdata/coder_server_create-admin-user_--help.golden index 1ff1e939ae..e69de29bb2 100644 --- a/cli/testdata/coder_server_create-admin-user_--help.golden +++ b/cli/testdata/coder_server_create-admin-user_--help.golden @@ -1,36 +0,0 @@ -Create a new admin user with the given username, email and password and adds it to every organization. - -Usage: - coder server create-admin-user [flags] - -Flags: - --email string The email of the new user. If not specified, you will be - prompted via stdin. Consumes $CODER_EMAIL. - -h, --help help for create-admin-user - --password string The password of the new user. If not specified, you will - be prompted via stdin. Consumes $CODER_PASSWORD. - --postgres-url string URL of a PostgreSQL database. If empty, the built-in - PostgreSQL deployment will be used (Coder must not be - already running in this case). Consumes $CODER_POSTGRES_URL. - --ssh-keygen-algorithm string The algorithm to use for generating ssh keys. Accepted - values are "ed25519", "ecdsa", or "rsa4096". Consumes - $CODER_SSH_KEYGEN_ALGORITHM. (default "ed25519") - --username string The username of the new user. If not specified, you will - be prompted via stdin. Consumes $CODER_USERNAME. - -Global Flags: - --global-config coder Path to the global coder config directory. - Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") - --header stringArray HTTP headers added to all requests. Provide as "Key=Value". - Consumes $CODER_HEADER - --no-feature-warning Suppress warnings about unlicensed features. - Consumes $CODER_NO_FEATURE_WARNING - --no-version-warning Suppress warning when client and server versions do not match. - Consumes $CODER_NO_VERSION_WARNING - --token string Specify an authentication token. For security reasons setting - CODER_SESSION_TOKEN is preferred. - Consumes $CODER_SESSION_TOKEN - --url string URL to a deployment. - Consumes $CODER_URL - -v, --verbose Enable verbose output. - Consumes $CODER_VERBOSE diff --git a/cli/testdata/coder_server_postgres-builtin-serve_--help.golden b/cli/testdata/coder_server_postgres-builtin-serve_--help.golden index 1bc7fd2fce..e69de29bb2 100644 --- a/cli/testdata/coder_server_postgres-builtin-serve_--help.golden +++ b/cli/testdata/coder_server_postgres-builtin-serve_--help.golden @@ -1,25 +0,0 @@ -Run the built-in PostgreSQL deployment. - -Usage: - coder server postgres-builtin-serve [flags] - -Flags: - -h, --help help for postgres-builtin-serve - --raw-url Output the raw connection URL instead of a psql command. - -Global Flags: - --global-config coder Path to the global coder config directory. - Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") - --header stringArray HTTP headers added to all requests. Provide as "Key=Value". - Consumes $CODER_HEADER - --no-feature-warning Suppress warnings about unlicensed features. - Consumes $CODER_NO_FEATURE_WARNING - --no-version-warning Suppress warning when client and server versions do not match. - Consumes $CODER_NO_VERSION_WARNING - --token string Specify an authentication token. For security reasons setting - CODER_SESSION_TOKEN is preferred. - Consumes $CODER_SESSION_TOKEN - --url string URL to a deployment. - Consumes $CODER_URL - -v, --verbose Enable verbose output. - Consumes $CODER_VERBOSE diff --git a/cli/testdata/coder_server_postgres-builtin-url_--help.golden b/cli/testdata/coder_server_postgres-builtin-url_--help.golden index 6fe19269dc..e69de29bb2 100644 --- a/cli/testdata/coder_server_postgres-builtin-url_--help.golden +++ b/cli/testdata/coder_server_postgres-builtin-url_--help.golden @@ -1,25 +0,0 @@ -Output the connection URL for the built-in PostgreSQL deployment. - -Usage: - coder server postgres-builtin-url [flags] - -Flags: - -h, --help help for postgres-builtin-url - --raw-url Output the raw connection URL instead of a psql command. - -Global Flags: - --global-config coder Path to the global coder config directory. - Consumes $CODER_CONFIG_DIR (default "~/.config/coderv2") - --header stringArray HTTP headers added to all requests. Provide as "Key=Value". - Consumes $CODER_HEADER - --no-feature-warning Suppress warnings about unlicensed features. - Consumes $CODER_NO_FEATURE_WARNING - --no-version-warning Suppress warning when client and server versions do not match. - Consumes $CODER_NO_VERSION_WARNING - --token string Specify an authentication token. For security reasons setting - CODER_SESSION_TOKEN is preferred. - Consumes $CODER_SESSION_TOKEN - --url string URL to a deployment. - Consumes $CODER_URL - -v, --verbose Enable verbose output. - Consumes $CODER_VERBOSE diff --git a/cli/usage.go b/cli/usage.go new file mode 100644 index 0000000000..0a49e8efda --- /dev/null +++ b/cli/usage.go @@ -0,0 +1,140 @@ +package cli + +import ( + _ "embed" + "fmt" + "io" + "sort" + "strings" + "text/template" + + "github.com/mitchellh/go-wordwrap" + + "github.com/coder/coder/cli/clibase" + "github.com/coder/coder/cli/cliui" +) + +//go:embed usage.tpl +var usageTemplateRaw string + +type optionGroup struct { + Name string + Description string + Options clibase.OptionSet +} + +const envPrefix = "CODER_" + +var usageTemplate = template.Must( + template.New("usage").Funcs( + template.FuncMap{ + "wordWrap": func(s string, width uint) string { + return wordwrap.WrapString(s, width) + }, + "trimNewline": func(s string) string { + return strings.TrimSuffix(s, "\n") + }, + "indent": func(s string, tabs int) string { + var sb strings.Builder + for _, line := range strings.Split(s, "\n") { + // Remove existing indent, if any. + _, _ = sb.WriteString(strings.Repeat("\t", tabs)) + _, _ = sb.WriteString(line) + _, _ = sb.WriteString("\n") + } + return sb.String() + }, + "envName": func(opt clibase.Option) string { + if opt.Env == "" { + return "" + } + return envPrefix + opt.Env + }, + "flagName": func(opt clibase.Option) string { + return opt.Flag + }, + "prettyHeader": func(s string) string { + return cliui.Styles.Bold.Render(s) + }, + "isEnterprise": func(opt clibase.Option) bool { + return opt.Annotations.IsSet("enterprise") + }, + "isDeprecated": func(opt clibase.Option) bool { + return len(opt.UseInstead) > 0 + }, + "formatGroupDescription": func(s string) string { + s = strings.ReplaceAll(s, "\n", "") + s = "\n" + s + "\n" + s = wordwrap.WrapString(s, 60) + return s + }, + "optionGroups": func(cmd *clibase.Cmd) []optionGroup { + groups := []optionGroup{{ + // Default group. + Name: "", + Description: "", + }} + + enterpriseGroup := optionGroup{ + Name: "Enterprise", + Description: `These options are only available in the Enterprise Edition.`, + } + + // Sort options lexicographically. + sort.Slice(cmd.Options, func(i, j int) bool { + return cmd.Options[i].Name < cmd.Options[j].Name + }) + + optionLoop: + for _, opt := range cmd.Options { + if opt.Hidden { + continue + } + // Enterprise options are always grouped separately. + if opt.Annotations.IsSet("enterprise") { + enterpriseGroup.Options = append(enterpriseGroup.Options, opt) + continue + } + if len(opt.Group.Ancestry()) == 0 { + // Just add option to default group. + groups[0].Options = append(groups[0].Options, opt) + continue + } + + groupName := opt.Group.FullName() + + for i, foundGroup := range groups { + if foundGroup.Name != groupName { + continue + } + groups[i].Options = append(groups[i].Options, opt) + continue optionLoop + } + + groups = append(groups, optionGroup{ + Name: groupName, + Description: opt.Group.Description, + Options: clibase.OptionSet{opt}, + }) + } + sort.Slice(groups, func(i, j int) bool { + // Sort groups lexicographically. + return groups[i].Name < groups[j].Name + }) + // Always show enterprise group last. + return append(groups, enterpriseGroup) + }, + }, + ).Parse(usageTemplateRaw), +) + +// usageFn returns a function that generates usage (help) +// output for a given command. +func usageFn(output io.Writer, cmd *clibase.Cmd) func() { + return func() { + err := usageTemplate.Execute(output, cmd) + if err != nil { + _, _ = fmt.Fprintf(output, "execute template: %v", err) + } + } +} diff --git a/cli/usage.tpl b/cli/usage.tpl new file mode 100644 index 0000000000..3e86925c2d --- /dev/null +++ b/cli/usage.tpl @@ -0,0 +1,31 @@ +{{- /* Heavily inspired by the Go toolchain formatting. */ -}} +usage: {{.FullUsage}} + +{{.Short}} +{{ with .Long}} {{.}} {{ end }} + +{{- range $index, $group := optionGroups . }} +{{ with $group.Name }} {{- print $group.Name " Options" | prettyHeader }} {{ else -}} {{ prettyHeader "Options"}}{{- end -}} +{{- with $group.Description }} +{{ formatGroupDescription . }} +{{- else }} +{{ " " }} +{{- end }} + {{- range $index, $option := $group.Options }} + {{- with flagName $option }} + --{{- . -}} {{ end }} {{- with $option.FlagShorthand }}, -{{- . -}} {{ end }} + {{- with envName $option }}, ${{ . }} {{ end }} + {{- with $option.Default }} (default: {{.}}) {{ end }} + {{- with $option.Description }} + {{- $desc := wordWrap $option.Description 60 }} +{{ indent $desc 2}} +{{- if isDeprecated $option }} DEPRECATED {{ end }} + {{- end -}} + {{- end }} +{{- end }} +{{- range $index, $child := .Children }} +{{- if eq $index 0 }} +{{ prettyHeader "Subcommands"}} +{{- end }} +{{ indent $child.Use 1 | trimNewline }}{{ indent $child.Short 1 | trimNewline }} +{{- end }} diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index cc0a7ac6a1..5d1a2156a7 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5307,6 +5307,177 @@ const docTemplate = `{ } } }, + "clibase.Annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "clibase.Group": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Group" + } + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent": { + "$ref": "#/definitions/clibase.Group" + } + } + }, + "clibase.HostPort": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "string" + } + } + }, + "clibase.Option": { + "type": "object", + "properties": { + "annotations": { + "description": "Annotations enable extensions to clibase higher up in the stack. It's useful for\nhelp formatting and documentation generation.", + "allOf": [ + { + "$ref": "#/definitions/clibase.Annotations" + } + ] + }, + "default": { + "description": "Default is parsed into Value if set.", + "type": "string" + }, + "description": { + "type": "string" + }, + "env": { + "description": "Env is the environment variable used to configure this option. If unset,\nenvironment configuring is disabled.", + "type": "string" + }, + "flag": { + "description": "Flag is the long name of the flag used to configure this option. If unset,\nflag configuring is disabled.", + "type": "string" + }, + "flag_shorthand": { + "description": "FlagShorthand is the one-character shorthand for the flag. If unset, no\nshorthand is used.", + "type": "string" + }, + "group": { + "description": "Group is a group hierarchy that helps organize this option in help, configs\nand other documentation.", + "allOf": [ + { + "$ref": "#/definitions/clibase.Group" + } + ] + }, + "hidden": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "use_instead": { + "description": "UseInstead is a list of options that should be used instead of this one.\nThe field is used to generate a deprecation warning.", + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Option" + } + }, + "value": { + "description": "Value includes the types listed in values.go." + }, + "yaml": { + "description": "YAML is the YAML key used to configure this option. If unset, YAML\nconfiguring is disabled.", + "type": "string" + } + } + }, + "clibase.Struct-array_codersdk_GitAuthConfig": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.GitAuthConfig" + } + } + } + }, + "clibase.Struct-array_codersdk_LinkConfig": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.LinkConfig" + } + } + } + }, + "clibase.URL": { + "type": "object", + "properties": { + "forceQuery": { + "description": "append a query ('?') even if RawQuery is empty", + "type": "boolean" + }, + "fragment": { + "description": "fragment for references, without '#'", + "type": "string" + }, + "host": { + "description": "host or host:port", + "type": "string" + }, + "omitHost": { + "description": "do not emit empty host (authority)", + "type": "boolean" + }, + "opaque": { + "description": "encoded opaque data", + "type": "string" + }, + "path": { + "description": "path (relative paths may omit leading slash)", + "type": "string" + }, + "rawFragment": { + "description": "encoded fragment hint (see EscapedFragment method)", + "type": "string" + }, + "rawPath": { + "description": "encoded path hint (see EscapedPath method)", + "type": "string" + }, + "rawQuery": { + "description": "encoded query values, without '?'", + "type": "string" + }, + "scheme": { + "type": "string" + }, + "user": { + "description": "username and password information", + "allOf": [ + { + "$ref": "#/definitions/url.Userinfo" + } + ] + } + } + }, "coderd.SCIMUser": { "type": "object", "properties": { @@ -6203,10 +6374,10 @@ const docTemplate = `{ "type": "object", "properties": { "path": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -6225,22 +6396,25 @@ const docTemplate = `{ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "region_code": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "region_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "region_name": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "relay_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "stun_addresses": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } } } }, @@ -6248,44 +6422,72 @@ const docTemplate = `{ "type": "object", "properties": { "allow_path_app_sharing": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allow_path_app_site_owner_access": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, "codersdk.DeploymentConfig": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/codersdk.DeploymentValues" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Option" + } + } + } + }, + "codersdk.DeploymentDAUsResponse": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.DAUEntry" + } + } + } + }, + "codersdk.DeploymentValues": { "type": "object", "properties": { "access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "address": { "description": "DEPRECATED: Use HTTPAddress or TLS.Address instead.", "allOf": [ { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" } ] }, "agent_fallback_troubleshooting_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "agent_stat_refresh_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "audit_logging": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "autobuild_poll_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "browser_only": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "cache_directory": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" + }, + "config": { + "type": "string" }, "dangerous": { "$ref": "#/definitions/codersdk.DangerousConfig" @@ -6294,45 +6496,41 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.DERP" }, "disable_password_auth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "disable_path_apps": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "disable_session_expiry_refresh": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" - }, - "experimental": { - "description": "DEPRECATED: Use Experiments instead.", - "allOf": [ - { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" - } - ] + "type": "boolean" }, "experiments": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, - "gitauth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig" + "git_auth": { + "$ref": "#/definitions/clibase.Struct-array_codersdk_GitAuthConfig" }, "http_address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "description": "HTTPAddress is a string because it may be set to zero to disable.", + "type": "string" }, "in_memory_database": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "logging": { "$ref": "#/definitions/codersdk.LoggingConfig" }, "max_session_expiry": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "max_token_lifetime": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "metrics_cache_refresh_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "oauth2": { "$ref": "#/definitions/codersdk.OAuth2Config" @@ -6341,7 +6539,7 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.OIDCConfig" }, "pg_connection_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "pprof": { "$ref": "#/definitions/codersdk.PprofConfig" @@ -6353,31 +6551,40 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.ProvisionerConfig" }, "proxy_trusted_headers": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "proxy_trusted_origins": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "rate_limit": { "$ref": "#/definitions/codersdk.RateLimitConfig" }, "redirect_to_access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "scim_api_key": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "secure_auth_cookie": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "ssh_keygen_algorithm": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "strict_transport_security": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "strict_transport_security_options": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "support": { "$ref": "#/definitions/codersdk.SupportConfig" @@ -6395,263 +6602,16 @@ const docTemplate = `{ "$ref": "#/definitions/codersdk.TraceConfig" }, "update_check": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" + }, + "verbose": { + "type": "boolean" }, "wildcard_access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" - } - } - }, - "codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthConfig" - } + "$ref": "#/definitions/clibase.URL" }, - "enterprise": { + "write_config": { "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthConfig" - } - } - } - }, - "codersdk.DeploymentConfigField-array_codersdk_LinkConfig": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.LinkConfig" - } - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.LinkConfig" - } - } - } - }, - "codersdk.DeploymentConfigField-array_string": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "type": "string" - } - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "codersdk.DeploymentConfigField-bool": { - "type": "object", - "properties": { - "default": { - "type": "boolean" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "boolean" - } - } - }, - "codersdk.DeploymentConfigField-int": { - "type": "object", - "properties": { - "default": { - "type": "integer" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "integer" - } - } - }, - "codersdk.DeploymentConfigField-string": { - "type": "object", - "properties": { - "default": { - "type": "string" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "codersdk.DeploymentConfigField-time_Duration": { - "type": "object", - "properties": { - "default": { - "type": "integer" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "integer" - } - } - }, - "codersdk.DeploymentDAUsResponse": { - "type": "object", - "properties": { - "entries": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.DAUEntry" - } } } }, @@ -6677,10 +6637,6 @@ const docTemplate = `{ "type": "string" } }, - "experimental": { - "description": "DEPRECATED: use Experiments instead.", - "type": "boolean" - }, "features": { "type": "object", "additionalProperties": { @@ -6936,13 +6892,13 @@ const docTemplate = `{ "type": "object", "properties": { "human": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "json": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "stackdriver": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -7000,25 +6956,31 @@ const docTemplate = `{ "type": "object", "properties": { "allow_everyone": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allow_signups": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allowed_orgs": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "allowed_teams": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "client_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_secret": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "enterprise_base_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -7040,34 +7002,40 @@ const docTemplate = `{ "type": "object", "properties": { "allow_signups": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "client_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_secret": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "email_domain": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "icon_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "ignore_email_verified": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "issuer_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "scopes": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "sign_in_text": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "username_field": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -7305,10 +7273,10 @@ const docTemplate = `{ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -7316,10 +7284,10 @@ const docTemplate = `{ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -7327,16 +7295,16 @@ const docTemplate = `{ "type": "object", "properties": { "daemon_poll_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "daemon_poll_jitter": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "daemons": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "force_cancel_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" } } }, @@ -7512,10 +7480,10 @@ const docTemplate = `{ "type": "object", "properties": { "api": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "disable_all": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -7628,7 +7596,7 @@ const docTemplate = `{ "type": "object", "properties": { "links": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_LinkConfig" + "$ref": "#/definitions/clibase.Struct-array_codersdk_LinkConfig" } } }, @@ -7636,7 +7604,7 @@ const docTemplate = `{ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -7644,34 +7612,40 @@ const docTemplate = `{ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "cert_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "client_auth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_ca_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_cert_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_key_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "key_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "min_version": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "redirect_http": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -7679,13 +7653,13 @@ const docTemplate = `{ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "trace": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" } } }, @@ -8054,13 +8028,13 @@ const docTemplate = `{ "type": "object", "properties": { "capture_logs": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "honeycomb_api_key": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -9077,6 +9051,9 @@ const docTemplate = `{ "type": "string" } } + }, + "url.Userinfo": { + "type": "object" } }, "securityDefinitions": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index dc5f1d9181..9788ef63ed 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4696,6 +4696,177 @@ } } }, + "clibase.Annotations": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "clibase.Group": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Group" + } + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parent": { + "$ref": "#/definitions/clibase.Group" + } + } + }, + "clibase.HostPort": { + "type": "object", + "properties": { + "host": { + "type": "string" + }, + "port": { + "type": "string" + } + } + }, + "clibase.Option": { + "type": "object", + "properties": { + "annotations": { + "description": "Annotations enable extensions to clibase higher up in the stack. It's useful for\nhelp formatting and documentation generation.", + "allOf": [ + { + "$ref": "#/definitions/clibase.Annotations" + } + ] + }, + "default": { + "description": "Default is parsed into Value if set.", + "type": "string" + }, + "description": { + "type": "string" + }, + "env": { + "description": "Env is the environment variable used to configure this option. If unset,\nenvironment configuring is disabled.", + "type": "string" + }, + "flag": { + "description": "Flag is the long name of the flag used to configure this option. If unset,\nflag configuring is disabled.", + "type": "string" + }, + "flag_shorthand": { + "description": "FlagShorthand is the one-character shorthand for the flag. If unset, no\nshorthand is used.", + "type": "string" + }, + "group": { + "description": "Group is a group hierarchy that helps organize this option in help, configs\nand other documentation.", + "allOf": [ + { + "$ref": "#/definitions/clibase.Group" + } + ] + }, + "hidden": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "use_instead": { + "description": "UseInstead is a list of options that should be used instead of this one.\nThe field is used to generate a deprecation warning.", + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Option" + } + }, + "value": { + "description": "Value includes the types listed in values.go." + }, + "yaml": { + "description": "YAML is the YAML key used to configure this option. If unset, YAML\nconfiguring is disabled.", + "type": "string" + } + } + }, + "clibase.Struct-array_codersdk_GitAuthConfig": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.GitAuthConfig" + } + } + } + }, + "clibase.Struct-array_codersdk_LinkConfig": { + "type": "object", + "properties": { + "value": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.LinkConfig" + } + } + } + }, + "clibase.URL": { + "type": "object", + "properties": { + "forceQuery": { + "description": "append a query ('?') even if RawQuery is empty", + "type": "boolean" + }, + "fragment": { + "description": "fragment for references, without '#'", + "type": "string" + }, + "host": { + "description": "host or host:port", + "type": "string" + }, + "omitHost": { + "description": "do not emit empty host (authority)", + "type": "boolean" + }, + "opaque": { + "description": "encoded opaque data", + "type": "string" + }, + "path": { + "description": "path (relative paths may omit leading slash)", + "type": "string" + }, + "rawFragment": { + "description": "encoded fragment hint (see EscapedFragment method)", + "type": "string" + }, + "rawPath": { + "description": "encoded path hint (see EscapedPath method)", + "type": "string" + }, + "rawQuery": { + "description": "encoded query values, without '?'", + "type": "string" + }, + "scheme": { + "type": "string" + }, + "user": { + "description": "username and password information", + "allOf": [ + { + "$ref": "#/definitions/url.Userinfo" + } + ] + } + } + }, "coderd.SCIMUser": { "type": "object", "properties": { @@ -5507,10 +5678,10 @@ "type": "object", "properties": { "path": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -5529,22 +5700,25 @@ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "region_code": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "region_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "region_name": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "relay_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "stun_addresses": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } } } }, @@ -5552,44 +5726,72 @@ "type": "object", "properties": { "allow_path_app_sharing": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allow_path_app_site_owner_access": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, "codersdk.DeploymentConfig": { + "type": "object", + "properties": { + "config": { + "$ref": "#/definitions/codersdk.DeploymentValues" + }, + "options": { + "type": "array", + "items": { + "$ref": "#/definitions/clibase.Option" + } + } + } + }, + "codersdk.DeploymentDAUsResponse": { + "type": "object", + "properties": { + "entries": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.DAUEntry" + } + } + } + }, + "codersdk.DeploymentValues": { "type": "object", "properties": { "access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "address": { "description": "DEPRECATED: Use HTTPAddress or TLS.Address instead.", "allOf": [ { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" } ] }, "agent_fallback_troubleshooting_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "agent_stat_refresh_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "audit_logging": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "autobuild_poll_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "browser_only": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "cache_directory": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" + }, + "config": { + "type": "string" }, "dangerous": { "$ref": "#/definitions/codersdk.DangerousConfig" @@ -5598,45 +5800,41 @@ "$ref": "#/definitions/codersdk.DERP" }, "disable_password_auth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "disable_path_apps": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "disable_session_expiry_refresh": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" - }, - "experimental": { - "description": "DEPRECATED: Use Experiments instead.", - "allOf": [ - { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" - } - ] + "type": "boolean" }, "experiments": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, - "gitauth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig" + "git_auth": { + "$ref": "#/definitions/clibase.Struct-array_codersdk_GitAuthConfig" }, "http_address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "description": "HTTPAddress is a string because it may be set to zero to disable.", + "type": "string" }, "in_memory_database": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "logging": { "$ref": "#/definitions/codersdk.LoggingConfig" }, "max_session_expiry": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "max_token_lifetime": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "metrics_cache_refresh_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "oauth2": { "$ref": "#/definitions/codersdk.OAuth2Config" @@ -5645,7 +5843,7 @@ "$ref": "#/definitions/codersdk.OIDCConfig" }, "pg_connection_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "pprof": { "$ref": "#/definitions/codersdk.PprofConfig" @@ -5657,31 +5855,40 @@ "$ref": "#/definitions/codersdk.ProvisionerConfig" }, "proxy_trusted_headers": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "proxy_trusted_origins": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "rate_limit": { "$ref": "#/definitions/codersdk.RateLimitConfig" }, "redirect_to_access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "scim_api_key": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "secure_auth_cookie": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "ssh_keygen_algorithm": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "strict_transport_security": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "strict_transport_security_options": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "support": { "$ref": "#/definitions/codersdk.SupportConfig" @@ -5699,263 +5906,16 @@ "$ref": "#/definitions/codersdk.TraceConfig" }, "update_check": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" + }, + "verbose": { + "type": "boolean" }, "wildcard_access_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" - } - } - }, - "codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthConfig" - } + "$ref": "#/definitions/clibase.URL" }, - "enterprise": { + "write_config": { "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.GitAuthConfig" - } - } - } - }, - "codersdk.DeploymentConfigField-array_codersdk_LinkConfig": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.LinkConfig" - } - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.LinkConfig" - } - } - } - }, - "codersdk.DeploymentConfigField-array_string": { - "type": "object", - "properties": { - "default": { - "type": "array", - "items": { - "type": "string" - } - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "codersdk.DeploymentConfigField-bool": { - "type": "object", - "properties": { - "default": { - "type": "boolean" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "boolean" - } - } - }, - "codersdk.DeploymentConfigField-int": { - "type": "object", - "properties": { - "default": { - "type": "integer" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "integer" - } - } - }, - "codersdk.DeploymentConfigField-string": { - "type": "object", - "properties": { - "default": { - "type": "string" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "string" - } - } - }, - "codersdk.DeploymentConfigField-time_Duration": { - "type": "object", - "properties": { - "default": { - "type": "integer" - }, - "enterprise": { - "type": "boolean" - }, - "flag": { - "type": "string" - }, - "hidden": { - "type": "boolean" - }, - "name": { - "type": "string" - }, - "secret": { - "type": "boolean" - }, - "shorthand": { - "type": "string" - }, - "usage": { - "type": "string" - }, - "value": { - "type": "integer" - } - } - }, - "codersdk.DeploymentDAUsResponse": { - "type": "object", - "properties": { - "entries": { - "type": "array", - "items": { - "$ref": "#/definitions/codersdk.DAUEntry" - } } } }, @@ -5977,10 +5937,6 @@ "type": "string" } }, - "experimental": { - "description": "DEPRECATED: use Experiments instead.", - "type": "boolean" - }, "features": { "type": "object", "additionalProperties": { @@ -6213,13 +6169,13 @@ "type": "object", "properties": { "human": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "json": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "stackdriver": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -6267,25 +6223,31 @@ "type": "object", "properties": { "allow_everyone": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allow_signups": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "allowed_orgs": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "allowed_teams": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "client_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_secret": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "enterprise_base_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -6307,34 +6269,40 @@ "type": "object", "properties": { "allow_signups": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "client_id": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_secret": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "email_domain": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "icon_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" }, "ignore_email_verified": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "issuer_url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "scopes": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "sign_in_text": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "username_field": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -6538,10 +6506,10 @@ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -6549,10 +6517,10 @@ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -6560,16 +6528,16 @@ "type": "object", "properties": { "daemon_poll_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "daemon_poll_jitter": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" }, "daemons": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "force_cancel_interval": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration" + "type": "integer" } } }, @@ -6733,10 +6701,10 @@ "type": "object", "properties": { "api": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-int" + "type": "integer" }, "disable_all": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -6849,7 +6817,7 @@ "type": "object", "properties": { "links": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_codersdk_LinkConfig" + "$ref": "#/definitions/clibase.Struct-array_codersdk_LinkConfig" } } }, @@ -6857,7 +6825,7 @@ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -6865,34 +6833,40 @@ "type": "object", "properties": { "address": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.HostPort" }, "cert_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "client_auth": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_ca_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_cert_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "client_key_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "key_file": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-array_string" + "type": "array", + "items": { + "type": "string" + } }, "min_version": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" }, "redirect_http": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" } } }, @@ -6900,13 +6874,13 @@ "type": "object", "properties": { "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "trace": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "url": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "$ref": "#/definitions/clibase.URL" } } }, @@ -7247,13 +7221,13 @@ "type": "object", "properties": { "capture_logs": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "enable": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-bool" + "type": "boolean" }, "honeycomb_api_key": { - "$ref": "#/definitions/codersdk.DeploymentConfigField-string" + "type": "string" } } }, @@ -8199,6 +8173,9 @@ "type": "string" } } + }, + "url.Userinfo": { + "type": "object" } }, "securityDefinitions": { diff --git a/coderd/apikey.go b/coderd/apikey.go index 3977fe8bc9..39b3fef29c 100644 --- a/coderd/apikey.go +++ b/coderd/apikey.go @@ -371,8 +371,11 @@ func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error { return xerrors.New("lifetime must be positive number greater than 0") } - if lifetime > api.DeploymentConfig.MaxTokenLifetime.Value { - return xerrors.Errorf("lifetime must be less than %s", api.DeploymentConfig.MaxTokenLifetime.Value) + if lifetime > api.DeploymentValues.MaxTokenLifetime.Value() { + return xerrors.Errorf( + "lifetime must be less than %v", + api.DeploymentValues.MaxTokenLifetime, + ) } return nil @@ -391,8 +394,8 @@ func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*h if params.LifetimeSeconds != 0 { params.ExpiresAt = database.Now().Add(time.Duration(params.LifetimeSeconds) * time.Second) } else { - params.ExpiresAt = database.Now().Add(api.DeploymentConfig.SessionDuration.Value) - params.LifetimeSeconds = int64(api.DeploymentConfig.SessionDuration.Value.Seconds()) + params.ExpiresAt = database.Now().Add(api.DeploymentValues.SessionDuration.Value()) + params.LifetimeSeconds = int64(api.DeploymentValues.SessionDuration.Value().Seconds()) } } if params.LifetimeSeconds == 0 { diff --git a/coderd/apikey_test.go b/coderd/apikey_test.go index eae8b3e06c..913d5f2551 100644 --- a/coderd/apikey_test.go +++ b/coderd/apikey_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/database" "github.com/coder/coder/coderd/database/dbtestutil" @@ -110,10 +111,10 @@ func TestTokenUserSetMaxLifetime(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - dc := coderdtest.DeploymentConfig(t) - dc.MaxTokenLifetime.Value = time.Hour * 24 * 7 + dc := coderdtest.DeploymentValues(t) + dc.MaxTokenLifetime = clibase.Duration(time.Hour * 24 * 7) client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: dc, + DeploymentValues: dc, }) _ = coderdtest.CreateFirstUser(t, client) @@ -130,41 +131,16 @@ func TestTokenUserSetMaxLifetime(t *testing.T) { require.ErrorContains(t, err, "lifetime must be less") } -func TestTokenDefaultMaxLifetime(t *testing.T) { - t.Parallel() - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - dc := coderdtest.DeploymentConfig(t) - client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: dc, - }) - _ = coderdtest.CreateFirstUser(t, client) - - // success - _, err := client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{ - Lifetime: time.Hour * 24 * 365, - }) - require.NoError(t, err) - - // fail - default --max-token-lifetime is the maximum value of time.Duration - // which is 24 * 365 * 290. - _, err = client.CreateToken(ctx, codersdk.Me, codersdk.CreateTokenRequest{ - Lifetime: time.Hour * 24 * 366 * 290, - }) - require.ErrorContains(t, err, "lifetime must be less") -} - func TestSessionExpiry(t *testing.T) { t.Parallel() ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - dc := coderdtest.DeploymentConfig(t) + dc := coderdtest.DeploymentValues(t) db, pubsub := dbtestutil.NewDB(t) adminClient := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: dc, + DeploymentValues: dc, Database: db, Pubsub: pubsub, }) @@ -176,7 +152,7 @@ func TestSessionExpiry(t *testing.T) { // // We don't support updating the deployment config after startup, but for // this test it works because we don't copy the value (and we use pointers). - dc.SessionDuration.Value = time.Second + dc.SessionDuration = clibase.Duration(time.Second) userClient, _ := coderdtest.CreateAnotherUser(t, adminClient, adminUser.OrganizationID) @@ -185,8 +161,8 @@ func TestSessionExpiry(t *testing.T) { apiKey, err := db.GetAPIKeyByID(ctx, strings.Split(token, "-")[0]) require.NoError(t, err) - require.EqualValues(t, dc.SessionDuration.Value.Seconds(), apiKey.LifetimeSeconds) - require.WithinDuration(t, apiKey.CreatedAt.Add(dc.SessionDuration.Value), apiKey.ExpiresAt, 2*time.Second) + require.EqualValues(t, dc.SessionDuration.Value().Seconds(), apiKey.LifetimeSeconds) + require.WithinDuration(t, apiKey.CreatedAt.Add(dc.SessionDuration.Value()), apiKey.ExpiresAt, 2*time.Second) // Update the session token to be expired so we can test that it is // rejected for extra points. diff --git a/coderd/authorize.go b/coderd/authorize.go index d75cb043bb..0681f38bf9 100644 --- a/coderd/authorize.go +++ b/coderd/authorize.go @@ -60,7 +60,7 @@ func (api *API) Authorize(r *http.Request, action rbac.Action, object rbac.Objec switch object.RBACObject().Type { case rbac.ResourceWorkspaceExecution.Type: // This is not a db resource, always in API layer - case rbac.ResourceDeploymentConfig.Type: + case rbac.ResourceDeploymentValues.Type: // For metric cache items like DAU, we do not hit the DB. // Some db actions are in asserted in the authz layer. case rbac.ResourceReplicas.Type: diff --git a/coderd/coderd.go b/coderd/coderd.go index 0535b00baf..2d80d9d877 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -135,7 +135,7 @@ type Options struct { MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration Experimental bool - DeploymentConfig *codersdk.DeploymentConfig + DeploymentValues *codersdk.DeploymentValues UpdateCheckOptions *updatecheck.Options // Set non-nil to enable update checking. HTTPClient *http.Client @@ -163,7 +163,9 @@ func New(options *Options) *API { if options == nil { options = &Options{} } - experiments := initExperiments(options.Logger, options.DeploymentConfig.Experiments.Value, options.DeploymentConfig.Experimental.Value) + experiments := initExperiments( + options.Logger, options.DeploymentValues.Experiments.Value(), + ) if options.AppHostname != "" && options.AppHostnameRegex == nil || options.AppHostname == "" && options.AppHostnameRegex != nil { panic("coderd: both AppHostname and AppHostnameRegex must be set or unset") } @@ -267,7 +269,7 @@ func New(options *Options) *API { options.AccessURL, options.Authorizer, options.Database, - options.DeploymentConfig, + options.DeploymentValues, oauthConfigs, options.AppSigningKey, ), @@ -292,7 +294,7 @@ func New(options *Options) *API { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: false, - DisableSessionExpiryRefresh: options.DeploymentConfig.DisableSessionExpiryRefresh.Value, + DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), Optional: false, }) // Same as above but it redirects to the login page. @@ -300,7 +302,7 @@ func New(options *Options) *API { DB: options.Database, OAuth2Configs: oauthConfigs, RedirectToLogin: true, - DisableSessionExpiryRefresh: options.DeploymentConfig.DisableSessionExpiryRefresh.Value, + DisableSessionExpiryRefresh: options.DeploymentValues.DisableSessionExpiryRefresh.Value(), Optional: false, }) @@ -397,7 +399,7 @@ func New(options *Options) *API { r.Get("/updatecheck", api.updateCheck) r.Route("/config", func(r chi.Router) { r.Use(apiKeyMiddleware) - r.Get("/deployment", api.deploymentConfig) + r.Get("/deployment", api.deploymentValues) }) r.Route("/audit", func(r chi.Router) { r.Use( @@ -853,7 +855,7 @@ func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, debounce ti } // nolint:revive -func initExperiments(log slog.Logger, raw []string, legacyAll bool) codersdk.Experiments { +func initExperiments(log slog.Logger, raw []string) codersdk.Experiments { exps := make([]codersdk.Experiment, 0, len(raw)) for _, v := range raw { switch v { @@ -867,11 +869,5 @@ func initExperiments(log slog.Logger, raw []string, legacyAll bool) codersdk.Exp exps = append(exps, ex) } } - - // --experiments takes precedence over --experimental. It's deprecated. - if legacyAll && len(raw) == 0 { - log.Warn(context.Background(), "--experimental is deprecated, use --experiments='*' instead") - exps = append(exps, codersdk.ExperimentsAll...) - } return exps } diff --git a/coderd/coderdtest/authorize.go b/coderd/coderdtest/authorize.go index e85dd961d4..4758b1ee46 100644 --- a/coderd/coderdtest/authorize.go +++ b/coderd/coderdtest/authorize.go @@ -792,7 +792,7 @@ func randomRBACType() string { rbac.ResourceOrganizationMember.Type, rbac.ResourceWildcard.Type, rbac.ResourceLicense.Type, - rbac.ResourceDeploymentConfig.Type, + rbac.ResourceDeploymentValues.Type, rbac.ResourceReplicas.Type, rbac.ResourceDebugInfo.Type, } diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 26b0149c80..0b4222763d 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -38,7 +38,6 @@ import ( "github.com/moby/moby/pkg/namesgenerator" "github.com/prometheus/client_golang/prometheus" "github.com/spf13/afero" - "github.com/spf13/pflag" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/oauth2" @@ -53,8 +52,6 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/slogtest" - "github.com/coder/coder/cli/config" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/audit" "github.com/coder/coder/coderd/autobuild/executor" @@ -118,7 +115,7 @@ type Options struct { IncludeProvisionerDaemon bool MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration - DeploymentConfig *codersdk.DeploymentConfig + DeploymentValues *codersdk.DeploymentValues // Set update check options to enable update check. UpdateCheckOptions *updatecheck.Options @@ -196,8 +193,8 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can } options.Database = dbauthz.New(options.Database, options.Authorizer, slogtest.Make(t, nil).Leveled(slog.LevelDebug)) } - if options.DeploymentConfig == nil { - options.DeploymentConfig = DeploymentConfig(t) + if options.DeploymentValues == nil { + options.DeploymentValues = DeploymentValues(t) } // If no ratelimits are set, disable all rate limiting for tests. @@ -332,7 +329,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can }, MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval, AgentStatsRefreshInterval: options.AgentStatsRefreshInterval, - DeploymentConfig: options.DeploymentConfig, + DeploymentValues: options.DeploymentValues, UpdateCheckOptions: options.UpdateCheckOptions, SwaggerEndpoint: options.SwaggerEndpoint, AppSigningKey: AppSigningKey, @@ -1068,12 +1065,10 @@ sz9Di8sGIaUbLZI2rd0CQQCzlVwEtRtoNCyMJTTrkgUuNufLP19RZ5FpyXxBO5/u QastnN77KfUwdj3SJt44U/uh1jAIv4oSLBr8HYUkbnI8 -----END RSA PRIVATE KEY-----` -func DeploymentConfig(t *testing.T) *codersdk.DeploymentConfig { - vip := deployment.NewViper() - fs := pflag.NewFlagSet(randomUsername(), pflag.ContinueOnError) - fs.String(config.FlagName, randomUsername(), randomUsername()) - cfg, err := deployment.Config(fs, vip) +func DeploymentValues(t *testing.T) *codersdk.DeploymentValues { + var cfg codersdk.DeploymentValues + opts := cfg.Options() + err := opts.SetDefaults() require.NoError(t, err) - - return cfg + return &cfg } diff --git a/coderd/database/dbauthz/querier.go b/coderd/database/dbauthz/querier.go index 9e71fd11d0..052863e2ed 100644 --- a/coderd/database/dbauthz/querier.go +++ b/coderd/database/dbauthz/querier.go @@ -287,14 +287,14 @@ func (q *querier) InsertLicense(ctx context.Context, arg database.InsertLicenseP } func (q *querier) InsertOrUpdateLogoURL(ctx context.Context, value string) error { - if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentConfig); err != nil { + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } return q.db.InsertOrUpdateLogoURL(ctx, value) } func (q *querier) InsertOrUpdateServiceBanner(ctx context.Context, value string) error { - if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentConfig); err != nil { + if err := q.authorizeContext(ctx, rbac.ActionCreate, rbac.ResourceDeploymentValues); err != nil { return err } return q.db.InsertOrUpdateServiceBanner(ctx, value) diff --git a/coderd/database/dbauthz/querier_test.go b/coderd/database/dbauthz/querier_test.go index a959329b71..d89a93319d 100644 --- a/coderd/database/dbauthz/querier_test.go +++ b/coderd/database/dbauthz/querier_test.go @@ -313,10 +313,10 @@ func (s *MethodTestSuite) TestLicense() { Asserts(rbac.ResourceLicense, rbac.ActionCreate) })) s.Run("InsertOrUpdateLogoURL", s.Subtest(func(db database.Store, check *expects) { - check.Args("value").Asserts(rbac.ResourceDeploymentConfig, rbac.ActionCreate) + check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) s.Run("InsertOrUpdateServiceBanner", s.Subtest(func(db database.Store, check *expects) { - check.Args("value").Asserts(rbac.ResourceDeploymentConfig, rbac.ActionCreate) + check.Args("value").Asserts(rbac.ResourceDeploymentValues, rbac.ActionCreate) })) s.Run("GetLicenseByID", s.Subtest(func(db database.Store, check *expects) { l, err := db.InsertLicense(context.Background(), database.InsertLicenseParams{ diff --git a/coderd/database/dbauthz/setup_test.go b/coderd/database/dbauthz/setup_test.go index 6ed55c8c9f..578a6d0445 100644 --- a/coderd/database/dbauthz/setup_test.go +++ b/coderd/database/dbauthz/setup_test.go @@ -271,7 +271,7 @@ func splitResp(t *testing.T, values []reflect.Value) ([]reflect.Value, error) { return outputs, err } outputs = append(outputs, r) - } //nolint: unreachable + } t.Fatal("no expected error value found in responses (error can be nil)") return nil, nil // unreachable, required to compile } diff --git a/coderd/deploymentconfig.go b/coderd/deploymentconfig.go index 9b84120d77..c84d915f46 100644 --- a/coderd/deploymentconfig.go +++ b/coderd/deploymentconfig.go @@ -5,6 +5,7 @@ import ( "github.com/coder/coder/coderd/httpapi" "github.com/coder/coder/coderd/rbac" + "github.com/coder/coder/codersdk" ) // @Summary Get deployment config @@ -14,11 +15,23 @@ import ( // @Tags General // @Success 200 {object} codersdk.DeploymentConfig // @Router /config/deployment [get] -func (api *API) deploymentConfig(rw http.ResponseWriter, r *http.Request) { - if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentConfig) { +func (api *API) deploymentValues(rw http.ResponseWriter, r *http.Request) { + if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentValues) { httpapi.Forbidden(rw) return } - httpapi.Write(r.Context(), rw, http.StatusOK, api.DeploymentConfig) + values, err := api.DeploymentValues.WithoutSecrets() + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write( + r.Context(), rw, http.StatusOK, + codersdk.DeploymentConfig{ + Values: values, + Options: values.Options(), + }, + ) } diff --git a/coderd/deploymentconfig_test.go b/coderd/deploymentconfig_test.go index 5389151d2d..a8c2034923 100644 --- a/coderd/deploymentconfig_test.go +++ b/coderd/deploymentconfig_test.go @@ -10,31 +10,31 @@ import ( "github.com/coder/coder/testutil" ) -func TestDeploymentConfig(t *testing.T) { +func TestDeploymentValues(t *testing.T) { t.Parallel() hi := "hi" ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) defer cancel() - cfg := coderdtest.DeploymentConfig(t) + cfg := coderdtest.DeploymentValues(t) // values should be returned - cfg.AccessURL.Value = hi + cfg.BrowserOnly = true // values should not be returned - cfg.OAuth2.Github.ClientSecret.Value = hi - cfg.OIDC.ClientSecret.Value = hi - cfg.PostgresURL.Value = hi - cfg.SCIMAPIKey.Value = hi + cfg.OAuth2.Github.ClientSecret.Set(hi) + cfg.OIDC.ClientSecret.Set(hi) + cfg.PostgresURL.Set(hi) + cfg.SCIMAPIKey.Set(hi) client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) _ = coderdtest.CreateFirstUser(t, client) - scrubbed, err := client.DeploymentConfig(ctx) + scrubbed, err := client.DeploymentValues(ctx) require.NoError(t, err) // ensure normal values pass through - require.EqualValues(t, hi, scrubbed.AccessURL.Value) + require.EqualValues(t, true, scrubbed.Values.BrowserOnly.Value()) // ensure secrets are removed - require.Empty(t, scrubbed.OAuth2.Github.ClientSecret.Value) - require.Empty(t, scrubbed.OIDC.ClientSecret.Value) - require.Empty(t, scrubbed.PostgresURL.Value) - require.Empty(t, scrubbed.SCIMAPIKey.Value) + require.Empty(t, scrubbed.Values.OAuth2.Github.ClientSecret.Value()) + require.Empty(t, scrubbed.Values.OIDC.ClientSecret.Value()) + require.Empty(t, scrubbed.Values.PostgresURL.Value()) + require.Empty(t, scrubbed.Values.SCIMAPIKey.Value()) } diff --git a/coderd/experiments_test.go b/coderd/experiments_test.go index 4b5ddfcfdb..5526d8324d 100644 --- a/coderd/experiments_test.go +++ b/coderd/experiments_test.go @@ -16,9 +16,9 @@ func Test_Experiments(t *testing.T) { t.Parallel() t.Run("empty", func(t *testing.T) { t.Parallel() - cfg := coderdtest.DeploymentConfig(t) + cfg := coderdtest.DeploymentValues(t) client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) _ = coderdtest.CreateFirstUser(t, client) @@ -34,10 +34,10 @@ func Test_Experiments(t *testing.T) { t.Run("multiple features", func(t *testing.T) { t.Parallel() - cfg := coderdtest.DeploymentConfig(t) - cfg.Experiments.Value = []string{"foo", "BAR"} + cfg := coderdtest.DeploymentValues(t) + cfg.Experiments = []string{"foo", "BAR"} client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) _ = coderdtest.CreateFirstUser(t, client) @@ -56,10 +56,10 @@ func Test_Experiments(t *testing.T) { t.Run("wildcard", func(t *testing.T) { t.Parallel() - cfg := coderdtest.DeploymentConfig(t) - cfg.Experiments.Value = []string{"*"} + cfg := coderdtest.DeploymentValues(t) + cfg.Experiments = []string{"*"} client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) _ = coderdtest.CreateFirstUser(t, client) @@ -78,10 +78,10 @@ func Test_Experiments(t *testing.T) { t.Run("alternate wildcard with manual opt-in", func(t *testing.T) { t.Parallel() - cfg := coderdtest.DeploymentConfig(t) - cfg.Experiments.Value = []string{"*", "dAnGeR"} + cfg := coderdtest.DeploymentValues(t) + cfg.Experiments = []string{"*", "dAnGeR"} client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) _ = coderdtest.CreateFirstUser(t, client) @@ -99,34 +99,12 @@ func Test_Experiments(t *testing.T) { require.False(t, experiments.Enabled("herebedragons")) }) - t.Run("legacy wildcard", func(t *testing.T) { - t.Parallel() - cfg := coderdtest.DeploymentConfig(t) - cfg.Experimental.Value = true - client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, - }) - _ = coderdtest.CreateFirstUser(t, client) - - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - experiments, err := client.Experiments(ctx) - require.NoError(t, err) - require.NotNil(t, experiments) - require.ElementsMatch(t, codersdk.ExperimentsAll, experiments) - for _, ex := range codersdk.ExperimentsAll { - require.True(t, experiments.Enabled(ex)) - } - require.False(t, experiments.Enabled("danger")) - }) - t.Run("Unauthorized", func(t *testing.T) { t.Parallel() - cfg := coderdtest.DeploymentConfig(t) - cfg.Experiments.Value = []string{"*"} + cfg := coderdtest.DeploymentValues(t) + cfg.Experiments = []string{"*"} client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }) // Explicitly omit creating a user so we're unauthorized. // _ = coderdtest.CreateFirstUser(t, client) diff --git a/coderd/httpapi/httpapi.go b/coderd/httpapi/httpapi.go index e0f510b372..467b93fc93 100644 --- a/coderd/httpapi/httpapi.go +++ b/coderd/httpapi/httpapi.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "flag" "fmt" "net/http" "reflect" @@ -114,6 +115,10 @@ func Write(ctx context.Context, rw http.ResponseWriter, status int, response int buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(true) + // Pretty up JSON when testing. + if flag.Lookup("test.v") != nil { + enc.SetIndent("", "\t") + } err := enc.Encode(response) if err != nil { http.Error(rw, err.Error(), http.StatusInternalServerError) diff --git a/coderd/insights.go b/coderd/insights.go index 303de2f065..79cf14210e 100644 --- a/coderd/insights.go +++ b/coderd/insights.go @@ -17,7 +17,7 @@ import ( // @Router /insights/daus [get] func (api *API) deploymentDAUs(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentConfig) { + if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentValues) { httpapi.Forbidden(rw) return } diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 7d83d3f905..baf542ae35 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -265,7 +265,10 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, } } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading job agent.", diff --git a/coderd/rbac/cache.go b/coderd/rbac/cache.go index 1bd2bb5325..6095f8b3d6 100644 --- a/coderd/rbac/cache.go +++ b/coderd/rbac/cache.go @@ -59,7 +59,7 @@ type authorizeCache struct { calls []cachedAuthCall } -//nolint:error-return,revive +//nolint:revive func (c *authorizeCache) Load(subject Subject, action Action, object Object) (error, bool) { if c == nil { return nil, false diff --git a/coderd/rbac/object.go b/coderd/rbac/object.go index 8f71ee3419..ca8a57245e 100644 --- a/coderd/rbac/object.go +++ b/coderd/rbac/object.go @@ -142,8 +142,8 @@ var ( Type: "license", } - // ResourceDeploymentConfig - ResourceDeploymentConfig = Object{ + // ResourceDeploymentValues + ResourceDeploymentValues = Object{ Type: "deployment_config", } diff --git a/coderd/userauth.go b/coderd/userauth.go index 55f065ae1f..7fab1df83f 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -89,7 +89,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { // If password authentication is disabled and the user does not have the // owner role, block the request. - if api.DeploymentConfig.DisablePasswordAuth.Value { + if api.DeploymentValues.DisablePasswordAuth { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "Password authentication is disabled.", }) @@ -285,7 +285,7 @@ func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) { httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.AuthMethods{ Password: codersdk.AuthMethod{ - Enabled: !api.DeploymentConfig.DisablePasswordAuth.Value, + Enabled: !api.DeploymentValues.DisablePasswordAuth.Value(), }, Github: codersdk.AuthMethod{Enabled: api.GithubOAuth2Config != nil}, OIDC: codersdk.OIDCAuthMethod{ diff --git a/coderd/users.go b/coderd/users.go index 31c548455b..69571eb32d 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -298,7 +298,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { // If password auth is disabled, don't allow new users to be // created with a password! - if api.DeploymentConfig.DisablePasswordAuth.Value { + if api.DeploymentValues.DisablePasswordAuth { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "You cannot manually provision new users with password authentication disabled!", }) diff --git a/coderd/users_test.go b/coderd/users_test.go index a5916be338..5e20f78bfb 100644 --- a/coderd/users_test.go +++ b/coderd/users_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/coderd/audit" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/database" @@ -186,9 +187,9 @@ func TestPostLogin(t *testing.T) { t.Run("DisabledPasswordAuth", func(t *testing.T) { t.Parallel() - dc := coderdtest.DeploymentConfig(t) + dc := coderdtest.DeploymentValues(t) client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: dc, + DeploymentValues: dc, }) first := coderdtest.CreateFirstUser(t, client) @@ -206,7 +207,7 @@ func TestPostLogin(t *testing.T) { }) require.NoError(t, err) - dc.DisablePasswordAuth.Value = true + dc.DisablePasswordAuth = clibase.Bool(true) userClient := codersdk.New(client.URL) _, err = userClient.LoginWithPassword(ctx, codersdk.LoginWithPasswordRequest{ diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 9fd6f143c7..2ec8391593 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -61,7 +61,10 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { }) return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -83,7 +86,10 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -171,7 +177,10 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) func (api *API) postWorkspaceAgentStartup(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -234,7 +243,11 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { httpapi.ResourceNotFound(rw) return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -315,7 +328,10 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index 92bd2d8ac8..ce43788b79 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -84,7 +84,7 @@ func (api *API) appHost(rw http.ResponseWriter, r *http.Request) { // workspaceAppsProxyPath proxies requests to a workspace application // through a relative URL path. func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) { - if api.DeploymentConfig.DisablePathApps.Value { + if api.DeploymentValues.DisablePathApps.Value() { site.RenderStaticErrorPage(rw, r, site.ErrorPageData{ Status: http.StatusUnauthorized, Title: "Unauthorized", @@ -106,7 +106,7 @@ func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) OIDC: api.OIDCConfig, }, RedirectToLogin: true, - DisableSessionExpiryRefresh: api.DeploymentConfig.DisableSessionExpiryRefresh.Value, + DisableSessionExpiryRefresh: api.DeploymentValues.DisableSessionExpiryRefresh.Value(), }) if !ok { return @@ -340,9 +340,9 @@ func (api *API) workspaceApplicationAuth(rw http.ResponseWriter, r *http.Request // the current session. exp := apiKey.ExpiresAt lifetimeSeconds := apiKey.LifetimeSeconds - if exp.IsZero() || time.Until(exp) > api.DeploymentConfig.SessionDuration.Value { - exp = database.Now().Add(api.DeploymentConfig.SessionDuration.Value) - lifetimeSeconds = int64(api.DeploymentConfig.SessionDuration.Value.Seconds()) + if exp.IsZero() || time.Until(exp) > api.DeploymentValues.SessionDuration.Value() { + exp = database.Now().Add(api.DeploymentValues.SessionDuration.Value()) + lifetimeSeconds = int64(api.DeploymentValues.SessionDuration.Value().Seconds()) } cookie, _, err := api.createAPIKey(ctx, createAPIKeyParams{ UserID: apiKey.UserID, diff --git a/coderd/workspaceapps/auth.go b/coderd/workspaceapps/auth.go index 425f9245ba..66bd8cf8b7 100644 --- a/coderd/workspaceapps/auth.go +++ b/coderd/workspaceapps/auth.go @@ -91,7 +91,7 @@ func (p *Provider) ResolveRequest(rw http.ResponseWriter, r *http.Request, appRe DB: p.Database, OAuth2Configs: p.OAuth2Configs, RedirectToLogin: false, - DisableSessionExpiryRefresh: p.DeploymentConfig.DisableSessionExpiryRefresh.Value, + DisableSessionExpiryRefresh: p.DeploymentValues.DisableSessionExpiryRefresh.Value(), // Optional is true to allow for public apps. If an authorization check // fails and the user is not authenticated, they will be redirected to // the login page using code below (not the redirect from the @@ -358,7 +358,7 @@ func (p *Provider) authorizeWorkspaceApp(ctx context.Context, roles *httpmw.Auth // // Site owners are blocked from accessing path-based apps unless the // Dangerous.AllowPathAppSiteOwnerAccess flag is enabled in the check below. - if isPathApp && !p.DeploymentConfig.Dangerous.AllowPathAppSharing.Value { + if isPathApp && !p.DeploymentValues.Dangerous.AllowPathAppSharing.Value() { sharingLevel = database.AppSharingLevelOwner } @@ -379,7 +379,7 @@ func (p *Provider) authorizeWorkspaceApp(ctx context.Context, roles *httpmw.Auth if isPathApp && sharingLevel == database.AppSharingLevelOwner && workspace.OwnerID.String() != roles.Actor.ID && - !p.DeploymentConfig.Dangerous.AllowPathAppSiteOwnerAccess.Value { + !p.DeploymentValues.Dangerous.AllowPathAppSiteOwnerAccess.Value() { return false, nil } diff --git a/coderd/workspaceapps/auth_test.go b/coderd/workspaceapps/auth_test.go index 41c1657545..e0a092b85e 100644 --- a/coderd/workspaceapps/auth_test.go +++ b/coderd/workspaceapps/auth_test.go @@ -43,13 +43,13 @@ func Test_ResolveRequest(t *testing.T) { ) allApps := []string{appNameOwner, appNameAuthed, appNamePublic} - deploymentConfig := coderdtest.DeploymentConfig(t) - deploymentConfig.DisablePathApps.Value = false - deploymentConfig.Dangerous.AllowPathAppSharing.Value = true - deploymentConfig.Dangerous.AllowPathAppSiteOwnerAccess.Value = true + deploymentValues := coderdtest.DeploymentValues(t) + deploymentValues.DisablePathApps = false + deploymentValues.Dangerous.AllowPathAppSharing = true + deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = true client, closer, api := coderdtest.NewWithAPI(t, &coderdtest.Options{ - DeploymentConfig: deploymentConfig, + DeploymentValues: deploymentValues, IncludeProvisionerDaemon: true, AgentStatsRefreshInterval: time.Millisecond * 100, MetricsCacheRefreshInterval: time.Millisecond * 100, diff --git a/coderd/workspaceapps/provider.go b/coderd/workspaceapps/provider.go index 7e0089015f..e85e1a893c 100644 --- a/coderd/workspaceapps/provider.go +++ b/coderd/workspaceapps/provider.go @@ -19,12 +19,12 @@ type Provider struct { AccessURL *url.URL Authorizer rbac.Authorizer Database database.Store - DeploymentConfig *codersdk.DeploymentConfig + DeploymentValues *codersdk.DeploymentValues OAuth2Configs *httpmw.OAuth2Configs TicketSigningKey []byte } -func New(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database.Store, cfg *codersdk.DeploymentConfig, oauth2Cfgs *httpmw.OAuth2Configs, ticketSigningKey []byte) *Provider { +func New(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database.Store, cfg *codersdk.DeploymentValues, oauth2Cfgs *httpmw.OAuth2Configs, ticketSigningKey []byte) *Provider { if len(ticketSigningKey) != 64 { panic("ticket signing key must be 64 bytes") } @@ -34,7 +34,7 @@ func New(log slog.Logger, accessURL *url.URL, authz rbac.Authorizer, db database AccessURL: accessURL, Authorizer: authz, Database: db, - DeploymentConfig: cfg, + DeploymentValues: cfg, OAuth2Configs: oauth2Cfgs, TicketSigningKey: ticketSigningKey, } diff --git a/coderd/workspaceapps_test.go b/coderd/workspaceapps_test.go index 4051cd8b95..a248f14d2f 100644 --- a/coderd/workspaceapps_test.go +++ b/coderd/workspaceapps_test.go @@ -23,6 +23,7 @@ import ( "cdr.dev/slog/sloggers/slogtest" "github.com/coder/coder/agent" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/coderd/httpapi" @@ -168,13 +169,13 @@ func setupProxyTest(t *testing.T, opts *setupProxyTestOpts) (*codersdk.Client, c }) go server.Serve(ln) - deploymentConfig := coderdtest.DeploymentConfig(t) - deploymentConfig.DisablePathApps.Value = opts.DisablePathApps - deploymentConfig.Dangerous.AllowPathAppSharing.Value = opts.DangerousAllowPathAppSharing - deploymentConfig.Dangerous.AllowPathAppSiteOwnerAccess.Value = opts.DangerousAllowPathAppSiteOwnerAccess + deploymentValues := coderdtest.DeploymentValues(t) + deploymentValues.DisablePathApps = clibase.Bool(opts.DisablePathApps) + deploymentValues.Dangerous.AllowPathAppSharing = clibase.Bool(opts.DangerousAllowPathAppSharing) + deploymentValues.Dangerous.AllowPathAppSiteOwnerAccess = clibase.Bool(opts.DangerousAllowPathAppSiteOwnerAccess) client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: deploymentConfig, + DeploymentValues: deploymentValues, AppHostname: opts.AppHost, IncludeProvisionerDaemon: true, AgentStatsRefreshInterval: time.Millisecond * 100, @@ -307,11 +308,11 @@ func TestWorkspaceAppsProxyPath(t *testing.T) { t.Run("Disabled", func(t *testing.T) { t.Parallel() - deploymentConfig := coderdtest.DeploymentConfig(t) - deploymentConfig.DisablePathApps.Value = true + deploymentValues := coderdtest.DeploymentValues(t) + deploymentValues.DisablePathApps = true client := coderdtest.New(t, &coderdtest.Options{ - DeploymentConfig: deploymentConfig, + DeploymentValues: deploymentValues, IncludeProvisionerDaemon: true, AgentStatsRefreshInterval: time.Millisecond * 100, MetricsCacheRefreshInterval: time.Millisecond * 100, @@ -1435,11 +1436,11 @@ func TestAppSharing(t *testing.T) { siteOwnerCanAccess := !isPathApp || siteOwnerPathAppAccessEnabled siteOwnerCanAccessShared := siteOwnerCanAccess || pathAppSharingEnabled - deploymentConfig, err := ownerClient.DeploymentConfig(context.Background()) + deploymentValues, err := ownerClient.DeploymentValues(context.Background()) require.NoError(t, err) - assert.Equal(t, pathAppSharingEnabled, deploymentConfig.Dangerous.AllowPathAppSharing.Value) - assert.Equal(t, siteOwnerPathAppAccessEnabled, deploymentConfig.Dangerous.AllowPathAppSiteOwnerAccess.Value) + assert.Equal(t, pathAppSharingEnabled, deploymentValues.Values.Dangerous.AllowPathAppSharing.Value()) + assert.Equal(t, siteOwnerPathAppAccessEnabled, deploymentValues.Values.Dangerous.AllowPathAppSiteOwnerAccess.Value()) t.Run("LevelOwner", func(t *testing.T) { t.Parallel() diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index ea6dfd17ae..064d966941 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -1122,7 +1122,10 @@ func (api *API) convertWorkspaceBuild( apiAgents := make([]codersdk.WorkspaceAgent, 0) for _, agent := range agents { apps := appsByAgentID[agent.ID] - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(apps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) + apiAgent, err := convertWorkspaceAgent( + api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(apps), api.AgentInactiveDisconnectTimeout, + api.DeploymentValues.AgentFallbackTroubleshootingURL.String(), + ) if err != nil { return codersdk.WorkspaceBuild{}, xerrors.Errorf("converting workspace agent: %w", err) } diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 4f5501c290..8b52f6457d 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -3,12 +3,23 @@ package codersdk import ( "context" "encoding/json" + "flag" + "io" + "math" "net/http" + "os" + "path/filepath" + "strconv" "strings" "time" "golang.org/x/mod/semver" "golang.org/x/xerrors" + + "github.com/coreos/go-oidc/v3/oidc" + + "github.com/coder/coder/buildinfo" + "github.com/coder/coder/cli/clibase" ) // Entitlement represents whether a feature is licensed. @@ -89,9 +100,6 @@ type Entitlements struct { HasLicense bool `json:"has_license"` Trial bool `json:"trial"` RequireTelemetry bool `json:"require_telemetry"` - - // DEPRECATED: use Experiments instead. - Experimental bool `json:"experimental"` } func (c *Client) Entitlements(ctx context.Context) (Entitlements, error) { @@ -107,137 +115,139 @@ func (c *Client) Entitlements(ctx context.Context) (Entitlements, error) { return ent, json.NewDecoder(res.Body).Decode(&ent) } -// DeploymentConfig is the central configuration for the coder server. -type DeploymentConfig struct { - AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` - WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` - RedirectToAccessURL *DeploymentConfigField[bool] `json:"redirect_to_access_url" typescript:",notnull"` - HTTPAddress *DeploymentConfigField[string] `json:"http_address" typescript:",notnull"` - AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` - DERP *DERP `json:"derp" typescript:",notnull"` - GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` - Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` - Pprof *PprofConfig `json:"pprof" typescript:",notnull"` - ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` - ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` - CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` - InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` - PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` - OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` - OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` - Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` - TLS *TLSConfig `json:"tls" typescript:",notnull"` - Trace *TraceConfig `json:"trace" typescript:",notnull"` - SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` - StrictTransportSecurity *DeploymentConfigField[int] `json:"strict_transport_security" typescript:",notnull"` - StrictTransportSecurityOptions *DeploymentConfigField[[]string] `json:"strict_transport_security_options" typescript:",notnull"` - SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` - MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` - AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` - AgentFallbackTroubleshootingURL *DeploymentConfigField[string] `json:"agent_fallback_troubleshooting_url" typescript:",notnull"` - AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` - BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` - SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` - Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"` - RateLimit *RateLimitConfig `json:"rate_limit" typescript:",notnull"` - Experiments *DeploymentConfigField[[]string] `json:"experiments" typescript:",notnull"` - UpdateCheck *DeploymentConfigField[bool] `json:"update_check" typescript:",notnull"` - MaxTokenLifetime *DeploymentConfigField[time.Duration] `json:"max_token_lifetime" typescript:",notnull"` - Swagger *SwaggerConfig `json:"swagger" typescript:",notnull"` - Logging *LoggingConfig `json:"logging" typescript:",notnull"` - Dangerous *DangerousConfig `json:"dangerous" typescript:",notnull"` - DisablePathApps *DeploymentConfigField[bool] `json:"disable_path_apps" typescript:",notnull"` - SessionDuration *DeploymentConfigField[time.Duration] `json:"max_session_expiry" typescript:",notnull"` - DisableSessionExpiryRefresh *DeploymentConfigField[bool] `json:"disable_session_expiry_refresh" typescript:",notnull"` - DisablePasswordAuth *DeploymentConfigField[bool] `json:"disable_password_auth" typescript:",notnull"` +// DeploymentValues is the central configuration values the coder server. +type DeploymentValues struct { + Verbose clibase.Bool `json:"verbose,omitempty"` + AccessURL clibase.URL `json:"access_url,omitempty"` + WildcardAccessURL clibase.URL `json:"wildcard_access_url,omitempty"` + RedirectToAccessURL clibase.Bool `json:"redirect_to_access_url,omitempty"` + // HTTPAddress is a string because it may be set to zero to disable. + HTTPAddress clibase.String `json:"http_address,omitempty" typescript:",notnull"` + AutobuildPollInterval clibase.Duration `json:"autobuild_poll_interval,omitempty"` + DERP DERP `json:"derp,omitempty" typescript:",notnull"` + Prometheus PrometheusConfig `json:"prometheus,omitempty" typescript:",notnull"` + Pprof PprofConfig `json:"pprof,omitempty" typescript:",notnull"` + ProxyTrustedHeaders clibase.Strings `json:"proxy_trusted_headers,omitempty" typescript:",notnull"` + ProxyTrustedOrigins clibase.Strings `json:"proxy_trusted_origins,omitempty" typescript:",notnull"` + CacheDir clibase.String `json:"cache_directory,omitempty" typescript:",notnull"` + InMemoryDatabase clibase.Bool `json:"in_memory_database,omitempty" typescript:",notnull"` + PostgresURL clibase.String `json:"pg_connection_url,omitempty" typescript:",notnull"` + OAuth2 OAuth2Config `json:"oauth2,omitempty" typescript:",notnull"` + OIDC OIDCConfig `json:"oidc,omitempty" typescript:",notnull"` + Telemetry TelemetryConfig `json:"telemetry,omitempty" typescript:",notnull"` + TLS TLSConfig `json:"tls,omitempty" typescript:",notnull"` + Trace TraceConfig `json:"trace,omitempty" typescript:",notnull"` + SecureAuthCookie clibase.Bool `json:"secure_auth_cookie,omitempty" typescript:",notnull"` + StrictTransportSecurity clibase.Int64 `json:"strict_transport_security,omitempty" typescript:",notnull"` + StrictTransportSecurityOptions clibase.Strings `json:"strict_transport_security_options,omitempty" typescript:",notnull"` + SSHKeygenAlgorithm clibase.String `json:"ssh_keygen_algorithm,omitempty" typescript:",notnull"` + MetricsCacheRefreshInterval clibase.Duration `json:"metrics_cache_refresh_interval,omitempty" typescript:",notnull"` + AgentStatRefreshInterval clibase.Duration `json:"agent_stat_refresh_interval,omitempty" typescript:",notnull"` + AgentFallbackTroubleshootingURL clibase.URL `json:"agent_fallback_troubleshooting_url,omitempty" typescript:",notnull"` + AuditLogging clibase.Bool `json:"audit_logging,omitempty" typescript:",notnull"` + BrowserOnly clibase.Bool `json:"browser_only,omitempty" typescript:",notnull"` + SCIMAPIKey clibase.String `json:"scim_api_key,omitempty" typescript:",notnull"` + Provisioner ProvisionerConfig `json:"provisioner,omitempty" typescript:",notnull"` + RateLimit RateLimitConfig `json:"rate_limit,omitempty" typescript:",notnull"` + Experiments clibase.Strings `json:"experiments,omitempty" typescript:",notnull"` + UpdateCheck clibase.Bool `json:"update_check,omitempty" typescript:",notnull"` + MaxTokenLifetime clibase.Duration `json:"max_token_lifetime,omitempty" typescript:",notnull"` + Swagger SwaggerConfig `json:"swagger,omitempty" typescript:",notnull"` + Logging LoggingConfig `json:"logging,omitempty" typescript:",notnull"` + Dangerous DangerousConfig `json:"dangerous,omitempty" typescript:",notnull"` + DisablePathApps clibase.Bool `json:"disable_path_apps,omitempty" typescript:",notnull"` + SessionDuration clibase.Duration `json:"max_session_expiry,omitempty" typescript:",notnull"` + DisableSessionExpiryRefresh clibase.Bool `json:"disable_session_expiry_refresh,omitempty" typescript:",notnull"` + DisablePasswordAuth clibase.Bool `json:"disable_password_auth,omitempty" typescript:",notnull"` + Support SupportConfig `json:"support,omitempty" typescript:",notnull"` + GitAuthProviders clibase.Struct[[]GitAuthConfig] `json:"git_auth,omitempty" typescript:",notnull"` + + Config clibase.String `json:"config,omitempty" typescript:",notnull"` + WriteConfig clibase.Bool `json:"write_config,omitempty" typescript:",notnull"` // DEPRECATED: Use HTTPAddress or TLS.Address instead. - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` - // DEPRECATED: Use Experiments instead. - Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"` - - Support *SupportConfig `json:"support" typescript:",notnull"` + Address clibase.HostPort `json:"address,omitempty" typescript:",notnull"` } type DERP struct { - Server *DERPServerConfig `json:"server" typescript:",notnull"` - Config *DERPConfig `json:"config" typescript:",notnull"` + Server DERPServerConfig `json:"server" typescript:",notnull"` + Config DERPConfig `json:"config" typescript:",notnull"` } type DERPServerConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - RegionID *DeploymentConfigField[int] `json:"region_id" typescript:",notnull"` - RegionCode *DeploymentConfigField[string] `json:"region_code" typescript:",notnull"` - RegionName *DeploymentConfigField[string] `json:"region_name" typescript:",notnull"` - STUNAddresses *DeploymentConfigField[[]string] `json:"stun_addresses" typescript:",notnull"` - RelayURL *DeploymentConfigField[string] `json:"relay_url" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + RegionID clibase.Int64 `json:"region_id" typescript:",notnull"` + RegionCode clibase.String `json:"region_code" typescript:",notnull"` + RegionName clibase.String `json:"region_name" typescript:",notnull"` + STUNAddresses clibase.Strings `json:"stun_addresses" typescript:",notnull"` + RelayURL clibase.URL `json:"relay_url" typescript:",notnull"` } type DERPConfig struct { - URL *DeploymentConfigField[string] `json:"url" typescript:",notnull"` - Path *DeploymentConfigField[string] `json:"path" typescript:",notnull"` + URL clibase.String `json:"url" typescript:",notnull"` + Path clibase.String `json:"path" typescript:",notnull"` } type PrometheusConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + Address clibase.HostPort `json:"address" typescript:",notnull"` } type PprofConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + Address clibase.HostPort `json:"address" typescript:",notnull"` } type OAuth2Config struct { - Github *OAuth2GithubConfig `json:"github" typescript:",notnull"` + Github OAuth2GithubConfig `json:"github" typescript:",notnull"` } type OAuth2GithubConfig struct { - ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"` - ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"` - AllowedOrgs *DeploymentConfigField[[]string] `json:"allowed_orgs" typescript:",notnull"` - AllowedTeams *DeploymentConfigField[[]string] `json:"allowed_teams" typescript:",notnull"` - AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"` - AllowEveryone *DeploymentConfigField[bool] `json:"allow_everyone" typescript:",notnull"` - EnterpriseBaseURL *DeploymentConfigField[string] `json:"enterprise_base_url" typescript:",notnull"` + ClientID clibase.String `json:"client_id" typescript:",notnull"` + ClientSecret clibase.String `json:"client_secret" typescript:",notnull"` + AllowedOrgs clibase.Strings `json:"allowed_orgs" typescript:",notnull"` + AllowedTeams clibase.Strings `json:"allowed_teams" typescript:",notnull"` + AllowSignups clibase.Bool `json:"allow_signups" typescript:",notnull"` + AllowEveryone clibase.Bool `json:"allow_everyone" typescript:",notnull"` + EnterpriseBaseURL clibase.String `json:"enterprise_base_url" typescript:",notnull"` } type OIDCConfig struct { - AllowSignups *DeploymentConfigField[bool] `json:"allow_signups" typescript:",notnull"` - ClientID *DeploymentConfigField[string] `json:"client_id" typescript:",notnull"` - ClientSecret *DeploymentConfigField[string] `json:"client_secret" typescript:",notnull"` - EmailDomain *DeploymentConfigField[[]string] `json:"email_domain" typescript:",notnull"` - IssuerURL *DeploymentConfigField[string] `json:"issuer_url" typescript:",notnull"` - Scopes *DeploymentConfigField[[]string] `json:"scopes" typescript:",notnull"` - IgnoreEmailVerified *DeploymentConfigField[bool] `json:"ignore_email_verified" typescript:",notnull"` - UsernameField *DeploymentConfigField[string] `json:"username_field" typescript:",notnull"` - SignInText *DeploymentConfigField[string] `json:"sign_in_text" typescript:",notnull"` - IconURL *DeploymentConfigField[string] `json:"icon_url" typescript:",notnull"` + AllowSignups clibase.Bool `json:"allow_signups" typescript:",notnull"` + ClientID clibase.String `json:"client_id" typescript:",notnull"` + ClientSecret clibase.String `json:"client_secret" typescript:",notnull"` + EmailDomain clibase.Strings `json:"email_domain" typescript:",notnull"` + IssuerURL clibase.String `json:"issuer_url" typescript:",notnull"` + Scopes clibase.Strings `json:"scopes" typescript:",notnull"` + IgnoreEmailVerified clibase.Bool `json:"ignore_email_verified" typescript:",notnull"` + UsernameField clibase.String `json:"username_field" typescript:",notnull"` + SignInText clibase.String `json:"sign_in_text" typescript:",notnull"` + IconURL clibase.URL `json:"icon_url" typescript:",notnull"` } type TelemetryConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - Trace *DeploymentConfigField[bool] `json:"trace" typescript:",notnull"` - URL *DeploymentConfigField[string] `json:"url" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + Trace clibase.Bool `json:"trace" typescript:",notnull"` + URL clibase.URL `json:"url" typescript:",notnull"` } type TLSConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` - RedirectHTTP *DeploymentConfigField[bool] `json:"redirect_http" typescript:",notnull"` - CertFiles *DeploymentConfigField[[]string] `json:"cert_file" typescript:",notnull"` - ClientAuth *DeploymentConfigField[string] `json:"client_auth" typescript:",notnull"` - ClientCAFile *DeploymentConfigField[string] `json:"client_ca_file" typescript:",notnull"` - KeyFiles *DeploymentConfigField[[]string] `json:"key_file" typescript:",notnull"` - MinVersion *DeploymentConfigField[string] `json:"min_version" typescript:",notnull"` - ClientCertFile *DeploymentConfigField[string] `json:"client_cert_file" typescript:",notnull"` - ClientKeyFile *DeploymentConfigField[string] `json:"client_key_file" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + Address clibase.HostPort `json:"address" typescript:",notnull"` + RedirectHTTP clibase.Bool `json:"redirect_http" typescript:",notnull"` + CertFiles clibase.Strings `json:"cert_file" typescript:",notnull"` + ClientAuth clibase.String `json:"client_auth" typescript:",notnull"` + ClientCAFile clibase.String `json:"client_ca_file" typescript:",notnull"` + KeyFiles clibase.Strings `json:"key_file" typescript:",notnull"` + MinVersion clibase.String `json:"min_version" typescript:",notnull"` + ClientCertFile clibase.String `json:"client_cert_file" typescript:",notnull"` + ClientKeyFile clibase.String `json:"client_key_file" typescript:",notnull"` } type TraceConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` - HoneycombAPIKey *DeploymentConfigField[string] `json:"honeycomb_api_key" typescript:",notnull"` - CaptureLogs *DeploymentConfigField[bool] `json:"capture_logs" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` + HoneycombAPIKey clibase.String `json:"honeycomb_api_key" typescript:",notnull"` + CaptureLogs clibase.Bool `json:"capture_logs" typescript:",notnull"` } type GitAuthConfig struct { @@ -254,109 +264,1091 @@ type GitAuthConfig struct { } type ProvisionerConfig struct { - Daemons *DeploymentConfigField[int] `json:"daemons" typescript:",notnull"` - DaemonPollInterval *DeploymentConfigField[time.Duration] `json:"daemon_poll_interval" typescript:",notnull"` - DaemonPollJitter *DeploymentConfigField[time.Duration] `json:"daemon_poll_jitter" typescript:",notnull"` - ForceCancelInterval *DeploymentConfigField[time.Duration] `json:"force_cancel_interval" typescript:",notnull"` + Daemons clibase.Int64 `json:"daemons" typescript:",notnull"` + DaemonPollInterval clibase.Duration `json:"daemon_poll_interval" typescript:",notnull"` + DaemonPollJitter clibase.Duration `json:"daemon_poll_jitter" typescript:",notnull"` + ForceCancelInterval clibase.Duration `json:"force_cancel_interval" typescript:",notnull"` } type RateLimitConfig struct { - DisableAll *DeploymentConfigField[bool] `json:"disable_all" typescript:",notnull"` - API *DeploymentConfigField[int] `json:"api" typescript:",notnull"` + DisableAll clibase.Bool `json:"disable_all" typescript:",notnull"` + API clibase.Int64 `json:"api" typescript:",notnull"` } type SwaggerConfig struct { - Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"` + Enable clibase.Bool `json:"enable" typescript:",notnull"` } type LoggingConfig struct { - Human *DeploymentConfigField[string] `json:"human" typescript:",notnull"` - JSON *DeploymentConfigField[string] `json:"json" typescript:",notnull"` - Stackdriver *DeploymentConfigField[string] `json:"stackdriver" typescript:",notnull"` + Human clibase.String `json:"human" typescript:",notnull"` + JSON clibase.String `json:"json" typescript:",notnull"` + Stackdriver clibase.String `json:"stackdriver" typescript:",notnull"` } type DangerousConfig struct { - AllowPathAppSharing *DeploymentConfigField[bool] `json:"allow_path_app_sharing" typescript:",notnull"` - AllowPathAppSiteOwnerAccess *DeploymentConfigField[bool] `json:"allow_path_app_site_owner_access" typescript:",notnull"` + AllowPathAppSharing clibase.Bool `json:"allow_path_app_sharing" typescript:",notnull"` + AllowPathAppSiteOwnerAccess clibase.Bool `json:"allow_path_app_site_owner_access" typescript:",notnull"` +} + +const ( + flagEnterpriseKey = "enterprise" + flagSecretKey = "secret" +) + +func IsSecretDeploymentOption(opt clibase.Option) bool { + return opt.Annotations.IsSet(flagSecretKey) +} + +func DefaultCacheDir() string { + defaultCacheDir, err := os.UserCacheDir() + if err != nil { + defaultCacheDir = os.TempDir() + } + if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" { + // For compatibility with systemd. + defaultCacheDir = dir + } + + return filepath.Join(defaultCacheDir, "coder") +} + +// The DeploymentGroup variables are used to organize the myriad server options. +var ( + DeploymentGroupNetworking = clibase.Group{ + Name: "Networking", + } + DeploymentGroupNetworkingTLS = clibase.Group{ + Parent: &DeploymentGroupNetworking, + Name: "TLS", + Description: `Configure TLS / HTTPS for your Coder deployment. If you're running + Coder behind a TLS-terminating reverse proxy or are accessing Coder over a + secure link, you can safely ignore these settings.`, + } + DeploymentGroupNetworkingHTTP = clibase.Group{ + Parent: &DeploymentGroupNetworking, + Name: "HTTP", + } + DeploymentGroupNetworkingDERP = clibase.Group{ + Parent: &DeploymentGroupNetworking, + Name: "DERP", + Description: `Most Coder deployments never have to think about DERP because all connections + between workspaces and users are peer-to-peer. However, when Coder cannot establish + a peer to peer connection, Coder uses a distributed relay network backed by + Tailscale and WireGuard.`, + } + DeploymentGroupIntrospection = clibase.Group{ + Name: "Introspection", + Description: `Configure logging, tracing, and metrics exporting.`, + } + DeploymentGroupIntrospectionPPROF = clibase.Group{ + Parent: &DeploymentGroupIntrospection, + Name: "pprof", + } + DeploymentGroupIntrospectionPrometheus = clibase.Group{ + Parent: &DeploymentGroupIntrospection, + Name: "Prometheus", + } + DeploymentGroupIntrospectionTracing = clibase.Group{ + Parent: &DeploymentGroupIntrospection, + Name: "Tracing", + } + DeploymentGroupIntrospectionLogging = clibase.Group{ + Parent: &DeploymentGroupIntrospection, + Name: "Logging", + } + DeploymentGroupOAuth2 = clibase.Group{ + Name: "OAuth2", + Description: `Configure login and user-provisioning with GitHub via oAuth2.`, + } + DeploymentGroupOAuth2GitHub = clibase.Group{ + Parent: &DeploymentGroupOAuth2, + Name: "GitHub", + } + DeploymentGroupOIDC = clibase.Group{ + Name: "OIDC", + } + DeploymentGroupTelemetry = clibase.Group{ + Name: "Telemetry", + Description: `Telemetry is critical to our ability to improve Coder. We strip all personal +information before sending data to our servers. Please only disable telemetry +when required by your organization's security policy.`, + } + DeploymentGroupProvisioning = clibase.Group{ + Name: "Provisioning", + Description: `Tune the behavior of the provisioner, which is responsible for creating, updating, and deleting workspace resources.`, + } + DeploymentGroupDangerous = clibase.Group{ + Name: "⚠️ Dangerous", + } + DeploymentGroupConfig = clibase.Group{ + Name: "Config", + Description: `Use a YAML configuration file when your server launch become unwieldy.`, + } +) + +// DeploymentConfig contains both the deployment values and how they're set. +// +// @typescript-ignore DeploymentConfig +// apitypings doesn't know how to generate the OptionSet... yet. +type DeploymentConfig struct { + Values *DeploymentValues `json:"config,omitempty"` + Options clibase.OptionSet `json:"options,omitempty"` +} + +func (c *DeploymentValues) Options() clibase.OptionSet { + httpAddress := clibase.Option{ + Name: "HTTP Address", + Description: "HTTP bind address of the server. Unset to disable the HTTP endpoint.", + Flag: "http-address", + Env: "HTTP_ADDRESS", + Default: "127.0.0.1:3000", + Value: &c.HTTPAddress, + Group: &DeploymentGroupNetworkingHTTP, + YAML: "httpAddress", + } + tlsBindAddress := clibase.Option{ + Name: "TLS Address", + Description: "HTTPS bind address of the server.", + Flag: "tls-address", + Env: "TLS_ADDRESS", + Default: "127.0.0.1:3443", + Value: &c.TLS.Address, + Group: &DeploymentGroupNetworkingTLS, + YAML: "address", + } + redirectToAccessURL := clibase.Option{ + Name: "Redirect to Access URL", + Description: "Specifies whether to redirect requests that do not match the access URL host.", + Flag: "redirect-to-access-url", + Env: "REDIRECT_TO_ACCESS_URL", + Value: &c.RedirectToAccessURL, + Group: &DeploymentGroupNetworking, + YAML: "redirectToAccessURL", + } + return clibase.OptionSet{ + { + Name: "Access URL", + Description: `The URL that users will use to access the Coder deployment.`, + Value: &c.AccessURL, + Flag: "access-url", + Env: "ACCESS_URL", + Group: &DeploymentGroupNetworking, + YAML: "accessURL", + }, + { + Name: "Wildcard Access URL", + Description: "Specifies the wildcard hostname to use for workspace applications in the form \"*.example.com\".", + Flag: "wildcard-access-url", + Env: "WILDCARD_ACCESS_URL", + Value: &c.WildcardAccessURL, + Group: &DeploymentGroupNetworking, + YAML: "wildcardAccessURL", + }, + redirectToAccessURL, + { + Name: "Autobuild Poll Interval", + Description: "Interval to poll for scheduled workspace builds.", + Flag: "autobuild-poll-interval", + Env: "AUTOBUILD_POLL_INTERVAL", + Hidden: true, + Default: time.Minute.String(), + Value: &c.AutobuildPollInterval, + YAML: "autobuildPollInterval", + }, + httpAddress, + tlsBindAddress, + { + Name: "Address", + Description: "Bind address of the server.", + Flag: "address", + FlagShorthand: "a", + Env: "ADDRESS", + Hidden: true, + Value: &c.Address, + UseInstead: []clibase.Option{ + httpAddress, + tlsBindAddress, + }, + Group: &DeploymentGroupNetworking, + }, + // TLS settings + { + Name: "TLS Enable", + Description: "Whether TLS will be enabled.", + Flag: "tls-enable", + Env: "TLS_ENABLE", + Value: &c.TLS.Enable, + Group: &DeploymentGroupNetworkingTLS, + YAML: "enable", + }, + { + Name: "Redirect HTTP to HTTPS", + Description: "Whether HTTP requests will be redirected to the access URL (if it's a https URL and TLS is enabled). Requests to local IP addresses are never redirected regardless of this setting.", + Flag: "tls-redirect-http-to-https", + Env: "TLS_REDIRECT_HTTP_TO_HTTPS", + Default: "true", + Hidden: true, + Value: &c.TLS.RedirectHTTP, + UseInstead: []clibase.Option{redirectToAccessURL}, + Group: &DeploymentGroupNetworkingTLS, + YAML: "redirectHTTP", + }, + { + Name: "TLS Certificate Files", + Description: "Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file.", + Flag: "tls-cert-file", + Env: "TLS_CERT_FILE", + Value: &c.TLS.CertFiles, + Group: &DeploymentGroupNetworkingTLS, + YAML: "certFiles", + }, + { + Name: "TLS Client CA Files", + Description: "PEM-encoded Certificate Authority file used for checking the authenticity of client", + Flag: "tls-client-ca-file", + Env: "TLS_CLIENT_CA_FILE", + Value: &c.TLS.ClientCAFile, + Group: &DeploymentGroupNetworkingTLS, + YAML: "clientCAFile", + }, + { + Name: "TLS Client Auth", + Description: "Policy the server will follow for TLS Client Authentication. Accepted values are \"none\", \"request\", \"require-any\", \"verify-if-given\", or \"require-and-verify\".", + Flag: "tls-client-auth", + Env: "TLS_CLIENT_AUTH", + Default: "none", + Value: &c.TLS.ClientAuth, + Group: &DeploymentGroupNetworkingTLS, + YAML: "clientAuth", + }, + { + Name: "TLS Key Files", + Description: "Paths to the private keys for each of the certificates. It requires a PEM-encoded file.", + Flag: "tls-key-file", + Env: "TLS_KEY_FILE", + Value: &c.TLS.KeyFiles, + Group: &DeploymentGroupNetworkingTLS, + YAML: "keyFiles", + }, + { + Name: "TLS Minimum Version", + Description: "Minimum supported version of TLS. Accepted values are \"tls10\", \"tls11\", \"tls12\" or \"tls13\"", + Flag: "tls-min-version", + Env: "TLS_MIN_VERSION", + Default: "tls12", + Value: &c.TLS.MinVersion, + Group: &DeploymentGroupNetworkingTLS, + YAML: "minVersion", + }, + { + Name: "TLS Client Cert File", + Description: "Path to certificate for client TLS authentication. It requires a PEM-encoded file.", + Flag: "tls-client-cert-file", + Env: "TLS_CLIENT_CERT_FILE", + Value: &c.TLS.ClientCertFile, + Group: &DeploymentGroupNetworkingTLS, + YAML: "clientCertFile", + }, + { + Name: "TLS Client Key File", + Description: "Path to key for client TLS authentication. It requires a PEM-encoded file.", + Flag: "tls-client-key-file", + Env: "TLS_CLIENT_KEY_FILE", + Value: &c.TLS.ClientKeyFile, + Group: &DeploymentGroupNetworkingTLS, + YAML: "clientKeyFile", + }, + // Derp settings + { + Name: "DERP Server Enable", + Description: "Whether to enable or disable the embedded DERP relay server.", + Flag: "derp-server-enable", + Env: "DERP_SERVER_ENABLE", + Default: "true", + Value: &c.DERP.Server.Enable, + Group: &DeploymentGroupNetworkingDERP, + YAML: "enable", + }, + { + Name: "DERP Server Region ID", + Description: "Region ID to use for the embedded DERP server.", + Flag: "derp-server-region-id", + Env: "DERP_SERVER_REGION_ID", + Default: "999", + Value: &c.DERP.Server.RegionID, + Group: &DeploymentGroupNetworkingDERP, + YAML: "regionID", + }, + { + Name: "DERP Server Region Code", + Description: "Region code to use for the embedded DERP server.", + Flag: "derp-server-region-code", + Env: "DERP_SERVER_REGION_CODE", + Default: "coder", + Value: &c.DERP.Server.RegionCode, + Group: &DeploymentGroupNetworkingDERP, + YAML: "regionCode", + }, + { + Name: "DERP Server Region Name", + Description: "Region name that for the embedded DERP server.", + Flag: "derp-server-region-name", + Env: "DERP_SERVER_REGION_NAME", + Default: "Coder Embedded Relay", + Value: &c.DERP.Server.RegionName, + Group: &DeploymentGroupNetworkingDERP, + YAML: "regionName", + }, + { + Name: "DERP Server STUN Addresses", + Description: "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", + Flag: "derp-server-stun-addresses", + Env: "DERP_SERVER_STUN_ADDRESSES", + Default: "stun.l.google.com:19302", + Value: &c.DERP.Server.STUNAddresses, + Group: &DeploymentGroupNetworkingDERP, + YAML: "stunAddresses", + }, + { + Name: "DERP Server Relay URL", + Description: "An HTTP URL that is accessible by other replicas to relay DERP traffic. Required for high availability.", + Flag: "derp-server-relay-url", + Env: "DERP_SERVER_RELAY_URL", + Annotations: clibase.Annotations{}.Mark(flagEnterpriseKey, "true"), + Value: &c.DERP.Server.RelayURL, + Group: &DeploymentGroupNetworkingDERP, + YAML: "relayURL", + }, + { + Name: "DERP Config URL", + Description: "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", + Flag: "derp-config-url", + Env: "DERP_CONFIG_URL", + Value: &c.DERP.Config.URL, + Group: &DeploymentGroupNetworkingDERP, + YAML: "url", + }, + { + Name: "DERP Config Path", + Description: "Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/", + Flag: "derp-config-path", + Env: "DERP_CONFIG_PATH", + Value: &c.DERP.Config.Path, + Group: &DeploymentGroupNetworkingDERP, + YAML: "configPath", + }, + // TODO: support Git Auth settings. + // Prometheus settings + { + Name: "Prometheus Enable", + Description: "Serve prometheus metrics on the address defined by prometheus address.", + Flag: "prometheus-enable", + Env: "PROMETHEUS_ENABLE", + Value: &c.Prometheus.Enable, + Group: &DeploymentGroupIntrospectionPrometheus, + YAML: "enable", + }, + { + Name: "Prometheus Address", + Description: "The bind address to serve prometheus metrics.", + Flag: "prometheus-address", + Env: "PROMETHEUS_ADDRESS", + Default: "127.0.0.1:2112", + Value: &c.Prometheus.Address, + Group: &DeploymentGroupIntrospectionPrometheus, + YAML: "address", + }, + // Pprof settings + { + Name: "pprof Enable", + Description: "Serve pprof metrics on the address defined by pprof address.", + Flag: "pprof-enable", + Env: "PPROF_ENABLE", + Value: &c.Pprof.Enable, + Group: &DeploymentGroupIntrospectionPPROF, + YAML: "enable", + }, + { + Name: "pprof Address", + Description: "The bind address to serve pprof.", + Flag: "pprof-address", + Env: "PPROF_ADDRESS", + Default: "127.0.0.1:6060", + Value: &c.Pprof.Address, + Group: &DeploymentGroupIntrospectionPPROF, + YAML: "address", + }, + // oAuth settings + { + Name: "OAuth2 GitHub Client ID", + Description: "Client ID for Login with GitHub.", + Flag: "oauth2-github-client-id", + Env: "OAUTH2_GITHUB_CLIENT_ID", + Value: &c.OAuth2.Github.ClientID, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "clientID", + }, + { + Name: "OAuth2 GitHub Client Secret", + Description: "Client secret for Login with GitHub.", + Flag: "oauth2-github-client-secret", + Env: "OAUTH2_GITHUB_CLIENT_SECRET", + Value: &c.OAuth2.Github.ClientSecret, + Annotations: clibase.Annotations{}.Mark(flagSecretKey, "true"), + Group: &DeploymentGroupOAuth2GitHub, + }, + { + Name: "OAuth2 GitHub Allowed Orgs", + Description: "Organizations the user must be a member of to Login with GitHub.", + Flag: "oauth2-github-allowed-orgs", + Env: "OAUTH2_GITHUB_ALLOWED_ORGS", + Value: &c.OAuth2.Github.AllowedOrgs, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "allowedOrgs", + }, + { + Name: "OAuth2 GitHub Allowed Teams", + Description: "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", + Flag: "oauth2-github-allowed-teams", + Env: "OAUTH2_GITHUB_ALLOWED_TEAMS", + Value: &c.OAuth2.Github.AllowedTeams, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "allowedTeams", + }, + { + Name: "OAuth2 GitHub Allow Signups", + Description: "Whether new users can sign up with GitHub.", + Flag: "oauth2-github-allow-signups", + Env: "OAUTH2_GITHUB_ALLOW_SIGNUPS", + Value: &c.OAuth2.Github.AllowSignups, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "allowSignups", + }, + { + Name: "OAuth2 GitHub Allow Everyone", + Description: "Allow all logins, setting this option means allowed orgs and teams must be empty.", + Flag: "oauth2-github-allow-everyone", + Env: "OAUTH2_GITHUB_ALLOW_EVERYONE", + Value: &c.OAuth2.Github.AllowEveryone, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "allowEveryone", + }, + { + Name: "OAuth2 GitHub Enterprise Base URL", + Description: "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", + Flag: "oauth2-github-enterprise-base-url", + Env: "OAUTH2_GITHUB_ENTERPRISE_BASE_URL", + Value: &c.OAuth2.Github.EnterpriseBaseURL, + Group: &DeploymentGroupOAuth2GitHub, + YAML: "enterpriseBaseURL", + }, + // OIDC settings. + { + Name: "OIDC Allow Signups", + Description: "Whether new users can sign up with OIDC.", + Flag: "oidc-allow-signups", + Env: "OIDC_ALLOW_SIGNUPS", + Default: "true", + Value: &c.OIDC.AllowSignups, + Group: &DeploymentGroupOIDC, + YAML: "allowSignups", + }, + { + Name: "OIDC Client ID", + Description: "Client ID to use for Login with OIDC.", + Flag: "oidc-client-id", + Env: "OIDC_CLIENT_ID", + Value: &c.OIDC.ClientID, + Group: &DeploymentGroupOIDC, + YAML: "clientID", + }, + { + Name: "OIDC Client Secret", + Description: "Client secret to use for Login with OIDC.", + Flag: "oidc-client-secret", + Env: "OIDC_CLIENT_SECRET", + Annotations: clibase.Annotations{}.Mark(flagSecretKey, "true"), + Value: &c.OIDC.ClientSecret, + Group: &DeploymentGroupOIDC, + }, + { + Name: "OIDC Email Domain", + Description: "Email domains that clients logging in with OIDC must match.", + Flag: "oidc-email-domain", + Env: "OIDC_EMAIL_DOMAIN", + Value: &c.OIDC.EmailDomain, + Group: &DeploymentGroupOIDC, + YAML: "emailDomain", + }, + { + Name: "OIDC Issuer URL", + Description: "Issuer URL to use for Login with OIDC.", + Flag: "oidc-issuer-url", + Env: "OIDC_ISSUER_URL", + Value: &c.OIDC.IssuerURL, + Group: &DeploymentGroupOIDC, + YAML: "issuerURL", + }, + { + Name: "OIDC Scopes", + Description: "Scopes to grant when authenticating with OIDC.", + Flag: "oidc-scopes", + Env: "OIDC_SCOPES", + Default: strings.Join([]string{oidc.ScopeOpenID, "profile", "email"}, ","), + Value: &c.OIDC.Scopes, + Group: &DeploymentGroupOIDC, + YAML: "scopes", + }, + { + Name: "OIDC Ignore Email Verified", + Description: "Ignore the email_verified claim from the upstream provider.", + Flag: "oidc-ignore-email-verified", + Env: "OIDC_IGNORE_EMAIL_VERIFIED", + Default: "false", + Value: &c.OIDC.IgnoreEmailVerified, + Group: &DeploymentGroupOIDC, + YAML: "ignoreEmailVerified", + }, + { + Name: "OIDC Username Field", + Description: "OIDC claim field to use as the username.", + Flag: "oidc-username-field", + Env: "OIDC_USERNAME_FIELD", + Default: "preferred_username", + Value: &c.OIDC.UsernameField, + Group: &DeploymentGroupOIDC, + YAML: "usernameField", + }, + { + Name: "OpenID Connect sign in text", + Description: "The text to show on the OpenID Connect sign in button", + Flag: "oidc-sign-in-text", + Env: "OIDC_SIGN_IN_TEXT", + Default: "OpenID Connect", + Value: &c.OIDC.SignInText, + Group: &DeploymentGroupOIDC, + YAML: "signInText", + }, + { + Name: "OpenID connect icon URL", + Description: "URL pointing to the icon to use on the OepnID Connect login button", + Flag: "oidc-icon-url", + Env: "OIDC_ICON_URL", + Value: &c.OIDC.IconURL, + Group: &DeploymentGroupOIDC, + YAML: "iconURL", + }, + // Telemetry settings + { + Name: "Telemetry Enable", + Description: "Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.", + Flag: "telemetry", + Env: "TELEMETRY_ENABLE", + Default: strconv.FormatBool(flag.Lookup("test.v") == nil), + Value: &c.Telemetry.Enable, + Group: &DeploymentGroupTelemetry, + YAML: "enable", + }, + { + Name: "Telemetry Trace", + Description: "Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option.", + Flag: "telemetry-trace", + Env: "TELEMETRY_TRACE", + Default: strconv.FormatBool(flag.Lookup("test.v") == nil), + Value: &c.Telemetry.Trace, + Group: &DeploymentGroupTelemetry, + YAML: "trace", + }, + { + Name: "Telemetry URL", + Description: "URL to send telemetry.", + Flag: "telemetry-url", + Env: "TELEMETRY_URL", + Hidden: true, + Default: "https://telemetry.coder.com", + Value: &c.Telemetry.URL, + Group: &DeploymentGroupTelemetry, + YAML: "url", + }, + // Trace settings + { + Name: "Trace Enable", + Description: "Whether application tracing data is collected. It exports to a backend configured by environment variables. See: https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md", + Flag: "trace", + Env: "TRACE_ENABLE", + Value: &c.Trace.Enable, + Group: &DeploymentGroupIntrospectionTracing, + YAML: "enable", + }, + { + Name: "Trace Honeycomb API Key", + Description: "Enables trace exporting to Honeycomb.io using the provided API Key.", + Flag: "trace-honeycomb-api-key", + Env: "TRACE_HONEYCOMB_API_KEY", + Annotations: clibase.Annotations{}.Mark(flagSecretKey, "true"), + Value: &c.Trace.HoneycombAPIKey, + Group: &DeploymentGroupIntrospectionTracing, + }, + { + Name: "Capture Logs in Traces", + Description: "Enables capturing of logs as events in traces. This is useful for debugging, but may result in a very large amount of events being sent to the tracing backend which may incur significant costs. If the verbose flag was supplied, debug-level logs will be included.", + Flag: "trace-logs", + Env: "TRACE_LOGS", + Value: &c.Trace.CaptureLogs, + Group: &DeploymentGroupIntrospectionTracing, + YAML: "captureLogs", + }, + // Provisioner settings + { + Name: "Provisioner Daemons", + Description: "Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this.", + Flag: "provisioner-daemons", + Env: "PROVISIONER_DAEMONS", + Default: "3", + Value: &c.Provisioner.Daemons, + Group: &DeploymentGroupProvisioning, + YAML: "daemons", + }, + { + Name: "Poll Interval", + Description: "Time to wait before polling for a new job.", + Flag: "provisioner-daemon-poll-interval", + Env: "PROVISIONER_DAEMON_POLL_INTERVAL", + Default: time.Second.String(), + Value: &c.Provisioner.DaemonPollInterval, + Group: &DeploymentGroupProvisioning, + YAML: "daemonPollInterval", + }, + { + Name: "Poll Jitter", + Description: "Random jitter added to the poll interval.", + Flag: "provisioner-daemon-poll-jitter", + Env: "PROVISIONER_DAEMON_POLL_JITTER", + Default: (100 * time.Millisecond).String(), + Value: &c.Provisioner.DaemonPollJitter, + Group: &DeploymentGroupProvisioning, + YAML: "daemonPollJitter", + }, + { + Name: "Force Cancel Interval", + Description: "Time to force cancel provisioning tasks that are stuck.", + Flag: "provisioner-force-cancel-interval", + Env: "PROVISIONER_FORCE_CANCEL_INTERVAL", + Default: (10 * time.Minute).String(), + Value: &c.Provisioner.ForceCancelInterval, + Group: &DeploymentGroupProvisioning, + YAML: "forceCancelInterval", + }, + // RateLimit settings + { + Name: "Disable All Rate Limits", + Description: "Disables all rate limits. This is not recommended in production.", + Flag: "dangerous-disable-rate-limits", + Env: "DANGEROUS_DISABLE_RATE_LIMITS", + Default: "false", + Value: &c.RateLimit.DisableAll, + Hidden: true, + }, + { + Name: "API Rate Limit", + Description: "Maximum number of requests per minute allowed to the API per user, or per IP address for unauthenticated users. Negative values mean no rate limit. Some API endpoints have separate strict rate limits regardless of this value to prevent denial-of-service or brute force attacks.", + // Change the env from the auto-generated CODER_RATE_LIMIT_API to the + // old value to avoid breaking existing deployments. + Env: "API_RATE_LIMIT", + Flag: "api-rate-limit", + Default: "512", + Value: &c.RateLimit.API, + Hidden: true, + }, + // Logging settings + { + Name: "Verbose", + Description: "Output debug-level logs.", + Flag: "verbose", + Env: "VERBOSE", + FlagShorthand: "v", + Default: "false", + Value: &c.Verbose, + Group: &DeploymentGroupIntrospectionLogging, + YAML: "verbose", + }, + { + Name: "Human Log Location", + Description: "Output human-readable logs to a given file.", + Flag: "log-human", + Env: "LOG_HUMAN", + Default: "/dev/stderr", + Value: &c.Logging.Human, + Group: &DeploymentGroupIntrospectionLogging, + YAML: "humanPath", + }, + { + Name: "JSON Log Location", + Description: "Output JSON logs to a given file.", + Flag: "log-json", + Env: "LOG_JSON", + Default: "", + Value: &c.Logging.JSON, + Group: &DeploymentGroupIntrospectionLogging, + YAML: "jsonPath", + }, + { + Name: "Stackdriver Log Location", + Description: "Output Stackdriver compatible logs to a given file.", + Flag: "log-stackdriver", + Env: "LOG_STACKDRIVER", + Default: "", + Value: &c.Logging.Stackdriver, + Group: &DeploymentGroupIntrospectionLogging, + YAML: "stackdriverPath", + }, + // ☢️ Dangerous settings + { + Name: "DANGEROUS: Allow Path App Sharing", + Description: "Allow workspace apps that are not served from subdomains to be shared. Path-based app sharing is DISABLED by default for security purposes. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security.", + Flag: "dangerous-allow-path-app-sharing", + Env: "DANGEROUS_ALLOW_PATH_APP_SHARING", + Default: "false", + Value: &c.Dangerous.AllowPathAppSharing, + Group: &DeploymentGroupDangerous, + }, + { + Name: "DANGEROUS: Allow Site Owners to Access Path Apps", + Description: "Allow site-owners to access workspace apps from workspaces they do not own. Owners cannot access path-based apps they do not own by default. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. Path-based apps can be disabled entirely with --disable-path-apps for further security.", + Flag: "dangerous-allow-path-app-site-owner-access", + Env: "DANGEROUS_ALLOW_PATH_APP_SITE_OWNER_ACCESS", + Default: "false", + Value: &c.Dangerous.AllowPathAppSiteOwnerAccess, + Group: &DeploymentGroupDangerous, + }, + // Misc. settings + { + Name: "Experiments", + Description: "Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments.", + Flag: "experiments", + Env: "EXPERIMENTS", + Value: &c.Experiments, + YAML: "experiments", + }, + { + Name: "Update Check", + Description: "Periodically check for new releases of Coder and inform the owner. The check is performed once per day.", + Flag: "update-check", + Env: "UPDATE_CHECK", + Default: strconv.FormatBool( + flag.Lookup("test.v") == nil && !buildinfo.IsDev(), + ), + Value: &c.UpdateCheck, + YAML: "updateCheck", + }, + { + Name: "Max Token Lifetime", + Description: "The maximum lifetime duration users can specify when creating an API token.", + Flag: "max-token-lifetime", + Env: "MAX_TOKEN_LIFETIME", + Default: time.Duration(math.MaxInt64).String(), + Value: &c.MaxTokenLifetime, + Group: &DeploymentGroupNetworkingHTTP, + YAML: "maxTokenLifetime", + }, + { + Name: "Enable swagger endpoint", + Description: "Expose the swagger endpoint via /swagger.", + Flag: "swagger-enable", + Env: "SWAGGER_ENABLE", + Default: "false", + Value: &c.Swagger.Enable, + YAML: "enableSwagger", + }, + { + Name: "Proxy Trusted Headers", + Flag: "proxy-trusted-headers", + Env: "PROXY_TRUSTED_HEADERS", + Description: "Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, True-Client-Ip, X-Forwarded-For", + Value: &c.ProxyTrustedHeaders, + Group: &DeploymentGroupNetworking, + YAML: "proxyTrustedHeaders", + }, + { + Name: "Proxy Trusted Origins", + Flag: "proxy-trusted-origins", + Env: "PROXY_TRUSTED_ORIGINS", + Description: "Origin addresses to respect \"proxy-trusted-headers\". e.g. 192.168.1.0/24", + Value: &c.ProxyTrustedOrigins, + Group: &DeploymentGroupNetworking, + YAML: "proxyTrustedOrigins", + }, + { + Name: "Cache Directory", + Description: "The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.", + Flag: "cache-dir", + Env: "CACHE_DIRECTORY", + Default: DefaultCacheDir(), + Value: &c.CacheDir, + YAML: "cacheDir", + }, + { + Name: "In Memory Database", + Description: "Controls whether data will be stored in an in-memory database.", + Flag: "in-memory", + Env: "IN_MEMORY", + Hidden: true, + Value: &c.InMemoryDatabase, + YAML: "inMemoryDatabase", + }, + { + Name: "Postgres Connection URL", + Description: "URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\".", + Flag: "postgres-url", + Env: "PG_CONNECTION_URL", + Annotations: clibase.Annotations{}.Mark(flagSecretKey, "true"), + Value: &c.PostgresURL, + }, + { + Name: "Secure Auth Cookie", + Description: "Controls if the 'Secure' property is set on browser session cookies.", + Flag: "secure-auth-cookie", + Env: "SECURE_AUTH_COOKIE", + Value: &c.SecureAuthCookie, + Group: &DeploymentGroupNetworking, + YAML: "secureAuthCookie", + }, + { + Name: "Strict-Transport-Security", + Description: "Controls if the 'Strict-Transport-Security' header is set on all static file responses. " + + "This header should only be set if the server is accessed via HTTPS. This value is the MaxAge in seconds of " + + "the header.", + Default: "0", + Flag: "strict-transport-security", + Env: "STRICT_TRANSPORT_SECURITY", + Value: &c.StrictTransportSecurity, + Group: &DeploymentGroupNetworkingTLS, + YAML: "strictTransportSecurity", + }, + { + Name: "Strict-Transport-Security Options", + Description: "Two optional fields can be set in the Strict-Transport-Security header; 'includeSubDomains' and 'preload'. " + + "The 'strict-transport-security' flag must be set to a non-zero value for these options to be used.", + Flag: "strict-transport-security-options", + Env: "STRICT_TRANSPORT_SECURITY_OPTIONS", + Value: &c.StrictTransportSecurityOptions, + Group: &DeploymentGroupNetworkingTLS, + YAML: "strictTransportSecurityOptions", + }, + { + Name: "SSH Keygen Algorithm", + Description: "The algorithm to use for generating ssh keys. Accepted values are \"ed25519\", \"ecdsa\", or \"rsa4096\".", + Flag: "ssh-keygen-algorithm", + Env: "SSH_KEYGEN_ALGORITHM", + Default: "ed25519", + Value: &c.SSHKeygenAlgorithm, + YAML: "sshKeygenAlgorithm", + }, + { + Name: "Metrics Cache Refresh Interval", + Description: "How frequently metrics are refreshed", + Flag: "metrics-cache-refresh-interval", + Env: "METRICS_CACHE_REFRESH_INTERVAL", + Hidden: true, + Default: time.Hour.String(), + Value: &c.MetricsCacheRefreshInterval, + }, + { + Name: "Agent Stat Refresh Interval", + Description: "How frequently agent stats are recorded", + Flag: "agent-stats-refresh-interval", + Env: "AGENT_STATS_REFRESH_INTERVAL", + Hidden: true, + Default: (10 * time.Minute).String(), + Value: &c.AgentStatRefreshInterval, + }, + { + Name: "Agent Fallback Troubleshooting URL", + Description: "URL to use for agent troubleshooting when not set in the template", + Flag: "agent-fallback-troubleshooting-url", + Env: "AGENT_FALLBACK_TROUBLESHOOTING_URL", + Hidden: true, + Default: "https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates", + Value: &c.AgentFallbackTroubleshootingURL, + YAML: "agentFallbackTroubleshootingURL", + }, + { + Name: "Audit Logging", + Description: "Specifies whether audit logging is enabled.", + Flag: "audit-logging", + Env: "AUDIT_LOGGING", + Default: "true", + Annotations: clibase.Annotations{}.Mark(flagEnterpriseKey, "true"), + Value: &c.AuditLogging, + YAML: "auditLogging", + }, + { + Name: "Browser Only", + Description: "Whether Coder only allows connections to workspaces via the browser.", + Flag: "browser-only", + Env: "BROWSER_ONLY", + Annotations: clibase.Annotations{}.Mark(flagEnterpriseKey, "true"), + Value: &c.BrowserOnly, + Group: &DeploymentGroupNetworking, + YAML: "browserOnly", + }, + { + Name: "SCIM API Key", + Description: "Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication.", + Flag: "scim-auth-header", + Env: "SCIM_AUTH_HEADER", + Annotations: clibase.Annotations{}.Mark(flagEnterpriseKey, "true").Mark(flagSecretKey, "true"), + Value: &c.SCIMAPIKey, + }, + + { + Name: "Disable Path Apps", + Description: "Disable workspace apps that are not served from subdomains. Path-based apps can make requests to the Coder API and pose a security risk when the workspace serves malicious JavaScript. This is recommended for security purposes if a --wildcard-access-url is configured.", + Flag: "disable-path-apps", + Env: "DISABLE_PATH_APPS", + Default: "false", + Value: &c.DisablePathApps, + YAML: "disablePathApps", + }, + { + Name: "Session Duration", + Description: "The token expiry duration for browser sessions. Sessions may last longer if they are actively making requests, but this functionality can be disabled via --disable-session-expiry-refresh.", + Flag: "session-duration", + Env: "SESSION_DURATION", + Default: (24 * time.Hour).String(), + Value: &c.SessionDuration, + Group: &DeploymentGroupNetworkingHTTP, + YAML: "sessionDuration", + }, + { + Name: "Disable Session Expiry Refresh", + Description: "Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached.", + Flag: "disable-session-expiry-refresh", + Env: "DISABLE_SESSION_EXPIRY_REFRESH", + Default: "false", + Value: &c.DisableSessionExpiryRefresh, + Group: &DeploymentGroupNetworkingHTTP, + YAML: "disableSessionExpiryRefresh", + }, + { + Name: "Disable Password Authentication", + Description: "Disable password authentication. This is recommended for security purposes in production deployments that rely on an identity provider. Any user with the owner role will be able to sign in with their password regardless of this setting to avoid potential lock out. If you are locked out of your account, you can use the `coder server create-admin` command to create a new admin user directly in the database.", + Flag: "disable-password-auth", + Env: "DISABLE_PASSWORD_AUTH", + Default: "false", + Value: &c.DisablePasswordAuth, + Group: &DeploymentGroupNetworkingHTTP, + YAML: "disablePasswordAuth", + }, + { + Name: "Config Path", + Description: `Specify a YAML file to load configuration from.`, + Flag: "config", + Env: "CONFIG_PATH", + FlagShorthand: "c", + Hidden: true, + Group: &DeploymentGroupConfig, + Value: &c.Config, + }, + { + Name: "Write Config", + Description: ` +Write out the current server configuration to the path specified by --config.`, + Flag: "write-config", + Env: "WRITE_CONFIG", + Group: &DeploymentGroupConfig, + Hidden: true, + Value: &c.WriteConfig, + }, + { + Name: "Support Links", + Description: "Support links to display in the top right drop down menu.", + YAML: "supportLinks", + Value: &c.Support.Links, + }, + { + // Env handling is done in cli.ReadGitAuthFromEnvironment + Name: "Git Auth Providers", + Description: "Git Authentication providers", + YAML: "gitAuthProviders", + Value: &c.GitAuthProviders, + Hidden: true, + }, + } } type SupportConfig struct { - Links *DeploymentConfigField[[]LinkConfig] `json:"links" typescript:",notnull"` + Links clibase.Struct[[]LinkConfig] `json:"links" typescript:",notnull"` } type LinkConfig struct { - Name string `json:"name"` - Target string `json:"target"` - Icon string `json:"icon"` + Name string `json:"name" yaml:"name"` + Target string `json:"target" yaml:"target"` + Icon string `json:"icon" yaml:"icon"` } -type Flaggable interface { - string | time.Duration | bool | int | []string | []GitAuthConfig | []LinkConfig -} +// WithoutSecrets returns a copy of the config without secret values. +func (c *DeploymentValues) WithoutSecrets() (*DeploymentValues, error) { + var ff DeploymentValues -type DeploymentConfigField[T Flaggable] struct { - Name string `json:"name"` - Usage string `json:"usage"` - Flag string `json:"flag"` - // EnvOverride will override the automatically generated environment - // variable name. Useful if you're moving values around but need to keep - // backwards compatibility with old environment variable names. - // - // NOTE: this is not supported for array flags. - EnvOverride string `json:"-"` - Shorthand string `json:"shorthand"` - Enterprise bool `json:"enterprise"` - Hidden bool `json:"hidden"` - Secret bool `json:"secret"` - Default T `json:"default"` - Value T `json:"value"` -} - -// MarshalJSON removes the Value field from the JSON output of any fields marked Secret. -// nolint:revive -func (f *DeploymentConfigField[T]) MarshalJSON() ([]byte, error) { - copy := struct { - Name string `json:"name"` - Usage string `json:"usage"` - Flag string `json:"flag"` - Shorthand string `json:"shorthand"` - Enterprise bool `json:"enterprise"` - Hidden bool `json:"hidden"` - Secret bool `json:"secret"` - Default T `json:"default"` - Value T `json:"value"` - }{ - Name: f.Name, - Usage: f.Usage, - Flag: f.Flag, - Shorthand: f.Shorthand, - Enterprise: f.Enterprise, - Hidden: f.Hidden, - Secret: f.Secret, + // Create copy via JSON. + byt, err := json.Marshal(c) + if err != nil { + return nil, err + } + err = json.Unmarshal(byt, &ff) + if err != nil { + return nil, err } - if !f.Secret { - copy.Default = f.Default - copy.Value = f.Value + for _, opt := range ff.Options() { + if !IsSecretDeploymentOption(opt) { + continue + } + + // This only works with string values for now. + switch v := opt.Value.(type) { + case *clibase.String: + err := v.Set("") + if err != nil { + panic(err) + } + default: + return nil, xerrors.Errorf("unsupported type %T", v) + } } - return json.Marshal(copy) + return &ff, nil } -// DeploymentConfig returns the deployment config for the coder server. -func (c *Client) DeploymentConfig(ctx context.Context) (DeploymentConfig, error) { +// DeploymentValues returns the deployment config for the coder server. +func (c *Client) DeploymentValues(ctx context.Context) (*DeploymentConfig, error) { res, err := c.Request(ctx, http.MethodGet, "/api/v2/config/deployment", nil) if err != nil { - return DeploymentConfig{}, xerrors.Errorf("execute request: %w", err) + return nil, xerrors.Errorf("execute request: %w", err) } defer res.Body.Close() if res.StatusCode != http.StatusOK { - return DeploymentConfig{}, ReadBodyAsError(res) + return nil, ReadBodyAsError(res) } - var df DeploymentConfig - return df, json.NewDecoder(res.Body).Decode(&df) + byt, err := io.ReadAll(res.Body) + if err != nil { + return nil, xerrors.Errorf("read response: %w", err) + } + + conf := &DeploymentValues{} + resp := &DeploymentConfig{ + Values: conf, + Options: conf.Options(), + } + + err = json.Unmarshal(byt, resp) + if err != nil { + return nil, xerrors.Errorf("decode response: %w\n%s", err, byt) + } + return resp, nil } type AppearanceConfig struct { diff --git a/codersdk/deployment_test.go b/codersdk/deployment_test.go new file mode 100644 index 0000000000..54a1af10ff --- /dev/null +++ b/codersdk/deployment_test.go @@ -0,0 +1,107 @@ +package codersdk_test + +import ( + "testing" + + "github.com/coder/coder/codersdk" +) + +type exclusion struct { + flag bool + env bool + yaml bool +} + +func TestDeploymentValues_HighlyConfigurable(t *testing.T) { + t.Parallel() + + // This test ensures that every deployment option has + // a corresponding Flag, Env, and YAML name, unless explicitly excluded. + + excludes := map[string]exclusion{ + // These are used to configure YAML support itself, so + // they make no sense within the YAML file. + "Config Path": { + yaml: true, + }, + "Write Config": { + yaml: true, + }, + // Dangerous values? Not sure we should help users + // persistent their configuration. + "DANGEROUS: Allow Path App Sharing": { + yaml: true, + }, + "DANGEROUS: Allow Site Owners to Access Path Apps": { + yaml: true, + }, + // Secrets + "Trace Honeycomb API Key": { + yaml: true, + }, + "OAuth2 GitHub Client Secret": { + yaml: true, + }, + "OIDC Client Secret": { + yaml: true, + }, + "Postgres Connection URL": { + yaml: true, + }, + "SCIM API Key": { + yaml: true, + }, + // These complex objects should be configured through YAML. + "Support Links": { + flag: true, + env: true, + }, + "Git Auth Providers": { + // Technically Git Auth Providers can be provided through the env, + // but bypassing clibase. See cli.ReadGitAuthProvidersFromEnv. + flag: true, + env: true, + }, + } + + set := (&codersdk.DeploymentValues{}).Options() + for _, opt := range set { + // These are generally for development, so their configurability is + // not relevant. + if opt.Hidden { + delete(excludes, opt.Name) + continue + } + + if codersdk.IsSecretDeploymentOption(opt) && opt.YAML != "" { + // Secrets should not be written to YAML and instead should continue + // to be read from the environment. + // + // Unfortunately, secrets are still accepted through flags for + // legacy purposes. Eventually, we should prevent that. + t.Errorf("Option %q is a secret but has a YAML name", opt.Name) + } + + excluded := excludes[opt.Name] + switch { + case opt.YAML == "" && !excluded.yaml: + t.Errorf("Option %q should have a YAML name", opt.Name) + case opt.YAML != "" && excluded.yaml: + t.Errorf("Option %q is excluded but has a YAML name", opt.Name) + case opt.Flag == "" && !excluded.flag: + t.Errorf("Option %q should have a flag name", opt.Name) + case opt.Flag != "" && excluded.flag: + t.Errorf("Option %q is excluded but has a flag name", opt.Name) + case opt.Env == "" && !excluded.env: + t.Errorf("Option %q should have an env name", opt.Name) + case opt.Env != "" && excluded.env: + t.Errorf("Option %q is excluded but has an env name", opt.Name) + } + + delete(excludes, opt.Name) + } + + for opt := range excludes { + t.Errorf("Excluded option %q is not in the deployment config. Remove it?", opt) + } +} diff --git a/docs/api/enterprise.md b/docs/api/enterprise.md index 87eae9c4f5..13dc4bfc9c 100644 --- a/docs/api/enterprise.md +++ b/docs/api/enterprise.md @@ -119,7 +119,6 @@ curl -X GET http://coder-server:8080/api/v2/entitlements \ ```json { "errors": ["string"], - "experimental": true, "features": { "property1": { "actual": 0, diff --git a/docs/api/general.md b/docs/api/general.md index 8102f24af5..649c051ea8 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -83,1044 +83,274 @@ curl -X GET http://coder-server:8080/api/v2/config/deployment \ ```json { - "access_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "agent_fallback_troubleshooting_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "agent_stat_refresh_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "audit_logging": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "autobuild_poll_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "browser_only": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "cache_directory": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "dangerous": { - "allow_path_app_sharing": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + "config": { + "access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "allow_path_app_site_owner_access": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "derp": { - "config": { - "path": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "server": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "region_code": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "region_id": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "region_name": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "relay_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "stun_addresses": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - } - } - }, - "disable_password_auth": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "disable_path_apps": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "disable_session_expiry_refresh": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "experimental": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "experiments": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "gitauth": { - "default": [ - { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": [ - { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" - } - ] - }, - "http_address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "in_memory_database": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "logging": { - "human": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "json": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "stackdriver": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "max_session_expiry": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "max_token_lifetime": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "metrics_cache_refresh_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "oauth2": { - "github": { - "allow_everyone": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allowed_orgs": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "allowed_teams": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enterprise_base_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - } - }, - "oidc": { - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "email_domain": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "icon_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "ignore_email_verified": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "issuer_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "scopes": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "sign_in_text": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "username_field": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "pg_connection_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "pprof": { "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "host": "string", + "port": "string" }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "prometheus": { - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "agent_fallback_troubleshooting_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "provisioner": { - "daemon_poll_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 + "agent_stat_refresh_interval": 0, + "audit_logging": true, + "autobuild_poll_interval": 0, + "browser_only": true, + "cache_directory": "string", + "config": "string", + "dangerous": { + "allow_path_app_sharing": true, + "allow_path_app_site_owner_access": true }, - "daemon_poll_jitter": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 + "derp": { + "config": { + "path": "string", + "url": "string" + }, + "server": { + "enable": true, + "region_code": "string", + "region_id": 0, + "region_name": "string", + "relay_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "stun_addresses": ["string"] + } }, - "daemons": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "force_cancel_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - } - }, - "proxy_trusted_headers": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "proxy_trusted_origins": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "rate_limit": { - "api": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "disable_all": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "redirect_to_access_url": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "scim_api_key": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "secure_auth_cookie": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "ssh_keygen_algorithm": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "strict_transport_security": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "strict_transport_security_options": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "support": { - "links": { - "default": [ - { - "icon": "string", - "name": "string", - "target": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", + "disable_password_auth": true, + "disable_path_apps": true, + "disable_session_expiry_refresh": true, + "experiments": ["string"], + "git_auth": { "value": [ { - "icon": "string", - "name": "string", - "target": "string" + "auth_url": "string", + "client_id": "string", + "id": "string", + "no_refresh": true, + "regex": "string", + "scopes": ["string"], + "token_url": "string", + "type": "string", + "validate_url": "string" } ] - } - }, - "swagger": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "telemetry": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + }, + "http_address": "string", + "in_memory_database": true, + "logging": { + "human": "string", + "json": "string", + "stackdriver": "string" + }, + "max_session_expiry": 0, + "max_token_lifetime": 0, + "metrics_cache_refresh_interval": 0, + "oauth2": { + "github": { + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" + } + }, + "oidc": { + "allow_signups": true, + "client_id": "string", + "client_secret": "string", + "email_domain": ["string"], + "icon_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "ignore_email_verified": true, + "issuer_url": "string", + "scopes": ["string"], + "sign_in_text": "string", + "username_field": "string" + }, + "pg_connection_url": "string", + "pprof": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "prometheus": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "provisioner": { + "daemon_poll_interval": 0, + "daemon_poll_jitter": 0, + "daemons": 0, + "force_cancel_interval": 0 + }, + "proxy_trusted_headers": ["string"], + "proxy_trusted_origins": ["string"], + "rate_limit": { + "api": 0, + "disable_all": true + }, + "redirect_to_access_url": true, + "scim_api_key": "string", + "secure_auth_cookie": true, + "ssh_keygen_algorithm": "string", + "strict_transport_security": 0, + "strict_transport_security_options": ["string"], + "support": { + "links": { + "value": [ + { + "icon": "string", + "name": "string", + "target": "string" + } + ] + } + }, + "swagger": { + "enable": true + }, + "telemetry": { + "enable": true, + "trace": true, + "url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + } + }, + "tls": { + "address": { + "host": "string", + "port": "string" + }, + "cert_file": ["string"], + "client_auth": "string", + "client_ca_file": "string", + "client_cert_file": "string", + "client_key_file": "string", + "enable": true, + "key_file": ["string"], + "min_version": "string", + "redirect_http": true }, "trace": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + "capture_logs": true, + "enable": true, + "honeycomb_api_key": "string" }, - "url": { + "update_check": true, + "verbose": true, + "wildcard_access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "write_config": true + }, + "options": [ + { + "annotations": { + "property1": "string", + "property2": "string" + }, "default": "string", - "enterprise": true, + "description": "string", + "env": "string", "flag": "string", + "flag_shorthand": "string", + "group": { + "children": [ + { + "children": [], + "description": "string", + "name": "string", + "parent": {} + } + ], + "description": "string", + "name": "string", + "parent": { + "children": [{}], + "description": "string", + "name": "string", + "parent": {} + } + }, "hidden": true, "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "use_instead": [{}], + "value": null, + "yaml": "string" } - }, - "tls": { - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "cert_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_auth": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_ca_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_cert_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_key_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "key_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "min_version": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "redirect_http": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "trace": { - "capture_logs": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "honeycomb_api_key": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "update_check": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "wildcard_access_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + ] } ``` diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 3d30dadc67..9f1ed5f358 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -300,6 +300,240 @@ | ----------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------ | | `report_interval` | integer | false | | Report interval is the duration after which the agent should send stats again. | +## clibase.Annotations + +```json +{ + "property1": "string", + "property2": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------- | ------ | -------- | ------------ | ----------- | +| `[any property]` | string | false | | | + +## clibase.Group + +```json +{ + "children": [ + { + "children": [], + "description": "string", + "name": "string", + "parent": {} + } + ], + "description": "string", + "name": "string", + "parent": { + "children": [{}], + "description": "string", + "name": "string", + "parent": {} + } +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | --------------------------------------- | -------- | ------------ | ----------- | +| `children` | array of [clibase.Group](#clibasegroup) | false | | | +| `description` | string | false | | | +| `name` | string | false | | | +| `parent` | [clibase.Group](#clibasegroup) | false | | | + +## clibase.HostPort + +```json +{ + "host": "string", + "port": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------ | ------ | -------- | ------------ | ----------- | +| `host` | string | false | | | +| `port` | string | false | | | + +## clibase.Option + +```json +{ + "annotations": { + "property1": "string", + "property2": "string" + }, + "default": "string", + "description": "string", + "env": "string", + "flag": "string", + "flag_shorthand": "string", + "group": { + "children": [ + { + "children": [], + "description": "string", + "name": "string", + "parent": {} + } + ], + "description": "string", + "name": "string", + "parent": { + "children": [{}], + "description": "string", + "name": "string", + "parent": {} + } + }, + "hidden": true, + "name": "string", + "use_instead": [ + { + "annotations": { + "property1": "string", + "property2": "string" + }, + "default": "string", + "description": "string", + "env": "string", + "flag": "string", + "flag_shorthand": "string", + "group": { + "children": [ + { + "children": [], + "description": "string", + "name": "string", + "parent": {} + } + ], + "description": "string", + "name": "string", + "parent": { + "children": [{}], + "description": "string", + "name": "string", + "parent": {} + } + }, + "hidden": true, + "name": "string", + "use_instead": [], + "value": null, + "yaml": "string" + } + ], + "value": null, + "yaml": "string" +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ---------------- | ------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | +| `annotations` | [clibase.Annotations](#clibaseannotations) | false | | Annotations enable extensions to clibase higher up in the stack. It's useful for help formatting and documentation generation. | +| `default` | string | false | | Default is parsed into Value if set. | +| `description` | string | false | | | +| `env` | string | false | | Env is the environment variable used to configure this option. If unset, environment configuring is disabled. | +| `flag` | string | false | | Flag is the long name of the flag used to configure this option. If unset, flag configuring is disabled. | +| `flag_shorthand` | string | false | | Flag shorthand is the one-character shorthand for the flag. If unset, no shorthand is used. | +| `group` | [clibase.Group](#clibasegroup) | false | | Group is a group hierarchy that helps organize this option in help, configs and other documentation. | +| `hidden` | boolean | false | | | +| `name` | string | false | | | +| `use_instead` | array of [clibase.Option](#clibaseoption) | false | | Use instead is a list of options that should be used instead of this one. The field is used to generate a deprecation warning. | +| `value` | any | false | | Value includes the types listed in values.go. | +| `yaml` | string | false | | Yaml is the YAML key used to configure this option. If unset, YAML configuring is disabled. | + +## clibase.Struct-array_codersdk_GitAuthConfig + +```json +{ + "value": [ + { + "auth_url": "string", + "client_id": "string", + "id": "string", + "no_refresh": true, + "regex": "string", + "scopes": ["string"], + "token_url": "string", + "type": "string", + "validate_url": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------- | --------------------------------------------------------- | -------- | ------------ | ----------- | +| `value` | array of [codersdk.GitAuthConfig](#codersdkgitauthconfig) | false | | | + +## clibase.Struct-array_codersdk_LinkConfig + +```json +{ + "value": [ + { + "icon": "string", + "name": "string", + "target": "string" + } + ] +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------- | --------------------------------------------------- | -------- | ------------ | ----------- | +| `value` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | + +## clibase.URL + +```json +{ + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------- | ---------------------------- | -------- | ------------ | -------------------------------------------------- | +| `forceQuery` | boolean | false | | append a query ('?') even if RawQuery is empty | +| `fragment` | string | false | | fragment for references, without '#' | +| `host` | string | false | | host or host:port | +| `omitHost` | boolean | false | | do not emit empty host (authority) | +| `opaque` | string | false | | encoded opaque data | +| `path` | string | false | | path (relative paths may omit leading slash) | +| `rawFragment` | string | false | | encoded fragment hint (see EscapedFragment method) | +| `rawPath` | string | false | | encoded path hint (see EscapedPath method) | +| `rawQuery` | string | false | | encoded query values, without '?' | +| `scheme` | string | false | | | +| `user` | [url.Userinfo](#urluserinfo) | false | | username and password information | + ## coderd.SCIMUser ```json @@ -1295,96 +1529,28 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { "config": { - "path": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "path": "string", + "url": "string" }, "server": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "region_code": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "region_id": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "region_name": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, + "enable": true, + "region_code": "string", + "region_id": 0, + "region_name": "string", "relay_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "stun_addresses": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - } + "stun_addresses": ["string"] } } ``` @@ -1400,37 +1566,17 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { - "path": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "path": "string", + "url": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------ | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `path` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------ | ------ | -------- | ------------ | ----------- | +| `path` | string | false | | | +| `url` | string | false | | | ## codersdk.DERPRegion @@ -1452,1251 +1598,324 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "region_code": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "region_id": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "region_name": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, + "enable": true, + "region_code": "string", + "region_id": 0, + "region_name": "string", "relay_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "stun_addresses": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - } + "stun_addresses": ["string"] } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------- | ------------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `region_code` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `region_id` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | | -| `region_name` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `relay_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `stun_addresses` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------- | -------------------------- | -------- | ------------ | ----------- | +| `enable` | boolean | false | | | +| `region_code` | string | false | | | +| `region_id` | integer | false | | | +| `region_name` | string | false | | | +| `relay_url` | [clibase.URL](#clibaseurl) | false | | | +| `stun_addresses` | array of string | false | | | ## codersdk.DangerousConfig ```json { - "allow_path_app_sharing": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allow_path_app_site_owner_access": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "allow_path_app_sharing": true, + "allow_path_app_site_owner_access": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ---------------------------------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `allow_path_app_sharing` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `allow_path_app_site_owner_access` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| ---------------------------------- | ------- | -------- | ------------ | ----------- | +| `allow_path_app_sharing` | boolean | false | | | +| `allow_path_app_site_owner_access` | boolean | false | | | ## codersdk.DeploymentConfig ```json { - "access_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "agent_fallback_troubleshooting_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "agent_stat_refresh_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "audit_logging": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "autobuild_poll_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "browser_only": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "cache_directory": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "dangerous": { - "allow_path_app_sharing": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + "config": { + "access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "allow_path_app_site_owner_access": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "derp": { - "config": { - "path": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "server": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "region_code": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "region_id": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "region_name": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "relay_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "stun_addresses": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - } - } - }, - "disable_password_auth": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "disable_path_apps": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "disable_session_expiry_refresh": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "experimental": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "experiments": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "gitauth": { - "default": [ - { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": [ - { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" - } - ] - }, - "http_address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "in_memory_database": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "logging": { - "human": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "json": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "stackdriver": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "max_session_expiry": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "max_token_lifetime": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "metrics_cache_refresh_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "oauth2": { - "github": { - "allow_everyone": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allowed_orgs": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "allowed_teams": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enterprise_base_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - } - }, - "oidc": { - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "email_domain": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "icon_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "ignore_email_verified": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "issuer_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "scopes": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "sign_in_text": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "username_field": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "pg_connection_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "pprof": { "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "host": "string", + "port": "string" }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "prometheus": { - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "agent_fallback_troubleshooting_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "provisioner": { - "daemon_poll_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 + "agent_stat_refresh_interval": 0, + "audit_logging": true, + "autobuild_poll_interval": 0, + "browser_only": true, + "cache_directory": "string", + "config": "string", + "dangerous": { + "allow_path_app_sharing": true, + "allow_path_app_site_owner_access": true }, - "daemon_poll_jitter": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 + "derp": { + "config": { + "path": "string", + "url": "string" + }, + "server": { + "enable": true, + "region_code": "string", + "region_id": 0, + "region_name": "string", + "relay_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "stun_addresses": ["string"] + } }, - "daemons": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "force_cancel_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - } - }, - "proxy_trusted_headers": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "proxy_trusted_origins": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "rate_limit": { - "api": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "disable_all": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "redirect_to_access_url": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "scim_api_key": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "secure_auth_cookie": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "ssh_keygen_algorithm": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "strict_transport_security": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "strict_transport_security_options": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "support": { - "links": { - "default": [ - { - "icon": "string", - "name": "string", - "target": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", + "disable_password_auth": true, + "disable_path_apps": true, + "disable_session_expiry_refresh": true, + "experiments": ["string"], + "git_auth": { "value": [ { - "icon": "string", - "name": "string", - "target": "string" + "auth_url": "string", + "client_id": "string", + "id": "string", + "no_refresh": true, + "regex": "string", + "scopes": ["string"], + "token_url": "string", + "type": "string", + "validate_url": "string" } ] - } - }, - "swagger": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "telemetry": { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + }, + "http_address": "string", + "in_memory_database": true, + "logging": { + "human": "string", + "json": "string", + "stackdriver": "string" + }, + "max_session_expiry": 0, + "max_token_lifetime": 0, + "metrics_cache_refresh_interval": 0, + "oauth2": { + "github": { + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" + } + }, + "oidc": { + "allow_signups": true, + "client_id": "string", + "client_secret": "string", + "email_domain": ["string"], + "icon_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "ignore_email_verified": true, + "issuer_url": "string", + "scopes": ["string"], + "sign_in_text": "string", + "username_field": "string" + }, + "pg_connection_url": "string", + "pprof": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "prometheus": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "provisioner": { + "daemon_poll_interval": 0, + "daemon_poll_jitter": 0, + "daemons": 0, + "force_cancel_interval": 0 + }, + "proxy_trusted_headers": ["string"], + "proxy_trusted_origins": ["string"], + "rate_limit": { + "api": 0, + "disable_all": true + }, + "redirect_to_access_url": true, + "scim_api_key": "string", + "secure_auth_cookie": true, + "ssh_keygen_algorithm": "string", + "strict_transport_security": 0, + "strict_transport_security_options": ["string"], + "support": { + "links": { + "value": [ + { + "icon": "string", + "name": "string", + "target": "string" + } + ] + } + }, + "swagger": { + "enable": true + }, + "telemetry": { + "enable": true, + "trace": true, + "url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + } + }, + "tls": { + "address": { + "host": "string", + "port": "string" + }, + "cert_file": ["string"], + "client_auth": "string", + "client_ca_file": "string", + "client_cert_file": "string", + "client_key_file": "string", + "enable": true, + "key_file": ["string"], + "min_version": "string", + "redirect_http": true }, "trace": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true + "capture_logs": true, + "enable": true, + "honeycomb_api_key": "string" }, - "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "update_check": true, + "verbose": true, + "wildcard_access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "write_config": true }, - "tls": { - "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "cert_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_auth": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_ca_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_cert_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_key_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "key_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "min_version": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "redirect_http": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } - }, - "trace": { - "capture_logs": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "honeycomb_api_key": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } - }, - "update_check": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "wildcard_access_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ----------------------------------------------- | -| `access_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | Address Use HTTPAddress or TLS.Address instead. | -| `agent_fallback_troubleshooting_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `agent_stat_refresh_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `audit_logging` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `autobuild_poll_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `browser_only` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `cache_directory` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `dangerous` | [codersdk.DangerousConfig](#codersdkdangerousconfig) | false | | | -| `derp` | [codersdk.DERP](#codersdkderp) | false | | | -| `disable_password_auth` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `disable_path_apps` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `disable_session_expiry_refresh` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `experimental` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | Experimental Use Experiments instead. | -| `experiments` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `gitauth` | [codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig](#codersdkdeploymentconfigfield-array_codersdk_gitauthconfig) | false | | | -| `http_address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `in_memory_database` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `logging` | [codersdk.LoggingConfig](#codersdkloggingconfig) | false | | | -| `max_session_expiry` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `max_token_lifetime` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `metrics_cache_refresh_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `oauth2` | [codersdk.OAuth2Config](#codersdkoauth2config) | false | | | -| `oidc` | [codersdk.OIDCConfig](#codersdkoidcconfig) | false | | | -| `pg_connection_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `pprof` | [codersdk.PprofConfig](#codersdkpprofconfig) | false | | | -| `prometheus` | [codersdk.PrometheusConfig](#codersdkprometheusconfig) | false | | | -| `provisioner` | [codersdk.ProvisionerConfig](#codersdkprovisionerconfig) | false | | | -| `proxy_trusted_headers` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `proxy_trusted_origins` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | | -| `redirect_to_access_url` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `scim_api_key` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `secure_auth_cookie` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `ssh_keygen_algorithm` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `strict_transport_security` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | | -| `strict_transport_security_options` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `support` | [codersdk.SupportConfig](#codersdksupportconfig) | false | | | -| `swagger` | [codersdk.SwaggerConfig](#codersdkswaggerconfig) | false | | | -| `telemetry` | [codersdk.TelemetryConfig](#codersdktelemetryconfig) | false | | | -| `tls` | [codersdk.TLSConfig](#codersdktlsconfig) | false | | | -| `trace` | [codersdk.TraceConfig](#codersdktraceconfig) | false | | | -| `update_check` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `wildcard_access_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | - -## codersdk.DeploymentConfigField-array_codersdk_GitAuthConfig - -```json -{ - "default": [ + "options": [ { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": [ - { - "auth_url": "string", - "client_id": "string", - "id": "string", - "no_refresh": true, - "regex": "string", - "scopes": ["string"], - "token_url": "string", - "type": "string", - "validate_url": "string" + "annotations": { + "property1": "string", + "property2": "string" + }, + "default": "string", + "description": "string", + "env": "string", + "flag": "string", + "flag_shorthand": "string", + "group": { + "children": [ + { + "children": [], + "description": "string", + "name": "string", + "parent": {} + } + ], + "description": "string", + "name": "string", + "parent": { + "children": [{}], + "description": "string", + "name": "string", + "parent": {} + } + }, + "hidden": true, + "name": "string", + "use_instead": [{}], + "value": null, + "yaml": "string" } ] } @@ -2704,209 +1923,10 @@ CreateParameterRequest is a structure used to create a new parameter value for a ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------ | --------------------------------------------------------- | -------- | ------------ | ----------- | -| `default` | array of [codersdk.GitAuthConfig](#codersdkgitauthconfig) | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | array of [codersdk.GitAuthConfig](#codersdkgitauthconfig) | false | | | - -## codersdk.DeploymentConfigField-array_codersdk_LinkConfig - -```json -{ - "default": [ - { - "icon": "string", - "name": "string", - "target": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": [ - { - "icon": "string", - "name": "string", - "target": "string" - } - ] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | --------------------------------------------------- | -------- | ------------ | ----------- | -| `default` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | array of [codersdk.LinkConfig](#codersdklinkconfig) | false | | | - -## codersdk.DeploymentConfigField-array_string - -```json -{ - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | --------------- | -------- | ------------ | ----------- | -| `default` | array of string | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | array of string | false | | | - -## codersdk.DeploymentConfigField-bool - -```json -{ - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `default` | boolean | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | boolean | false | | | - -## codersdk.DeploymentConfigField-int - -```json -{ - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `default` | integer | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | integer | false | | | - -## codersdk.DeploymentConfigField-string - -```json -{ - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `default` | string | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | string | false | | | - -## codersdk.DeploymentConfigField-time_Duration - -```json -{ - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 -} -``` - -### Properties - -| Name | Type | Required | Restrictions | Description | -| ------------ | ------- | -------- | ------------ | ----------- | -| `default` | integer | false | | | -| `enterprise` | boolean | false | | | -| `flag` | string | false | | | -| `hidden` | boolean | false | | | -| `name` | string | false | | | -| `secret` | boolean | false | | | -| `shorthand` | string | false | | | -| `usage` | string | false | | | -| `value` | integer | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ------------------------------------------------------ | -------- | ------------ | ----------- | +| `config` | [codersdk.DeploymentValues](#codersdkdeploymentvalues) | false | | | +| `options` | array of [clibase.Option](#clibaseoption) | false | | | ## codersdk.DeploymentDAUsResponse @@ -2927,6 +1947,294 @@ CreateParameterRequest is a structure used to create a new parameter value for a | --------- | ----------------------------------------------- | -------- | ------------ | ----------- | | `entries` | array of [codersdk.DAUEntry](#codersdkdauentry) | false | | | +## codersdk.DeploymentValues + +```json +{ + "access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "address": { + "host": "string", + "port": "string" + }, + "agent_fallback_troubleshooting_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "agent_stat_refresh_interval": 0, + "audit_logging": true, + "autobuild_poll_interval": 0, + "browser_only": true, + "cache_directory": "string", + "config": "string", + "dangerous": { + "allow_path_app_sharing": true, + "allow_path_app_site_owner_access": true + }, + "derp": { + "config": { + "path": "string", + "url": "string" + }, + "server": { + "enable": true, + "region_code": "string", + "region_id": 0, + "region_name": "string", + "relay_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "stun_addresses": ["string"] + } + }, + "disable_password_auth": true, + "disable_path_apps": true, + "disable_session_expiry_refresh": true, + "experiments": ["string"], + "git_auth": { + "value": [ + { + "auth_url": "string", + "client_id": "string", + "id": "string", + "no_refresh": true, + "regex": "string", + "scopes": ["string"], + "token_url": "string", + "type": "string", + "validate_url": "string" + } + ] + }, + "http_address": "string", + "in_memory_database": true, + "logging": { + "human": "string", + "json": "string", + "stackdriver": "string" + }, + "max_session_expiry": 0, + "max_token_lifetime": 0, + "metrics_cache_refresh_interval": 0, + "oauth2": { + "github": { + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" + } + }, + "oidc": { + "allow_signups": true, + "client_id": "string", + "client_secret": "string", + "email_domain": ["string"], + "icon_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "ignore_email_verified": true, + "issuer_url": "string", + "scopes": ["string"], + "sign_in_text": "string", + "username_field": "string" + }, + "pg_connection_url": "string", + "pprof": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "prometheus": { + "address": { + "host": "string", + "port": "string" + }, + "enable": true + }, + "provisioner": { + "daemon_poll_interval": 0, + "daemon_poll_jitter": 0, + "daemons": 0, + "force_cancel_interval": 0 + }, + "proxy_trusted_headers": ["string"], + "proxy_trusted_origins": ["string"], + "rate_limit": { + "api": 0, + "disable_all": true + }, + "redirect_to_access_url": true, + "scim_api_key": "string", + "secure_auth_cookie": true, + "ssh_keygen_algorithm": "string", + "strict_transport_security": 0, + "strict_transport_security_options": ["string"], + "support": { + "links": { + "value": [ + { + "icon": "string", + "name": "string", + "target": "string" + } + ] + } + }, + "swagger": { + "enable": true + }, + "telemetry": { + "enable": true, + "trace": true, + "url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + } + }, + "tls": { + "address": { + "host": "string", + "port": "string" + }, + "cert_file": ["string"], + "client_auth": "string", + "client_ca_file": "string", + "client_cert_file": "string", + "client_key_file": "string", + "enable": true, + "key_file": ["string"], + "min_version": "string", + "redirect_http": true + }, + "trace": { + "capture_logs": true, + "enable": true, + "honeycomb_api_key": "string" + }, + "update_check": true, + "verbose": true, + "wildcard_access_url": { + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} + }, + "write_config": true +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +| ------------------------------------ | ------------------------------------------------------------------------------------------ | -------- | ------------ | ------------------------------------------------------------------ | +| `access_url` | [clibase.URL](#clibaseurl) | false | | | +| `address` | [clibase.HostPort](#clibasehostport) | false | | Address Use HTTPAddress or TLS.Address instead. | +| `agent_fallback_troubleshooting_url` | [clibase.URL](#clibaseurl) | false | | | +| `agent_stat_refresh_interval` | integer | false | | | +| `audit_logging` | boolean | false | | | +| `autobuild_poll_interval` | integer | false | | | +| `browser_only` | boolean | false | | | +| `cache_directory` | string | false | | | +| `config` | string | false | | | +| `dangerous` | [codersdk.DangerousConfig](#codersdkdangerousconfig) | false | | | +| `derp` | [codersdk.DERP](#codersdkderp) | false | | | +| `disable_password_auth` | boolean | false | | | +| `disable_path_apps` | boolean | false | | | +| `disable_session_expiry_refresh` | boolean | false | | | +| `experiments` | array of string | false | | | +| `git_auth` | [clibase.Struct-array_codersdk_GitAuthConfig](#clibasestruct-array_codersdk_gitauthconfig) | false | | | +| `http_address` | string | false | | Http address is a string because it may be set to zero to disable. | +| `in_memory_database` | boolean | false | | | +| `logging` | [codersdk.LoggingConfig](#codersdkloggingconfig) | false | | | +| `max_session_expiry` | integer | false | | | +| `max_token_lifetime` | integer | false | | | +| `metrics_cache_refresh_interval` | integer | false | | | +| `oauth2` | [codersdk.OAuth2Config](#codersdkoauth2config) | false | | | +| `oidc` | [codersdk.OIDCConfig](#codersdkoidcconfig) | false | | | +| `pg_connection_url` | string | false | | | +| `pprof` | [codersdk.PprofConfig](#codersdkpprofconfig) | false | | | +| `prometheus` | [codersdk.PrometheusConfig](#codersdkprometheusconfig) | false | | | +| `provisioner` | [codersdk.ProvisionerConfig](#codersdkprovisionerconfig) | false | | | +| `proxy_trusted_headers` | array of string | false | | | +| `proxy_trusted_origins` | array of string | false | | | +| `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | | +| `redirect_to_access_url` | boolean | false | | | +| `scim_api_key` | string | false | | | +| `secure_auth_cookie` | boolean | false | | | +| `ssh_keygen_algorithm` | string | false | | | +| `strict_transport_security` | integer | false | | | +| `strict_transport_security_options` | array of string | false | | | +| `support` | [codersdk.SupportConfig](#codersdksupportconfig) | false | | | +| `swagger` | [codersdk.SwaggerConfig](#codersdkswaggerconfig) | false | | | +| `telemetry` | [codersdk.TelemetryConfig](#codersdktelemetryconfig) | false | | | +| `tls` | [codersdk.TLSConfig](#codersdktlsconfig) | false | | | +| `trace` | [codersdk.TraceConfig](#codersdktraceconfig) | false | | | +| `update_check` | boolean | false | | | +| `verbose` | boolean | false | | | +| `wildcard_access_url` | [clibase.URL](#clibaseurl) | false | | | +| `write_config` | boolean | false | | | + ## codersdk.Entitlement ```json @@ -2948,7 +2256,6 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { "errors": ["string"], - "experimental": true, "features": { "property1": { "actual": 0, @@ -2972,16 +2279,15 @@ CreateParameterRequest is a structure used to create a new parameter value for a ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------------------------------------ | -------- | ------------ | ------------------------------------- | -| `errors` | array of string | false | | | -| `experimental` | boolean | false | | Experimental use Experiments instead. | -| `features` | object | false | | | -| » `[any property]` | [codersdk.Feature](#codersdkfeature) | false | | | -| `has_license` | boolean | false | | | -| `require_telemetry` | boolean | false | | | -| `trial` | boolean | false | | | -| `warnings` | array of string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------------------------------------ | -------- | ------------ | ----------- | +| `errors` | array of string | false | | | +| `features` | object | false | | | +| » `[any property]` | [codersdk.Feature](#codersdkfeature) | false | | | +| `has_license` | boolean | false | | | +| `require_telemetry` | boolean | false | | | +| `trial` | boolean | false | | | +| `warnings` | array of string | false | | | ## codersdk.Experiment @@ -3266,49 +2572,19 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { - "human": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "json": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "stackdriver": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "human": "string", + "json": "string", + "stackdriver": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `human` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `json` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `stackdriver` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------ | -------- | ------------ | ----------- | +| `human` | string | false | | | +| `json` | string | false | | | +| `stackdriver` | string | false | | | ## codersdk.LoginType @@ -3362,83 +2638,13 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { "github": { - "allow_everyone": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allowed_orgs": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "allowed_teams": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enterprise_base_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" } } ``` @@ -3453,97 +2659,27 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { - "allow_everyone": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "allowed_orgs": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "allowed_teams": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enterprise_base_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "allow_everyone": true, + "allow_signups": true, + "allowed_orgs": ["string"], + "allowed_teams": ["string"], + "client_id": "string", + "client_secret": "string", + "enterprise_base_url": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------------------- | ------------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `allow_everyone` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `allow_signups` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `allowed_orgs` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `allowed_teams` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `client_id` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `client_secret` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `enterprise_base_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| --------------------- | --------------- | -------- | ------------ | ----------- | +| `allow_everyone` | boolean | false | | | +| `allow_signups` | boolean | false | | | +| `allowed_orgs` | array of string | false | | | +| `allowed_teams` | array of string | false | | | +| `client_id` | string | false | | | +| `client_secret` | string | false | | | +| `enterprise_base_url` | string | false | | | ## codersdk.OIDCAuthMethod @@ -3567,133 +2703,45 @@ CreateParameterRequest is a structure used to create a new parameter value for a ```json { - "allow_signups": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "client_id": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_secret": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "email_domain": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, + "allow_signups": true, + "client_id": "string", + "client_secret": "string", + "email_domain": ["string"], "icon_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} }, - "ignore_email_verified": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "issuer_url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "scopes": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "sign_in_text": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "username_field": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "ignore_email_verified": true, + "issuer_url": "string", + "scopes": ["string"], + "sign_in_text": "string", + "username_field": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | ------------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `allow_signups` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `client_id` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `client_secret` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `email_domain` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `icon_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `ignore_email_verified` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `issuer_url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `scopes` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `sign_in_text` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `username_field` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------------- | -------------------------- | -------- | ------------ | ----------- | +| `allow_signups` | boolean | false | | | +| `client_id` | string | false | | | +| `client_secret` | string | false | | | +| `email_domain` | array of string | false | | | +| `icon_url` | [clibase.URL](#clibaseurl) | false | | | +| `ignore_email_verified` | boolean | false | | | +| `issuer_url` | string | false | | | +| `scopes` | array of string | false | | | +| `sign_in_text` | string | false | | | +| `username_field` | string | false | | | ## codersdk.Organization @@ -3893,132 +2941,58 @@ Parameter represents a set value for the scope. ```json { "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "host": "string", + "port": "string" }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "enable": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [clibase.HostPort](#clibasehostport) | false | | | +| `enable` | boolean | false | | | ## codersdk.PrometheusConfig ```json { "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "host": "string", + "port": "string" }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "enable": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| --------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| --------- | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [clibase.HostPort](#clibasehostport) | false | | | +| `enable` | boolean | false | | | ## codersdk.ProvisionerConfig ```json { - "daemon_poll_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "daemon_poll_jitter": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "daemons": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "force_cancel_interval": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - } + "daemon_poll_interval": 0, + "daemon_poll_jitter": 0, + "daemons": 0, + "force_cancel_interval": 0 } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ----------------------- | -------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `daemon_poll_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `daemon_poll_jitter` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | -| `daemons` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | | -| `force_cancel_interval` | [codersdk.DeploymentConfigField-time_Duration](#codersdkdeploymentconfigfield-time_duration) | false | | | +| Name | Type | Required | Restrictions | Description | +| ----------------------- | ------- | -------- | ------------ | ----------- | +| `daemon_poll_interval` | integer | false | | | +| `daemon_poll_jitter` | integer | false | | | +| `daemons` | integer | false | | | +| `force_cancel_interval` | integer | false | | | ## codersdk.ProvisionerDaemon @@ -4183,37 +3157,17 @@ Parameter represents a set value for the scope. ```json { - "api": { - "default": 0, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": 0 - }, - "disable_all": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "api": 0, + "disable_all": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `api` | [codersdk.DeploymentConfigField-int](#codersdkdeploymentconfigfield-int) | false | | | -| `disable_all` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------- | -------- | ------------ | ----------- | +| `api` | integer | false | | | +| `disable_all` | boolean | false | | | ## codersdk.Replica @@ -4325,20 +3279,6 @@ Parameter represents a set value for the scope. ```json { "links": { - "default": [ - { - "icon": "string", - "name": "string", - "target": "string" - } - ], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", "value": [ { "icon": "string", @@ -4352,213 +3292,88 @@ Parameter represents a set value for the scope. ### Properties -| Name | Type | Required | Restrictions | Description | -| ------- | -------------------------------------------------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `links` | [codersdk.DeploymentConfigField-array_codersdk_LinkConfig](#codersdkdeploymentconfigfield-array_codersdk_linkconfig) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------- | ------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | +| `links` | [clibase.Struct-array_codersdk_LinkConfig](#clibasestruct-array_codersdk_linkconfig) | false | | | ## codersdk.SwaggerConfig ```json { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "enable": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | -------------------------------------------------------------------------- | -------- | ------------ | ----------- | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| -------- | ------- | -------- | ------------ | ----------- | +| `enable` | boolean | false | | | ## codersdk.TLSConfig ```json { "address": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "host": "string", + "port": "string" }, - "cert_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "client_auth": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_ca_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_cert_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "client_key_file": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "key_file": { - "default": ["string"], - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": ["string"] - }, - "min_version": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - }, - "redirect_http": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - } + "cert_file": ["string"], + "client_auth": "string", + "client_ca_file": "string", + "client_cert_file": "string", + "client_key_file": "string", + "enable": true, + "key_file": ["string"], + "min_version": "string", + "redirect_http": true } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------ | ------------------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `address` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `cert_file` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `client_auth` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `client_ca_file` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `client_cert_file` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `client_key_file` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `key_file` | [codersdk.DeploymentConfigField-array_string](#codersdkdeploymentconfigfield-array_string) | false | | | -| `min_version` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | -| `redirect_http` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------ | ------------------------------------ | -------- | ------------ | ----------- | +| `address` | [clibase.HostPort](#clibasehostport) | false | | | +| `cert_file` | array of string | false | | | +| `client_auth` | string | false | | | +| `client_ca_file` | string | false | | | +| `client_cert_file` | string | false | | | +| `client_key_file` | string | false | | | +| `enable` | boolean | false | | | +| `key_file` | array of string | false | | | +| `min_version` | string | false | | | +| `redirect_http` | boolean | false | | | ## codersdk.TelemetryConfig ```json { - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "trace": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, + "enable": true, + "trace": true, "url": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" + "forceQuery": true, + "fragment": "string", + "host": "string", + "omitHost": true, + "opaque": "string", + "path": "string", + "rawFragment": "string", + "rawPath": "string", + "rawQuery": "string", + "scheme": "string", + "user": {} } } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| -------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `trace` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `url` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| -------- | -------------------------- | -------- | ------------ | ----------- | +| `enable` | boolean | false | | | +| `trace` | boolean | false | | | +| `url` | [clibase.URL](#clibaseurl) | false | | | ## codersdk.Template @@ -4943,49 +3758,19 @@ Parameter represents a set value for the scope. ```json { - "capture_logs": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "enable": { - "default": true, - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": true - }, - "honeycomb_api_key": { - "default": "string", - "enterprise": true, - "flag": "string", - "hidden": true, - "name": "string", - "secret": true, - "shorthand": "string", - "usage": "string", - "value": "string" - } + "capture_logs": true, + "enable": true, + "honeycomb_api_key": "string" } ``` ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------------- | ------------------------------------------------------------------------------ | -------- | ------------ | ----------- | -| `capture_logs` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `enable` | [codersdk.DeploymentConfigField-bool](#codersdkdeploymentconfigfield-bool) | false | | | -| `honeycomb_api_key` | [codersdk.DeploymentConfigField-string](#codersdkdeploymentconfigfield-string) | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------------- | ------- | -------- | ------------ | ----------- | +| `capture_logs` | boolean | false | | | +| `enable` | boolean | false | | | +| `honeycomb_api_key` | string | false | | | ## codersdk.TransitionStats @@ -6509,3 +5294,13 @@ It corresponds to the legacy derpN.tailscale.com hostnames used by older clients RegionIDs must be non-zero, positive, and guaranteed to fit in a JavaScript number. RegionIDs in range 900-999 are reserved for end users to run their own DERP nodes.| |`regionName`|string|false||Regionname is a long English name for the region: "New York City", "San Francisco", "Singapore", "Frankfurt", etc.| + +## url.Userinfo + +```json +{} +``` + +### Properties + +_None_ diff --git a/enterprise/cli/features_test.go b/enterprise/cli/features_test.go index 1708807ddf..1679ae86ff 100644 --- a/enterprise/cli/features_test.go +++ b/enterprise/cli/features_test.go @@ -62,6 +62,5 @@ func TestFeaturesList(t *testing.T) { assert.Equal(t, codersdk.EntitlementNotEntitled, entitlements.Features[featureName].Entitlement) } assert.False(t, entitlements.HasLicense) - assert.False(t, entitlements.Experimental) }) } diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index 7c78367986..47393406d2 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -348,9 +348,8 @@ func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, r *http.Request) { } } httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Entitlements{ - Features: features, - Warnings: []string{testWarning}, - HasLicense: true, - Experimental: true, + Features: features, + Warnings: []string{testWarning}, + HasLicense: true, }) } diff --git a/enterprise/cli/provisionerdaemons.go b/enterprise/cli/provisionerdaemons.go index 8182b5c212..becf715924 100644 --- a/enterprise/cli/provisionerdaemons.go +++ b/enterprise/cli/provisionerdaemons.go @@ -15,7 +15,6 @@ import ( agpl "github.com/coder/coder/cli" "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd/database" "github.com/coder/coder/codersdk" "github.com/coder/coder/provisioner/terraform" @@ -149,7 +148,7 @@ func provisionerDaemonStart() *cobra.Command { }, } - cliflag.StringVarP(cmd.Flags(), &cacheDir, "cache-dir", "c", "CODER_CACHE_DIRECTORY", deployment.DefaultCacheDir(), + cliflag.StringVarP(cmd.Flags(), &cacheDir, "cache-dir", "c", "CODER_CACHE_DIRECTORY", codersdk.DefaultCacheDir(), "Specify a directory to cache provisioner job files.") cliflag.StringArrayVarP(cmd.Flags(), &rawTags, "tag", "t", "CODER_PROVISIONERD_TAGS", []string{}, "Specify a list of tags to target provisioner jobs.") diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index 31fbd55565..e9853cfdb5 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -14,7 +14,6 @@ import ( "tailscale.com/derp" "tailscale.com/types/key" - "github.com/coder/coder/cli/deployment" "github.com/coder/coder/cryptorand" "github.com/coder/coder/enterprise/audit" "github.com/coder/coder/enterprise/audit/backends" @@ -27,10 +26,9 @@ import ( ) func server() *cobra.Command { - vip := deployment.NewViper() - cmd := agpl.Server(vip, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { - if options.DeploymentConfig.DERP.Server.RelayURL.Value != "" { - _, err := url.Parse(options.DeploymentConfig.DERP.Server.RelayURL.Value) + cmd := agpl.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { + if options.DeploymentValues.DERP.Server.RelayURL.String() != "" { + _, err := url.Parse(options.DeploymentValues.DERP.Server.RelayURL.String()) if err != nil { return nil, nil, xerrors.Errorf("derp-server-relay-address must be a valid HTTP URL: %w", err) } @@ -53,7 +51,7 @@ func server() *cobra.Command { } options.DERPServer.SetMeshKey(meshKey) - if options.DeploymentConfig.AuditLogging.Value { + if options.DeploymentValues.AuditLogging.Value() { options.Auditor = audit.NewAuditor(audit.DefaultFilter, backends.NewPostgres(options.Database, true), backends.NewSlog(options.Logger), @@ -63,14 +61,13 @@ func server() *cobra.Command { options.TrialGenerator = trialer.New(options.Database, "https://v2-licensor.coder.com/trial", coderd.Keys) o := &coderd.Options{ - AuditLogging: options.DeploymentConfig.AuditLogging.Value, - BrowserOnly: options.DeploymentConfig.BrowserOnly.Value, - SCIMAPIKey: []byte(options.DeploymentConfig.SCIMAPIKey.Value), + AuditLogging: options.DeploymentValues.AuditLogging.Value(), + BrowserOnly: options.DeploymentValues.BrowserOnly.Value(), + SCIMAPIKey: []byte(options.DeploymentValues.SCIMAPIKey.Value()), RBAC: true, - DERPServerRelayAddress: options.DeploymentConfig.DERP.Server.RelayURL.Value, - DERPServerRegionID: options.DeploymentConfig.DERP.Server.RegionID.Value, - - Options: options, + DERPServerRelayAddress: options.DeploymentValues.DERP.Server.RelayURL.String(), + DERPServerRegionID: int(options.DeploymentValues.DERP.Server.RegionID.Value()), + Options: options, } api, err := coderd.New(ctx, o) @@ -79,8 +76,5 @@ func server() *cobra.Command { } return api.AGPL, api, nil }) - - deployment.AttachFlags(cmd.Flags(), vip, true) - return cmd } diff --git a/enterprise/cli/server_slim.go b/enterprise/cli/server_slim.go index 8aabd7c6b0..834280f8e5 100644 --- a/enterprise/cli/server_slim.go +++ b/enterprise/cli/server_slim.go @@ -6,22 +6,16 @@ import ( "context" "io" - "github.com/spf13/cobra" "golang.org/x/xerrors" - "github.com/coder/coder/cli/deployment" - agpl "github.com/coder/coder/cli" agplcoderd "github.com/coder/coder/coderd" + "github.com/spf13/cobra" ) func server() *cobra.Command { - vip := deployment.NewViper() - cmd := agpl.Server(vip, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { + cmd := agpl.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { return nil, nil, xerrors.Errorf("slim build does not support `coder server`") }) - - deployment.AttachFlags(cmd.Flags(), vip, true) - return cmd } diff --git a/enterprise/coderd/appearance.go b/enterprise/coderd/appearance.go index 7c230218e8..a784b678ea 100644 --- a/enterprise/coderd/appearance.go +++ b/enterprise/coderd/appearance.go @@ -87,10 +87,10 @@ func (api *API) appearance(rw http.ResponseWriter, r *http.Request) { } } - if len(api.DeploymentConfig.Support.Links.Value) == 0 { + if len(api.DeploymentValues.Support.Links.Value) == 0 { cfg.SupportLinks = DefaultSupportLinks } else { - cfg.SupportLinks = api.DeploymentConfig.Support.Links.Value + cfg.SupportLinks = api.DeploymentValues.Support.Links.Value } httpapi.Write(r.Context(), rw, http.StatusOK, cfg) @@ -119,7 +119,7 @@ func validateHexColor(color string) error { func (api *API) putAppearance(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - if !api.Authorize(r, rbac.ActionUpdate, rbac.ResourceDeploymentConfig) { + if !api.Authorize(r, rbac.ActionUpdate, rbac.ResourceDeploymentValues) { httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "Insufficient permissions to update appearance", }) diff --git a/enterprise/coderd/appearance_test.go b/enterprise/coderd/appearance_test.go index c1451c1193..b7772acee7 100644 --- a/enterprise/coderd/appearance_test.go +++ b/enterprise/coderd/appearance_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/coderd/coderdtest" "github.com/coder/coder/codersdk" "github.com/coder/coder/enterprise/coderd" @@ -89,15 +90,14 @@ func TestCustomSupportLinks(t *testing.T) { Icon: "bug", }, } - cfg := coderdtest.DeploymentConfig(t) - cfg.Support = new(codersdk.SupportConfig) - cfg.Support.Links = &codersdk.DeploymentConfigField[[]codersdk.LinkConfig]{ + cfg := coderdtest.DeploymentValues(t) + cfg.Support.Links = clibase.Struct[[]codersdk.LinkConfig]{ Value: supportLinks, } client := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ - DeploymentConfig: cfg, + DeploymentValues: cfg, }, }) coderdtest.CreateFirstUser(t, client) diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 96e2d048c4..aedcd49b1a 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -259,7 +259,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { return err } - if entitlements.RequireTelemetry && !api.DeploymentConfig.Telemetry.Enable.Value { + if entitlements.RequireTelemetry && !api.DeploymentValues.Telemetry.Enable.Value() { // We can't fail because then the user couldn't remove the offending // license w/o a restart. // @@ -272,8 +272,6 @@ func (api *API) updateEntitlements(ctx context.Context) error { return nil } - entitlements.Experimental = api.DeploymentConfig.Experimental.Value || len(api.AGPL.Experiments) != 0 - featureChanged := func(featureName codersdk.FeatureName) (changed bool, enabled bool) { if api.entitlements.Features == nil { return true, entitlements.Features[featureName].Enabled diff --git a/go.mod b/go.mod index f2d110e5cf..31fa7acc13 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ replace github.com/gliderlabs/ssh => github.com/coder/ssh v0.0.0-20220811105153- replace github.com/imulab/go-scim/pkg/v2 => github.com/coder/go-scim/pkg/v2 v2.0.0-20230221055123-1d63c1222136 require ( - cdr.dev/slog v1.4.2-0.20220525200111-18dce5c2cd5f + cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 cloud.google.com/go/compute/metadata v0.2.1 github.com/AlecAivazis/survey/v2 v2.3.5 github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d @@ -134,7 +134,6 @@ require ( github.com/spf13/afero v1.9.3 github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.8.1 github.com/swaggo/http-swagger v1.3.3 github.com/swaggo/swag v1.8.6 @@ -228,7 +227,6 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/elastic/go-windows v1.0.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.7.7 // indirect @@ -265,6 +263,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 // indirect github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect + github.com/iancoleman/strcase v0.2.0 github.com/illarion/gonotify v1.0.1 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -278,7 +277,6 @@ require ( github.com/kr/fs v0.1.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect @@ -303,7 +301,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/opencontainers/runc v1.1.2 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect github.com/pion/transport v0.13.1 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -317,7 +314,6 @@ require ( github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/subosito/gotenv v1.4.1 // indirect github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/tadvi/systray v0.0.0-20190226123456-11a2b8fa57af // indirect github.com/tailscale/certstore v0.1.1-0.20220316223106-78d6e1c49d8d // indirect @@ -355,7 +351,6 @@ require ( golang.zx2c4.com/wireguard/windows v0.5.3 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20221118155620-16455021b5e6 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v1.0.0 // indirect inet.af/peercred v0.0.0-20210906144145-0893ea02156a // indirect diff --git a/go.sum b/go.sum index 901c287100..66da404954 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= bitbucket.org/creachadair/shell v0.0.6/go.mod h1:8Qqi/cYk7vPnsOePHroKXDJYmb5x7ENhtiFtfZq8K+M= -cdr.dev/slog v1.4.2-0.20220525200111-18dce5c2cd5f h1:3bol05M9G8jeze6ylp+oReemDB0eeahsETzFVND0S3U= -cdr.dev/slog v1.4.2-0.20220525200111-18dce5c2cd5f/go.mod h1:wRFV/Qp1sEyUTLuhv8k/v97zRIpfGVR1mpWUmAOwREk= +cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04 h1:d5MQ+iI2zk7t0HrHwBP9p7k2XfRsXnRclSe8Kpp3xOo= +cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04/go.mod h1:YPVZsUbRMaLaPgme0RzlPWlC7fI7YmDj/j/kZLuvICs= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -179,7 +179,6 @@ github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVb github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= github.com/akutz/memconn v0.1.0 h1:NawI0TORU4hcOMsMr11g7vwlCdkYeLKXBcxWu2W/P8A= github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukDjH+Fw= -github.com/alecthomas/chroma v0.9.4/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -627,8 +626,6 @@ github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0X github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= @@ -1088,6 +1085,7 @@ github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/iancoleman/orderedmap v0.2.0 h1:sq1N/TFpYH++aViPcaKjys3bDClUEU7s5B+z6jq8pNA= +github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -1306,8 +1304,6 @@ github.com/mafredri/udp v0.1.2-0.20220805105907-b2872e92e98d/go.mod h1:GUd681aT3 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1576,8 +1572,6 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= @@ -1777,8 +1771,6 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= -github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU= -github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1805,8 +1797,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= @@ -2431,7 +2421,6 @@ golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= @@ -2844,8 +2833,6 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/pty/ptytest/ptytest.go b/pty/ptytest/ptytest.go index 35525038f7..75529bfa62 100644 --- a/pty/ptytest/ptytest.go +++ b/pty/ptytest/ptytest.go @@ -31,6 +31,8 @@ func New(t *testing.T, opts ...pty.Option) *PTY { return create(t, ptty, "cmd") } +// Start starts a new process asynchronously and returns a PTY and Process. +// It kills the process upon cleanup. func Start(t *testing.T, cmd *exec.Cmd, opts ...pty.StartOption) (*PTY, pty.Process) { t.Helper() diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 2499608132..689226d2f5 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -710,6 +710,8 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) { return TypescriptType{ValueType: "string"}, nil case "encoding/json.RawMessage": return TypescriptType{ValueType: "Record"}, nil + case "github.com/coder/coder/cli/clibase.URL": + return TypescriptType{ValueType: "string"}, nil } // Then see if the type is defined elsewhere. If it is, we can just diff --git a/scripts/clidocgen/gen.go b/scripts/clidocgen/gen.go index 051ab0f371..dd0b2dd247 100644 --- a/scripts/clidocgen/gen.go +++ b/scripts/clidocgen/gen.go @@ -140,14 +140,19 @@ func fmtDocFilename(cmd *cobra.Command) string { return fmt.Sprintf("%s.md", name) } -func generateDocsTree(rootCmd *cobra.Command, basePath string) error { - if rootCmd.Hidden { +func generateDocsTree(cmd *cobra.Command, basePath string) error { + if cmd.Hidden { + return nil + } + + if cmd.Name() == "server" { + // The server command is now managed by clibase and needs a new generator. return nil } // Write out root. fi, err := os.OpenFile( - filepath.Join(basePath, fmtDocFilename(rootCmd)), + filepath.Join(basePath, fmtDocFilename(cmd)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644, ) if err != nil { @@ -155,15 +160,15 @@ func generateDocsTree(rootCmd *cobra.Command, basePath string) error { } defer fi.Close() - err = writeCommand(fi, rootCmd) + err = writeCommand(fi, cmd) if err != nil { return err } - flog.Info("Generated docs for %q at %v", fullCommandName(rootCmd), fi.Name()) + flog.Info("Generated docs for %q at %v", fullCommandName(cmd), fi.Name()) // Recursively generate docs. - for _, subcommand := range rootCmd.Commands() { + for _, subcommand := range cmd.Commands() { err = generateDocsTree(subcommand, basePath) if err != nil { return err diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 1fa82a0ba8..76c0cefc32 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1,6 +1,7 @@ import axios, { AxiosRequestHeaders } from "axios" import dayjs from "dayjs" import * as Types from "./types" +import { DeploymentConfig } from "./types" import * as TypesGen from "./typesGenerated" export const hardCodedCSRFCookie = (): string => { @@ -684,7 +685,6 @@ export const getEntitlements = async (): Promise => { if (axios.isAxiosError(ex) && ex.response?.status === 404) { return { errors: [], - experimental: false, features: withDefaultFeatures({}), has_license: false, require_telemetry: false, @@ -806,11 +806,10 @@ export const getAgentListeningPorts = async ( return response.data } -export const getDeploymentConfig = - async (): Promise => { - const response = await axios.get(`/api/v2/config/deployment`) - return response.data - } +export const getDeploymentValues = async (): Promise => { + const response = await axios.get(`/api/v2/config/deployment`) + return response.data +} export const getReplicas = async (): Promise => { const response = await axios.get(`/api/v2/replicas`) diff --git a/site/src/api/types.ts b/site/src/api/types.ts index daf4e451ac..d0c31fe6f8 100644 --- a/site/src/api/types.ts +++ b/site/src/api/types.ts @@ -1,3 +1,5 @@ +import { DeploymentValues } from "./typesGenerated" + export interface UserAgent { readonly browser: string readonly device: string @@ -14,3 +16,25 @@ export interface ReconnectingPTYRequest { export type WorkspaceBuildTransition = "start" | "stop" | "delete" export type Message = { message: string } + +export interface DeploymentGroup { + readonly name: string + readonly parent?: DeploymentGroup + readonly description: string + readonly children: DeploymentGroup[] +} + +export interface DeploymentOption { + readonly name: string + readonly description: string + readonly flag: string + readonly flag_shorthand: string + readonly value: unknown + readonly hidden: boolean + readonly group?: DeploymentGroup +} + +export type DeploymentConfig = { + readonly config: DeploymentValues + readonly options: DeploymentOption[] +} diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 0fd6819f06..8e3556b060 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -272,8 +272,10 @@ export interface DERP { // From codersdk/deployment.go export interface DERPConfig { - readonly url: DeploymentConfigField - readonly path: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly url: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly path: string } // From codersdk/workspaceagents.go @@ -284,79 +286,25 @@ export interface DERPRegion { // From codersdk/deployment.go export interface DERPServerConfig { - readonly enable: DeploymentConfigField - readonly region_id: DeploymentConfigField - readonly region_code: DeploymentConfigField - readonly region_name: DeploymentConfigField - readonly stun_addresses: DeploymentConfigField - readonly relay_url: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Int64") + readonly region_id: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly region_code: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly region_name: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly stun_addresses: string[] + readonly relay_url: string } // From codersdk/deployment.go export interface DangerousConfig { - readonly allow_path_app_sharing: DeploymentConfigField - readonly allow_path_app_site_owner_access: DeploymentConfigField -} - -// From codersdk/deployment.go -export interface DeploymentConfig { - readonly access_url: DeploymentConfigField - readonly wildcard_access_url: DeploymentConfigField - readonly redirect_to_access_url: DeploymentConfigField - readonly http_address: DeploymentConfigField - readonly autobuild_poll_interval: DeploymentConfigField - readonly derp: DERP - readonly gitauth: DeploymentConfigField - readonly prometheus: PrometheusConfig - readonly pprof: PprofConfig - readonly proxy_trusted_headers: DeploymentConfigField - readonly proxy_trusted_origins: DeploymentConfigField - readonly cache_directory: DeploymentConfigField - readonly in_memory_database: DeploymentConfigField - readonly pg_connection_url: DeploymentConfigField - readonly oauth2: OAuth2Config - readonly oidc: OIDCConfig - readonly telemetry: TelemetryConfig - readonly tls: TLSConfig - readonly trace: TraceConfig - readonly secure_auth_cookie: DeploymentConfigField - readonly strict_transport_security: DeploymentConfigField - readonly strict_transport_security_options: DeploymentConfigField - readonly ssh_keygen_algorithm: DeploymentConfigField - readonly metrics_cache_refresh_interval: DeploymentConfigField - readonly agent_stat_refresh_interval: DeploymentConfigField - readonly agent_fallback_troubleshooting_url: DeploymentConfigField - readonly audit_logging: DeploymentConfigField - readonly browser_only: DeploymentConfigField - readonly scim_api_key: DeploymentConfigField - readonly provisioner: ProvisionerConfig - readonly rate_limit: RateLimitConfig - readonly experiments: DeploymentConfigField - readonly update_check: DeploymentConfigField - readonly max_token_lifetime: DeploymentConfigField - readonly swagger: SwaggerConfig - readonly logging: LoggingConfig - readonly dangerous: DangerousConfig - readonly disable_path_apps: DeploymentConfigField - readonly max_session_expiry: DeploymentConfigField - readonly disable_session_expiry_refresh: DeploymentConfigField - readonly disable_password_auth: DeploymentConfigField - readonly address: DeploymentConfigField - readonly experimental: DeploymentConfigField - readonly support: SupportConfig -} - -// From codersdk/deployment.go -export interface DeploymentConfigField { - readonly name: string - readonly usage: string - readonly flag: string - readonly shorthand: string - readonly enterprise: boolean - readonly hidden: boolean - readonly secret: boolean - readonly default: T - readonly value: T + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly allow_path_app_sharing: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly allow_path_app_site_owner_access: boolean } // From codersdk/deployment.go @@ -364,6 +312,87 @@ export interface DeploymentDAUsResponse { readonly entries: DAUEntry[] } +// From codersdk/deployment.go +export interface DeploymentValues { + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly verbose?: boolean + readonly access_url?: string + readonly wildcard_access_url?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly redirect_to_access_url?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly http_address?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly autobuild_poll_interval?: number + readonly derp?: DERP + readonly prometheus?: PrometheusConfig + readonly pprof?: PprofConfig + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly proxy_trusted_headers?: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly proxy_trusted_origins?: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly cache_directory?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly in_memory_database?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly pg_connection_url?: string + readonly oauth2?: OAuth2Config + readonly oidc?: OIDCConfig + readonly telemetry?: TelemetryConfig + readonly tls?: TLSConfig + readonly trace?: TraceConfig + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly secure_auth_cookie?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Int64") + readonly strict_transport_security?: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly strict_transport_security_options?: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly ssh_keygen_algorithm?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly metrics_cache_refresh_interval?: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly agent_stat_refresh_interval?: number + readonly agent_fallback_troubleshooting_url?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly audit_logging?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly browser_only?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly scim_api_key?: string + readonly provisioner?: ProvisionerConfig + readonly rate_limit?: RateLimitConfig + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly experiments?: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly update_check?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly max_token_lifetime?: number + readonly swagger?: SwaggerConfig + readonly logging?: LoggingConfig + readonly dangerous?: DangerousConfig + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly disable_path_apps?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly max_session_expiry?: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly disable_session_expiry_refresh?: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly disable_password_auth?: boolean + readonly support?: SupportConfig + // Named type "github.com/coder/coder/cli/clibase.Struct[[]github.com/coder/coder/codersdk.GitAuthConfig]" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly git_auth?: any + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly config?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly write_config?: boolean + // Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly address?: any +} + // From codersdk/deployment.go export interface Entitlements { readonly features: Record @@ -372,7 +401,6 @@ export interface Entitlements { readonly has_license: boolean readonly trial: boolean readonly require_telemetry: boolean - readonly experimental: boolean } // From codersdk/deployment.go @@ -453,9 +481,12 @@ export interface LinkConfig { // From codersdk/deployment.go export interface LoggingConfig { - readonly human: DeploymentConfigField - readonly json: DeploymentConfigField - readonly stackdriver: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly human: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly json: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly stackdriver: string } // From codersdk/users.go @@ -476,13 +507,20 @@ export interface OAuth2Config { // From codersdk/deployment.go export interface OAuth2GithubConfig { - readonly client_id: DeploymentConfigField - readonly client_secret: DeploymentConfigField - readonly allowed_orgs: DeploymentConfigField - readonly allowed_teams: DeploymentConfigField - readonly allow_signups: DeploymentConfigField - readonly allow_everyone: DeploymentConfigField - readonly enterprise_base_url: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_id: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_secret: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly allowed_orgs: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly allowed_teams: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly allow_signups: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly allow_everyone: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly enterprise_base_url: string } // From codersdk/users.go @@ -493,16 +531,25 @@ export interface OIDCAuthMethod extends AuthMethod { // From codersdk/deployment.go export interface OIDCConfig { - readonly allow_signups: DeploymentConfigField - readonly client_id: DeploymentConfigField - readonly client_secret: DeploymentConfigField - readonly email_domain: DeploymentConfigField - readonly issuer_url: DeploymentConfigField - readonly scopes: DeploymentConfigField - readonly ignore_email_verified: DeploymentConfigField - readonly username_field: DeploymentConfigField - readonly sign_in_text: DeploymentConfigField - readonly icon_url: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly allow_signups: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_id: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_secret: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly email_domain: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly issuer_url: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly scopes: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly ignore_email_verified: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly username_field: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly sign_in_text: string + readonly icon_url: string } // From codersdk/organizations.go @@ -573,22 +620,32 @@ export interface PatchGroupRequest { // From codersdk/deployment.go export interface PprofConfig { - readonly enable: DeploymentConfigField - readonly address: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly address: any } // From codersdk/deployment.go export interface PrometheusConfig { - readonly enable: DeploymentConfigField - readonly address: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly address: any } // From codersdk/deployment.go export interface ProvisionerConfig { - readonly daemons: DeploymentConfigField - readonly daemon_poll_interval: DeploymentConfigField - readonly daemon_poll_jitter: DeploymentConfigField - readonly force_cancel_interval: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Int64") + readonly daemons: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly daemon_poll_interval: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly daemon_poll_jitter: number + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Duration") + readonly force_cancel_interval: number } // From codersdk/provisionerdaemons.go @@ -632,8 +689,10 @@ export interface PutExtendWorkspaceRequest { // From codersdk/deployment.go export interface RateLimitConfig { - readonly disable_all: DeploymentConfigField - readonly api: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly disable_all: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Int64") + readonly api: number } // From codersdk/replicas.go @@ -676,33 +735,49 @@ export interface ServiceBannerConfig { // From codersdk/deployment.go export interface SupportConfig { - readonly links: DeploymentConfigField + // Named type "github.com/coder/coder/cli/clibase.Struct[[]github.com/coder/coder/codersdk.LinkConfig]" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly links: any } // From codersdk/deployment.go export interface SwaggerConfig { - readonly enable: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean } // From codersdk/deployment.go export interface TLSConfig { - readonly enable: DeploymentConfigField - readonly address: DeploymentConfigField - readonly redirect_http: DeploymentConfigField - readonly cert_file: DeploymentConfigField - readonly client_auth: DeploymentConfigField - readonly client_ca_file: DeploymentConfigField - readonly key_file: DeploymentConfigField - readonly min_version: DeploymentConfigField - readonly client_cert_file: DeploymentConfigField - readonly client_key_file: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any" + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO explain why this is needed + readonly address: any + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly redirect_http: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly cert_file: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_auth: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_ca_file: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Strings") + readonly key_file: string[] + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly min_version: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_cert_file: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly client_key_file: string } // From codersdk/deployment.go export interface TelemetryConfig { - readonly enable: DeploymentConfigField - readonly trace: DeploymentConfigField - readonly url: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly trace: boolean + readonly url: string } // From codersdk/templates.go @@ -834,9 +909,12 @@ export interface TokensFilter { // From codersdk/deployment.go export interface TraceConfig { - readonly enable: DeploymentConfigField - readonly honeycomb_api_key: DeploymentConfigField - readonly capture_logs: DeploymentConfigField + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly enable: boolean + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.String") + readonly honeycomb_api_key: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.Bool") + readonly capture_logs: boolean } // From codersdk/templates.go @@ -1390,12 +1468,3 @@ export const WorkspaceTransitions: WorkspaceTransition[] = [ "start", "stop", ] - -// From codersdk/deployment.go -export type Flaggable = - | string - | number - | boolean - | string[] - | GitAuthConfig[] - | LinkConfig[] diff --git a/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx b/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx index a203666185..3b04e4fce2 100644 --- a/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx +++ b/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx @@ -5,15 +5,16 @@ import { Sidebar } from "./Sidebar" import { createContext, Suspense, useContext, FC } from "react" import { useMachine } from "@xstate/react" import { Loader } from "components/Loader/Loader" -import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated" +import { DeploymentDAUsResponse } from "api/typesGenerated" import { deploymentConfigMachine } from "xServices/deploymentConfig/deploymentConfigMachine" import { RequirePermission } from "components/RequirePermission/RequirePermission" import { usePermissions } from "hooks/usePermissions" import { Outlet } from "react-router-dom" +import { DeploymentConfig } from "api/types" type DeploySettingsContextValue = { - deploymentConfig: DeploymentConfig - getDeploymentConfigError: unknown + deploymentValues: DeploymentConfig + getDeploymentValuesError: unknown deploymentDAUs?: DeploymentDAUsResponse getDeploymentDAUsError: unknown } @@ -36,24 +37,24 @@ export const DeploySettingsLayout: FC = () => { const [state] = useMachine(deploymentConfigMachine) const styles = useStyles() const { - deploymentConfig, + deploymentValues, deploymentDAUs, - getDeploymentConfigError, + getDeploymentValuesError, getDeploymentDAUsError, } = state.context const permissions = usePermissions() return ( - +
- {deploymentConfig ? ( + {deploymentValues ? ( { return Not set } -export const OptionValue: FC = ({ children }) => { +export const OptionValue: FC<{ children?: ReactNode | unknown }> = ({ + children, +}) => { const styles = useStyles() if (typeof children === "boolean") { return children ? : } + if (typeof children === "number") { + return {children} + } + + if (typeof children === "string") { + return {children} + } + if (Array.isArray(children)) { if (children.length === 0) { return @@ -46,7 +56,7 @@ export const OptionValue: FC = ({ children }) => { return } - return {children} + return {JSON.stringify(children)} } const useStyles = makeStyles((theme) => ({ diff --git a/site/src/components/DeploySettingsLayout/OptionsTable.tsx b/site/src/components/DeploySettingsLayout/OptionsTable.tsx index 4a1b04973c..efe99034d8 100644 --- a/site/src/components/DeploySettingsLayout/OptionsTable.tsx +++ b/site/src/components/DeploySettingsLayout/OptionsTable.tsx @@ -5,19 +5,24 @@ import TableCell from "@material-ui/core/TableCell" import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" -import { DeploymentConfigField, Flaggable } from "api/typesGenerated" +import { DeploymentOption } from "api/types" import { OptionDescription, OptionName, OptionValue, } from "components/DeploySettingsLayout/Option" import { FC } from "react" +import { DisabledBadge } from "./Badges" const OptionsTable: FC<{ - options: Record> + options: DeploymentOption[] }> = ({ options }) => { const styles = useStyles() + if (options.length === 0) { + return + } + return ( @@ -29,15 +34,18 @@ const OptionsTable: FC<{ {Object.values(options).map((option) => { + if (option.value === null || option.value === "") { + return null + } return ( {option.name} - {option.usage} + {option.description} - {option.value.toString()} + {option.value} ) diff --git a/site/src/components/Navbar/Navbar.tsx b/site/src/components/Navbar/Navbar.tsx index f46e6bfccb..3e25eed4cb 100644 --- a/site/src/components/Navbar/Navbar.tsx +++ b/site/src/components/Navbar/Navbar.tsx @@ -14,7 +14,7 @@ export const Navbar: FC = () => { const featureVisibility = useFeatureVisibility() const canViewAuditLog = featureVisibility["audit_log"] && Boolean(permissions.viewAuditLog) - const canViewDeployment = Boolean(permissions.viewDeploymentConfig) + const canViewDeployment = Boolean(permissions.viewDeploymentValues) const onSignOut = () => authSend("SIGN_OUT") return ( diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx index d122890072..781b72a5a6 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPage.tsx @@ -5,7 +5,7 @@ import { pageTitle } from "util/page" import { GeneralSettingsPageView } from "./GeneralSettingsPageView" const GeneralSettingsPage: FC = () => { - const { deploymentConfig, deploymentDAUs, getDeploymentDAUsError } = + const { deploymentValues, deploymentDAUs, getDeploymentDAUsError } = useDeploySettings() return ( @@ -14,7 +14,7 @@ const GeneralSettingsPage: FC = () => { {pageTitle("General Settings")} diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx index 35cec9b290..5341be792c 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.stories.tsx @@ -12,21 +12,21 @@ export default { title: "pages/GeneralSettingsPageView", component: GeneralSettingsPageView, argTypes: { - deploymentConfig: { - defaultValue: { - access_url: { + deploymentOptions: { + defaultValue: [ + { name: "Access URL", usage: "External URL to access your deployment. This must be accessible by all provisioned workspaces.", value: "https://dev.coder.com", }, - wildcard_access_url: { + { name: "Wildcard Access URL", usage: 'Specifies the wildcard hostname to use for workspace applications in the form "*.example.com".', value: "*--apps.dev.coder.com", }, - }, + ], }, deploymentDAUs: { defaultValue: MockDeploymentDAUResponse, diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx index 0b4acc28b8..54ec4740e8 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage/GeneralSettingsPageView.tsx @@ -1,17 +1,19 @@ -import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated" +import { DeploymentOption } from "api/types" +import { DeploymentDAUsResponse } from "api/typesGenerated" import { AlertBanner } from "components/AlertBanner/AlertBanner" import { DAUChart } from "components/DAUChart/DAUChart" import { Header } from "components/DeploySettingsLayout/Header" import OptionsTable from "components/DeploySettingsLayout/OptionsTable" import { Stack } from "components/Stack/Stack" +import { useDeploymentOptions } from "util/deployOptions" export type GeneralSettingsPageViewProps = { - deploymentConfig: Pick + deploymentOptions: DeploymentOption[] deploymentDAUs?: DeploymentDAUsResponse getDeploymentDAUsError: unknown } export const GeneralSettingsPageView = ({ - deploymentConfig, + deploymentOptions, deploymentDAUs, getDeploymentDAUsError, }: GeneralSettingsPageViewProps): JSX.Element => { @@ -28,10 +30,11 @@ export const GeneralSettingsPageView = ({ )} {deploymentDAUs && } diff --git a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPage.tsx b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPage.tsx index ecbb193726..ff0a59153b 100644 --- a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPage.tsx @@ -5,7 +5,7 @@ import { pageTitle } from "util/page" import { GitAuthSettingsPageView } from "./GitAuthSettingsPageView" const GitAuthSettingsPage: FC = () => { - const { deploymentConfig: deploymentConfig } = useDeploySettings() + const { deploymentValues: deploymentValues } = useDeploySettings() return ( <> @@ -13,7 +13,7 @@ const GitAuthSettingsPage: FC = () => { {pageTitle("Git Authentication Settings")} - + ) } diff --git a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx index 530981a6ca..a8b0738363 100644 --- a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.stories.tsx @@ -8,18 +8,16 @@ export default { title: "pages/GitAuthSettingsPageView", component: GitAuthSettingsPageView, argTypes: { - deploymentConfig: { + config: { defaultValue: { - gitauth: { - name: "Git Auth", - usage: "Automatically authenticate Git inside workspaces.", - value: [ - { - id: "123", - client_id: "575", - }, - ], - }, + git_auth: [ + { + id: "0000-1111", + type: "GitHub", + client_id: "client_id", + regex: "regex", + }, + ], }, }, }, diff --git a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx index de283cdf57..771977473b 100644 --- a/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/GitAuthSettingsPage/GitAuthSettingsPageView.tsx @@ -5,17 +5,17 @@ import TableCell from "@material-ui/core/TableCell" import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" -import { DeploymentConfig } from "api/typesGenerated" +import { DeploymentValues, GitAuthConfig } from "api/typesGenerated" import { AlertBanner } from "components/AlertBanner/AlertBanner" import { EnterpriseBadge } from "components/DeploySettingsLayout/Badges" import { Header } from "components/DeploySettingsLayout/Header" export type GitAuthSettingsPageViewProps = { - deploymentConfig: Pick + config: DeploymentValues } export const GitAuthSettingsPageView = ({ - deploymentConfig, + config, }: GitAuthSettingsPageViewProps): JSX.Element => { const styles = useStyles() @@ -57,7 +57,7 @@ export const GitAuthSettingsPageView = ({ - {deploymentConfig.gitauth.value.length === 0 && ( + {((config.git_auth === null || config.git_auth.length === 0) && (
@@ -65,18 +65,17 @@ export const GitAuthSettingsPageView = ({
- )} - - {deploymentConfig.gitauth.value.map((git) => { - const name = git.id || git.type - return ( - - {name} - {git.client_id} - {git.regex || "Not Set"} - - ) - })} + )) || + config.git_auth.map((git: GitAuthConfig) => { + const name = git.id || git.type + return ( + + {name} + {git.client_id} + {git.regex || "Not Set"} + + ) + })}
diff --git a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage.tsx b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage.tsx index fad3beccb5..b97e3c7b4e 100644 --- a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPage.tsx @@ -5,7 +5,7 @@ import { pageTitle } from "util/page" import { NetworkSettingsPageView } from "./NetworkSettingsPageView" const NetworkSettingsPage: FC = () => { - const { deploymentConfig: deploymentConfig } = useDeploySettings() + const { deploymentValues: deploymentValues } = useDeploySettings() return ( <> @@ -13,7 +13,7 @@ const NetworkSettingsPage: FC = () => { {pageTitle("Network Settings")} - + ) } diff --git a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.stories.tsx index 7f2d30b68c..3400de4a21 100644 --- a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.stories.tsx @@ -8,41 +8,50 @@ export default { title: "pages/NetworkSettingsPageView", component: NetworkSettingsPageView, argTypes: { - deploymentConfig: { - defaultValue: { - derp: { - server: { - enable: { - name: "DERP Server Enable", - usage: - "Whether to enable or disable the embedded DERP relay server.", - value: true, - }, - region_name: { - name: "DERP Server Region Name", - usage: "Region name that for the embedded DERP server.", - value: "aws-east", - }, - stun_addresses: { - name: "DERP Server STUN Addresses", - usage: - "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", - value: ["stun.l.google.com:19302", "stun.l.google.com:19301"], - }, - }, - config: { - url: { - name: "DERP Config URL", - usage: - "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", - value: "https://coder.com", - }, + options: { + defaultValue: [ + { + name: "DERP Server Enable", + usage: "Whether to enable or disable the embedded DERP relay server.", + value: true, + group: { + name: "Networking", }, }, - wildcard_access_url: { + { + name: "DERP Server Region Name", + usage: "Region name that for the embedded DERP server.", + value: "aws-east", + group: { + name: "Networking", + }, + }, + { + name: "DERP Server STUN Addresses", + usage: + "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", + value: ["stun.l.google.com:19302", "stun.l.google.com:19301"], + group: { + name: "Networking", + }, + }, + { + name: "DERP Config URL", + usage: + "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", value: "https://coder.com", + group: { + name: "Networking", + }, }, - }, + { + name: "Wildcard Access URL", + value: "https://coder.com", + group: { + name: "Networking", + }, + }, + ], }, }, } as ComponentMeta diff --git a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.tsx index 480b5d2d1c..f53d0f925f 100644 --- a/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/NetworkSettingsPage/NetworkSettingsPageView.tsx @@ -1,4 +1,4 @@ -import { DeploymentConfig } from "api/typesGenerated" +import { DeploymentOption } from "api/types" import { Badges, EnabledBadge, @@ -7,13 +7,17 @@ import { import { Header } from "components/DeploySettingsLayout/Header" import OptionsTable from "components/DeploySettingsLayout/OptionsTable" import { Stack } from "components/Stack/Stack" +import { + deploymentGroupHasParent, + useDeploymentOptions, +} from "util/deployOptions" export type NetworkSettingsPageViewProps = { - deploymentConfig: Pick + options: DeploymentOption[] } export const NetworkSettingsPageView = ({ - deploymentConfig, + options: options, }: NetworkSettingsPageViewProps): JSX.Element => (
@@ -23,13 +27,9 @@ export const NetworkSettingsPageView = ({ docsHref="https://coder.com/docs/coder-oss/latest/networking" /> + deploymentGroupHasParent(o.group, "Networking"), + )} />
@@ -42,7 +42,8 @@ export const NetworkSettingsPageView = ({ /> - {deploymentConfig.wildcard_access_url.value !== "" ? ( + {useDeploymentOptions(options, "Wildcard Access URL")[0].value !== + "" ? ( ) : ( diff --git a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx index 410164fb58..c296d18d12 100644 --- a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPage.tsx @@ -6,7 +6,7 @@ import { pageTitle } from "util/page" import { SecuritySettingsPageView } from "./SecuritySettingsPageView" const SecuritySettingsPage: FC = () => { - const { deploymentConfig: deploymentConfig } = useDeploySettings() + const { deploymentValues: deploymentValues } = useDeploySettings() const { entitlements } = useDashboard() return ( @@ -16,7 +16,7 @@ const SecuritySettingsPage: FC = () => { = (args) => ( ) export const Page = Template.bind({}) + +export const NoTLS = Template.bind({}) +NoTLS.args = { + options: [ + { + name: "SSH Keygen Algorithm", + value: "1234", + } as DeploymentOption, + { + name: "Secure Auth Cookie", + value: "1234", + } as DeploymentOption, + ], +} diff --git a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPageView.tsx b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPageView.tsx index 01a98389cb..bb80e5de87 100644 --- a/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/SecuritySettingsPage/SecuritySettingsPageView.tsx @@ -1,4 +1,4 @@ -import { DeploymentConfig } from "api/typesGenerated" +import { DeploymentOption } from "api/types" import { Badges, DisabledBadge, @@ -8,17 +8,18 @@ import { import { Header } from "components/DeploySettingsLayout/Header" import OptionsTable from "components/DeploySettingsLayout/OptionsTable" import { Stack } from "components/Stack/Stack" +import { + deploymentGroupHasParent, + useDeploymentOptions, +} from "util/deployOptions" export type SecuritySettingsPageViewProps = { - deploymentConfig: Pick< - DeploymentConfig, - "tls" | "ssh_keygen_algorithm" | "secure_auth_cookie" - > + options: DeploymentOption[] featureAuditLogEnabled: boolean featureBrowserOnlyEnabled: boolean } export const SecuritySettingsPageView = ({ - deploymentConfig, + options: options, featureAuditLogEnabled, featureBrowserOnlyEnabled, }: SecuritySettingsPageViewProps): JSX.Element => ( @@ -31,10 +32,11 @@ export const SecuritySettingsPageView = ({ /> @@ -74,12 +76,9 @@ export const SecuritySettingsPageView = ({ /> + deploymentGroupHasParent(o.group, "TLS"), + )} />
diff --git a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage.tsx b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage.tsx index 13e58139ff..1cbfcb3b20 100644 --- a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPage.tsx @@ -5,7 +5,7 @@ import { pageTitle } from "util/page" import { UserAuthSettingsPageView } from "./UserAuthSettingsPageView" const UserAuthSettingsPage: FC = () => { - const { deploymentConfig: deploymentConfig } = useDeploySettings() + const { deploymentValues: deploymentValues } = useDeploySettings() return ( <> @@ -13,7 +13,7 @@ const UserAuthSettingsPage: FC = () => { {pageTitle("User Authentication Settings")} - + ) } diff --git a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.stories.tsx b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.stories.tsx index c772d0f720..4d43bfdc26 100644 --- a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.stories.tsx +++ b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.stories.tsx @@ -8,69 +8,92 @@ export default { title: "pages/UserAuthSettingsPageView", component: UserAuthSettingsPageView, argTypes: { - deploymentConfig: { - defaultValue: { - oidc: { - client_id: { - name: "OIDC Client ID", - usage: "Client ID to use for Login with OIDC.", - value: "1234", - }, - allow_signups: { - name: "OIDC Allow Signups", - usage: "Whether new users can sign up with OIDC.", - value: true, - }, - email_domain: { - name: "OIDC Email Domain", - usage: - "Email domains that clients logging in with OIDC must match.", - value: "@coder.com", - }, - issuer_url: { - name: "OIDC Issuer URL", - usage: "Issuer URL to use for Login with OIDC.", - value: "https://coder.com", - }, - scopes: { - name: "OIDC Scopes", - usage: "Scopes to grant when authenticating with OIDC.", - value: ["idk"], + options: { + defaultValue: [ + { + name: "OIDC Client ID", + usage: "Client ID to use for Login with OIDC.", + value: "1234", + group: { + name: "OIDC", }, }, - oauth2: { - github: { - client_id: { - name: "OAuth2 GitHub Client ID", - usage: "Client ID for Login with GitHub.", - value: "1224", - }, - allow_signups: { - name: "OAuth2 GitHub Allow Signups", - usage: "Whether new users can sign up with GitHub.", - value: true, - }, - enterprise_base_url: { - name: "OAuth2 GitHub Enterprise Base URL", - usage: - "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", - value: "https://google.com", - }, - allowed_orgs: { - name: "OAuth2 GitHub Allowed Orgs", - usage: - "Organizations the user must be a member of to Login with GitHub.", - value: true, - }, - allowed_teams: { - name: "OAuth2 GitHub Allowed Teams", - usage: - "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", - value: true, - }, + { + name: "OIDC Allow Signups", + usage: "Whether new users can sign up with OIDC.", + value: true, + group: { + name: "OIDC", }, }, - }, + { + name: "OIDC Email Domain", + usage: "Email domains that clients logging in with OIDC must match.", + value: "@coder.com", + group: { + name: "OIDC", + }, + }, + { + name: "OIDC Issuer URL", + usage: "Issuer URL to use for Login with OIDC.", + value: "https://coder.com", + group: { + name: "OIDC", + }, + }, + { + name: "OIDC Scopes", + usage: "Scopes to grant when authenticating with OIDC.", + value: ["idk"], + group: { + name: "OIDC", + }, + }, + { + name: "OAuth2 GitHub Client ID", + usage: "Client ID for Login with GitHub.", + value: "1224", + group: { + name: "GitHub", + }, + }, + { + name: "OAuth2 GitHub Allow Signups", + usage: "Whether new users can sign up with GitHub.", + value: true, + group: { + name: "GitHub", + }, + }, + { + name: "OAuth2 GitHub Enterprise Base URL", + usage: + "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", + value: "https://google.com", + group: { + name: "GitHub", + }, + }, + { + name: "OAuth2 GitHub Allowed Orgs", + usage: + "Organizations the user must be a member of to Login with GitHub.", + value: true, + group: { + name: "GitHub", + }, + }, + { + name: "OAuth2 GitHub Allowed Teams", + usage: + "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", + value: true, + group: { + name: "GitHub", + }, + }, + ], }, }, } as ComponentMeta diff --git a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.tsx b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.tsx index 7d574d4543..300dd76231 100644 --- a/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.tsx +++ b/site/src/pages/DeploySettingsPage/UserAuthSettingsPage/UserAuthSettingsPageView.tsx @@ -1,4 +1,4 @@ -import { DeploymentConfig } from "api/typesGenerated" +import { DeploymentOption } from "api/types" import { Badges, DisabledBadge, @@ -7,13 +7,17 @@ import { import { Header } from "components/DeploySettingsLayout/Header" import OptionsTable from "components/DeploySettingsLayout/OptionsTable" import { Stack } from "components/Stack/Stack" +import { + deploymentGroupHasParent, + useDeploymentOptions, +} from "util/deployOptions" export type UserAuthSettingsPageViewProps = { - deploymentConfig: Pick + options: DeploymentOption[] } export const UserAuthSettingsPageView = ({ - deploymentConfig, + options, }: UserAuthSettingsPageViewProps): JSX.Element => ( <> @@ -28,7 +32,7 @@ export const UserAuthSettingsPageView = ({ /> - {deploymentConfig.oidc.client_id.value ? ( + {useDeploymentOptions(options, "OIDC Client ID")[0].value ? ( ) : ( @@ -36,13 +40,9 @@ export const UserAuthSettingsPageView = ({ + deploymentGroupHasParent(o.group, "OIDC"), + )} /> @@ -55,7 +55,7 @@ export const UserAuthSettingsPageView = ({ /> - {deploymentConfig.oauth2.github.client_id.value ? ( + {useDeploymentOptions(options, "OAuth2 GitHub Client ID")[0].value ? ( ) : ( @@ -63,14 +63,9 @@ export const UserAuthSettingsPageView = ({ + deploymentGroupHasParent(o.group, "GitHub"), + )} /> diff --git a/site/src/testHelpers/entities.ts b/site/src/testHelpers/entities.ts index b524553cb7..84a2099a33 100644 --- a/site/src/testHelpers/entities.ts +++ b/site/src/testHelpers/entities.ts @@ -1224,7 +1224,6 @@ export const MockEntitlements: TypesGen.Entitlements = { warnings: [], has_license: false, features: withDefaultFeatures({}), - experimental: false, require_telemetry: false, trial: false, } @@ -1233,7 +1232,6 @@ export const MockEntitlementsWithWarnings: TypesGen.Entitlements = { errors: [], warnings: ["You are over your active user limit.", "And another thing."], has_license: true, - experimental: false, trial: false, require_telemetry: false, features: withDefaultFeatures({ @@ -1258,7 +1256,6 @@ export const MockEntitlementsWithAuditLog: TypesGen.Entitlements = { errors: [], warnings: [], has_license: true, - experimental: false, require_telemetry: false, trial: false, features: withDefaultFeatures({ @@ -1446,7 +1443,7 @@ export const MockPermissions: Permissions = { readAllUsers: true, updateUsers: true, viewAuditLog: true, - viewDeploymentConfig: true, + viewDeploymentValues: true, viewUpdateCheck: true, } diff --git a/site/src/util/deployOptions.ts b/site/src/util/deployOptions.ts new file mode 100644 index 0000000000..990cddbbee --- /dev/null +++ b/site/src/util/deployOptions.ts @@ -0,0 +1,41 @@ +import { useMemo } from "react" +import { DeploymentGroup, DeploymentOption } from "./../api/types" + +const deploymentOptions = ( + options: DeploymentOption[], + ...names: string[] +): DeploymentOption[] => { + const found: DeploymentOption[] = [] + for (const name of names) { + const option = options.find((o) => o.name === name) + if (option) { + found.push(option) + } else { + throw new Error(`Deployment option ${name} not found`) + } + } + return found +} + +export const useDeploymentOptions = ( + options: DeploymentOption[], + ...names: string[] +): DeploymentOption[] => { + return useMemo(() => deploymentOptions(options, ...names), [options, names]) +} + +export const deploymentGroupHasParent = ( + group: DeploymentGroup | undefined, + parent: string, +): boolean => { + if (!group) { + return false + } + if (group.parent) { + return deploymentGroupHasParent(group.parent, parent) + } + if (group.name === parent) { + return true + } + return false +} diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index de3d367459..9094d71928 100644 --- a/site/src/xServices/auth/authXService.ts +++ b/site/src/xServices/auth/authXService.ts @@ -14,7 +14,7 @@ export const checks = { createTemplates: "createTemplates", deleteTemplates: "deleteTemplates", viewAuditLog: "viewAuditLog", - viewDeploymentConfig: "viewDeploymentConfig", + viewDeploymentValues: "viewDeploymentValues", createGroup: "createGroup", viewUpdateCheck: "viewUpdateCheck", } as const @@ -56,7 +56,7 @@ export const permissionsToCheck = { }, action: "read", }, - [checks.viewDeploymentConfig]: { + [checks.viewDeploymentValues]: { object: { resource_type: "deployment_flags", }, diff --git a/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts b/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts index 2bf7aa6e5a..343bdd7fd3 100644 --- a/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts +++ b/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts @@ -1,6 +1,7 @@ -import { getDeploymentConfig, getDeploymentDAUs } from "api/api" -import { DeploymentConfig, DeploymentDAUsResponse } from "api/typesGenerated" +import { DeploymentDAUsResponse } from "./../../api/typesGenerated" +import { getDeploymentValues, getDeploymentDAUs } from "api/api" import { createMachine, assign } from "xstate" +import { DeploymentConfig } from "api/types" export const deploymentConfigMachine = createMachine( { @@ -9,14 +10,14 @@ export const deploymentConfigMachine = createMachine( schema: { context: {} as { - deploymentConfig?: DeploymentConfig - getDeploymentConfigError?: unknown + deploymentValues?: DeploymentConfig + getDeploymentValuesError?: unknown deploymentDAUs?: DeploymentDAUsResponse getDeploymentDAUsError?: unknown }, events: {} as { type: "LOAD" }, services: {} as { - getDeploymentConfig: { + getDeploymentValues: { data: DeploymentConfig } getDeploymentDAUs: { @@ -29,14 +30,14 @@ export const deploymentConfigMachine = createMachine( states: { config: { invoke: { - src: "getDeploymentConfig", + src: "getDeploymentValues", onDone: { target: "daus", - actions: ["assignDeploymentConfig"], + actions: ["assignDeploymentValues"], }, onError: { target: "daus", - actions: ["assignGetDeploymentConfigError"], + actions: ["assignGetDeploymentValuesError"], }, }, tags: "loading", @@ -62,15 +63,15 @@ export const deploymentConfigMachine = createMachine( }, { services: { - getDeploymentConfig: getDeploymentConfig, + getDeploymentValues: getDeploymentValues, getDeploymentDAUs: getDeploymentDAUs, }, actions: { - assignDeploymentConfig: assign({ - deploymentConfig: (_, { data }) => data, + assignDeploymentValues: assign({ + deploymentValues: (_, { data }) => data, }), - assignGetDeploymentConfigError: assign({ - getDeploymentConfigError: (_, { data }) => data, + assignGetDeploymentValuesError: assign({ + getDeploymentValuesError: (_, { data }) => data, }), assignDeploymentDAUs: assign({ deploymentDAUs: (_, { data }) => data, diff --git a/testutil/temp.go b/testutil/temp.go index 4f17552415..539ea052d6 100644 --- a/testutil/temp.go +++ b/testutil/temp.go @@ -22,6 +22,12 @@ func TempFile(t *testing.T, dir, pattern string) string { err = os.Remove(name) require.NoError(t, err, "remove temp file") + t.Cleanup(func() { + // The test might have created created and it may have already removed it, + // so we ignore the error. + _ = os.Remove(name) + }) + return name }