From 0d08b844123f54c47f3fa4114432e59ec7f7398c Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 19:39:26 +0000 Subject: [PATCH 01/35] big wip --- cli/clibase/yaml.go | 118 +++++++++++++++++++++++++++++++++++++++ cli/clibase/yaml_test.go | 66 +++++++++++++++++++++- cli/server.go | 4 ++ 3 files changed, 187 insertions(+), 1 deletion(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 82abee342f783..1854163ef500a 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -1,6 +1,8 @@ package clibase import ( + "errors" + "github.com/iancoleman/strcase" "github.com/mitchellh/go-wordwrap" "golang.org/x/xerrors" @@ -41,6 +43,8 @@ func deepMapNode(n *yaml.Node, path []string, headComment string) *yaml.Node { // // The node is returned to enable post-processing higher up in // the stack. +// +// It is isomorphic with FromYAML. func (s OptionSet) ToYAML() (*yaml.Node, error) { root := yaml.Node{ Kind: yaml.MappingNode, @@ -103,3 +107,117 @@ func (s OptionSet) ToYAML() (*yaml.Node, error) { } return &root, nil } + +// FromYAML converts the given YAML node into the option set. +// It is isomorphic with ToYAML. + +func (s *OptionSet) FromYAML(n *yaml.Node) error { + return fromYAML(*s, nil, n) +} + +func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { + if n.Kind == yaml.DocumentNode && ofGroup == nil { + // The root may be a document node. + if len(n.Content) != 1 { + return xerrors.Errorf("expected one content node, got %d", len(n.Content)) + } + return fromYAML(os, ofGroup, n.Content[0]) + } + + if n.Kind != yaml.MappingNode { + byt, _ := yaml.Marshal(n) + return xerrors.Errorf("expected mapping node, got type %v, contents:\n%v", n.Kind, string(byt)) + } + + var ( + subGroupsByName = make(map[string]*Group) + optionsByName = make(map[string]*Option) + ) + for i, opt := range os { + if opt.YAML == "" { + continue + } + + // We only want to process options that are of the identified group, + // even if that group is nil. + if opt.Group != ofGroup { + if opt.Group.Parent == ofGroup { + subGroupsByName[opt.Group.Name] = opt.Group + } + continue + } + + if _, ok := optionsByName[opt.YAML]; ok { + return xerrors.Errorf("duplicate option name %q", opt.YAML) + } + + optionsByName[opt.YAML] = &os[i] + } + + for k := range subGroupsByName { + if _, ok := optionsByName[k]; !ok { + continue + } + return xerrors.Errorf("there is both an option and a group with name %q", k) + } + + var ( + name string + merr error + ) + + for i, item := range n.Content { + if isName := i%2 == 0; isName { + if item.Kind != yaml.ScalarNode { + return xerrors.Errorf("expected scalar node for name, got %v", item.Kind) + } + name = item.Value + continue + } + + switch item.Kind { + case yaml.MappingNode: + // Item is either a group or an option with a complex object. + if opt, ok := optionsByName[name]; ok { + unmarshaler, ok := opt.Value.(yaml.Unmarshaler) + if !ok { + return xerrors.Errorf("complex option %q must support unmarshaling", opt.Name) + } + err := unmarshaler.UnmarshalYAML(item) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) + } + continue + } + if g, ok := subGroupsByName[name]; ok { + // Group, recurse. + err := fromYAML(os, g, item) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.FullName(), err)) + } + continue + } + merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q", name)) + case yaml.ScalarNode: + opt, ok := optionsByName[name] + if !ok { + merr = errors.Join(merr, xerrors.Errorf("unknown option %q", name)) + continue + } + + unmarshaler, ok := opt.Value.(yaml.Unmarshaler) + if !ok { + err := opt.Value.Set(item.Value) + merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.Name, err)) + continue + } + err := unmarshaler.UnmarshalYAML(item) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) + } + default: + return xerrors.Errorf("unexpected kind for value %v", item.Kind) + } + } + return merr +} diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 3efad6ee54ed8..594430d97874e 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -3,13 +3,15 @@ package clibase_test import ( "testing" + "github.com/spf13/pflag" "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" "gopkg.in/yaml.v3" "github.com/coder/coder/cli/clibase" ) -func TestOption_ToYAML(t *testing.T) { +func TestOptionSet_ToYAML(t *testing.T) { t.Parallel() t.Run("RequireKey", func(t *testing.T) { @@ -55,3 +57,65 @@ func TestOption_ToYAML(t *testing.T) { t.Logf("Raw YAML:\n%s", string(byt)) }) } + +func TestOptionSet_YAMLIsomorphism(t *testing.T) { + t.Parallel() + for _, tc := range []struct { + name string + os clibase.OptionSet + zeroValue func() pflag.Value + }{ + { + name: "SimpleString", + os: clibase.OptionSet{ + { + Name: "Workspace Name", + Default: "billie", + Description: "The workspace's name.", + Group: &clibase.Group{Name: "Names"}, + YAML: "workspaceName", + }, + }, + zeroValue: func() pflag.Value { + return clibase.StringOf(new(string)) + }, + }, + } { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + for i := range tc.os { + tc.os[i].Value = tc.zeroValue() + } + err := tc.os.SetDefaults() + require.NoError(t, err) + + y, err := tc.os.ToYAML() + require.NoError(t, err) + + toByt, err := yaml.Marshal(y) + require.NoError(t, err) + + t.Logf("Raw YAML:\n%s", string(toByt)) + + var y2 yaml.Node + err = yaml.Unmarshal(toByt, &y2) + require.NoError(t, err) + + os2 := slices.Clone(tc.os) + for i := range os2 { + os2[i].Value = tc.zeroValue() + } + + // os2 values should be zeroed whereas tc.os should be + // set to defaults. + // This makes sure we aren't mixing pointers. + require.NotEqual(t, tc.os, os2) + err = os2.FromYAML(&y2) + require.NoError(t, err) + + require.Equal(t, tc.os, os2) + }) + } +} diff --git a/cli/server.go b/cli/server.go index b6fa7c31b647c..c4c7242c9c119 100644 --- a/cli/server.go +++ b/cli/server.go @@ -190,6 +190,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. return nil } + if cfg.Config != "" { + cliui.Warnf(inv.Stderr, "YAML support is experimental and offers no compatibility guarantees.") + } + // Print deprecation warnings. for _, opt := range opts { if opt.UseInstead == nil { From fce8e5fb2cfcd02f10ea133409c5316118e9d6ca Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 19:45:56 +0000 Subject: [PATCH 02/35] SimpleString isomorphism!!! --- cli/clibase/clibase.go | 1 + cli/clibase/yaml.go | 20 ++++++++++++-------- cli/clibase/yaml_test.go | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cli/clibase/clibase.go b/cli/clibase/clibase.go index bdad2e97c36a6..9fc44ea37f36b 100644 --- a/cli/clibase/clibase.go +++ b/cli/clibase/clibase.go @@ -16,6 +16,7 @@ import ( type Group struct { Parent *Group `json:"parent,omitempty"` Name string `json:"name,omitempty"` + YAMLName string `json:"yaml_name,omitempty"` Children []Group `json:"children,omitempty"` Description string `json:"description,omitempty"` } diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 1854163ef500a..ba8c23de2723c 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -3,7 +3,6 @@ package clibase import ( "errors" - "github.com/iancoleman/strcase" "github.com/mitchellh/go-wordwrap" "golang.org/x/xerrors" "gopkg.in/yaml.v3" @@ -82,14 +81,14 @@ func (s OptionSet) ToYAML() (*yaml.Node, error) { } var group []string for _, g := range opt.Group.Ancestry() { - if g.Name == "" { + if g.YAMLName == "" { return nil, xerrors.Errorf( - "group name is empty for %q, groups: %+v", + "group yaml name is empty for %q, groups: %+v", opt.Name, opt.Group, ) } - group = append(group, strcase.ToLowerCamel(g.Name)) + group = append(group, g.YAMLName) } var groupDesc string if opt.Group != nil { @@ -141,8 +140,11 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { // We only want to process options that are of the identified group, // even if that group is nil. if opt.Group != ofGroup { - if opt.Group.Parent == ofGroup { - subGroupsByName[opt.Group.Name] = opt.Group + if opt.Group != nil && opt.Group.Parent == ofGroup { + if opt.Group.YAMLName == "" { + return xerrors.Errorf("group yaml name is empty for %q, groups: %+v", opt.Name, opt.Group) + } + subGroupsByName[opt.Group.YAMLName] = opt.Group } continue } @@ -193,7 +195,7 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { // Group, recurse. err := fromYAML(os, g, item) if err != nil { - merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.FullName(), err)) + merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.YAMLName, err)) } continue } @@ -208,7 +210,9 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { unmarshaler, ok := opt.Value.(yaml.Unmarshaler) if !ok { err := opt.Value.Set(item.Value) - merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.Name, err)) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.Name, err)) + } continue } err := unmarshaler.UnmarshalYAML(item) diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 594430d97874e..f2673fc0a8d52 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -72,7 +72,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { Name: "Workspace Name", Default: "billie", Description: "The workspace's name.", - Group: &clibase.Group{Name: "Names"}, + Group: &clibase.Group{YAMLName: "names"}, YAML: "workspaceName", }, }, From 28444971007696d36d2ab69275d04a2df434fd5e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 20:02:20 +0000 Subject: [PATCH 03/35] Support scalars --- cli/clibase/clibase.go | 2 +- cli/clibase/yaml.go | 65 ++++++++++++++++++++++++++++++---------- cli/clibase/yaml_test.go | 16 ++++++++-- 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/cli/clibase/clibase.go b/cli/clibase/clibase.go index 9fc44ea37f36b..e133296c8c39f 100644 --- a/cli/clibase/clibase.go +++ b/cli/clibase/clibase.go @@ -16,7 +16,7 @@ import ( type Group struct { Parent *Group `json:"parent,omitempty"` Name string `json:"name,omitempty"` - YAMLName string `json:"yaml_name,omitempty"` + YAML string `json:"yaml_name,omitempty"` Children []Group `json:"children,omitempty"` Description string `json:"description,omitempty"` } diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index ba8c23de2723c..98a808b574c22 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -74,21 +74,43 @@ func (s OptionSet) ToYAML() (*yaml.Node, error) { ) } } else { - valueNode = yaml.Node{ - Kind: yaml.ScalarNode, - Value: opt.Value.String(), + // The all-other types case. + // + // A bit of a hack, we marshal and then unmarshal to get + // the underlying node. + byt, err := yaml.Marshal(opt.Value) + if err != nil { + return nil, xerrors.Errorf( + "marshal %q: %w", opt.Name, err, + ) } + + var docNode yaml.Node + err = yaml.Unmarshal(byt, &docNode) + if err != nil { + return nil, xerrors.Errorf( + "unmarshal %q: %w", opt.Name, err, + ) + } + if len(docNode.Content) != 1 { + return nil, xerrors.Errorf( + "unmarshal %q: expected one node, got %d", + opt.Name, len(docNode.Content), + ) + } + + valueNode = *docNode.Content[0] } var group []string for _, g := range opt.Group.Ancestry() { - if g.YAMLName == "" { + if g.YAML == "" { return nil, xerrors.Errorf( "group yaml name is empty for %q, groups: %+v", opt.Name, opt.Group, ) } - group = append(group, g.YAMLName) + group = append(group, g.YAML) } var groupDesc string if opt.Group != nil { @@ -141,10 +163,10 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { // even if that group is nil. if opt.Group != ofGroup { if opt.Group != nil && opt.Group.Parent == ofGroup { - if opt.Group.YAMLName == "" { + if opt.Group.YAML == "" { return xerrors.Errorf("group yaml name is empty for %q, groups: %+v", opt.Name, opt.Group) } - subGroupsByName[opt.Group.YAMLName] = opt.Group + subGroupsByName[opt.Group.YAML] = opt.Group } continue } @@ -195,30 +217,41 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { // Group, recurse. err := fromYAML(os, g, item) if err != nil { - merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.YAMLName, err)) + merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.YAML, err)) } continue } merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q", name)) - case yaml.ScalarNode: + case yaml.ScalarNode, yaml.SequenceNode: opt, ok := optionsByName[name] if !ok { merr = errors.Join(merr, xerrors.Errorf("unknown option %q", name)) continue } - unmarshaler, ok := opt.Value.(yaml.Unmarshaler) - if !ok { + unmarshaler, _ := opt.Value.(yaml.Unmarshaler) + switch { + case unmarshaler == nil && item.Kind == yaml.ScalarNode: err := opt.Value.Set(item.Value) if err != nil { merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.Name, err)) } - continue - } - err := unmarshaler.UnmarshalYAML(item) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) + case unmarshaler == nil && item.Kind == yaml.SequenceNode: + // Item is an option with a slice value. + err := item.Decode(opt.Value) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("decode %q: %w", opt.Name, err)) + } + case unmarshaler != nil: + err := unmarshaler.UnmarshalYAML(item) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) + } + default: + panic("unreachable?") } + continue + default: return xerrors.Errorf("unexpected kind for value %v", item.Kind) } diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index f2673fc0a8d52..80600145d5408 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -11,7 +11,7 @@ import ( "github.com/coder/coder/cli/clibase" ) -func TestOptionSet_ToYAML(t *testing.T) { +func TestOptionSet_YAML(t *testing.T) { t.Parallel() t.Run("RequireKey", func(t *testing.T) { @@ -72,7 +72,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { Name: "Workspace Name", Default: "billie", Description: "The workspace's name.", - Group: &clibase.Group{YAMLName: "names"}, + Group: &clibase.Group{YAML: "names"}, YAML: "workspaceName", }, }, @@ -80,6 +80,18 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { return clibase.StringOf(new(string)) }, }, + { + name: "Array", + os: clibase.OptionSet{ + { + YAML: "names", + Default: "jill,jack,joan", + }, + }, + zeroValue: func() pflag.Value { + return clibase.StringArrayOf(&[]string{}) + }, + }, } { tc := tc t.Run(tc.name, func(t *testing.T) { From 4960df8a16fef50ab651e8cf9b6e8a40bdf99447 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 20:09:14 +0000 Subject: [PATCH 04/35] ComplexObjects work! --- cli/clibase/yaml_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 80600145d5408..253f685734453 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -60,6 +60,12 @@ func TestOptionSet_YAML(t *testing.T) { func TestOptionSet_YAMLIsomorphism(t *testing.T) { t.Parallel() + //nolint:unused + type kid struct { + Name string `yaml:"name"` + Age int `yaml:"age"` + } + for _, tc := range []struct { name string os clibase.OptionSet @@ -92,6 +98,21 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { return clibase.StringArrayOf(&[]string{}) }, }, + { + name: "ComplexObject", + os: clibase.OptionSet{ + { + YAML: "kids", + Default: `- name: jill + age: 12 +- name: jack + age: 13`, + }, + }, + zeroValue: func() pflag.Value { + return &clibase.Struct[[]kid]{} + }, + }, } { tc := tc t.Run(tc.name, func(t *testing.T) { From 2a2c926a77c826482a3719ea86c6c1ae815f80db Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 22:26:52 +0000 Subject: [PATCH 05/35] golden files work! --- Makefile | 2 +- cli/clibase/yaml.go | 4 + cli/server_test.go | 30 +++ cli/testdata/server-config.yaml.golden | 326 +++++++++++++++++++++++++ codersdk/deployment.go | 16 ++ 5 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 cli/testdata/server-config.yaml.golden diff --git a/Makefile b/Makefile index 0e8508c2a81aa..e251cd40576c3 100644 --- a/Makefile +++ b/Makefile @@ -518,7 +518,7 @@ update-golden-files: cli/testdata/.gen-golden helm/tests/testdata/.gen-golden .PHONY: update-golden-files cli/testdata/.gen-golden: $(wildcard cli/testdata/*.golden) $(wildcard cli/*.tpl) $(GO_SRC_FILES) - go test ./cli -run=TestCommandHelp -update + go test ./cli -run=Test\(CommandHelp\|ServerConfig\) -update touch "$@" helm/tests/testdata/.gen-golden: $(wildcard helm/tests/testdata/*.golden) $(GO_SRC_FILES) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 98a808b574c22..eaa264c73a30e 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -171,6 +171,10 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { continue } + if opt.Group.YAML == "" { + return xerrors.Errorf("group yaml name is empty for %q", opt.Name) + } + if _, ok := optionsByName[opt.YAML]; ok { return xerrors.Errorf("duplicate option name %q", opt.YAML) } diff --git a/cli/server_test.go b/cli/server_test.go index 3512601a1c31e..28836adb0856f 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -18,6 +18,7 @@ import ( "net/http/httptest" "net/url" "os" + "path/filepath" "runtime" "strconv" "strings" @@ -29,6 +30,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/goleak" + "gopkg.in/yaml.v3" "github.com/coder/coder/cli" "github.com/coder/coder/cli/clitest" @@ -1464,3 +1466,31 @@ func waitAccessURL(t *testing.T, cfg config.Root) *url.URL { return accessURL } + +func TestServerConfig(t *testing.T) { + t.Parallel() + + var deployValues codersdk.DeploymentValues + opts := deployValues.Options() + + err := opts.SetDefaults() + require.NoError(t, err) + + n, err := opts.ToYAML() + require.NoError(t, err) + + wantByt, err := yaml.Marshal(n) + require.NoError(t, err) + + goldenPath := filepath.Join("testdata", "server-config.yaml.golden") + + if *updateGoldenFiles { + require.NoError(t, os.WriteFile(goldenPath, wantByt, 0o600)) + return + } + + got, err := os.ReadFile(goldenPath) + require.NoError(t, err) + + require.Equal(t, string(wantByt), string(got)) +} diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden new file mode 100644 index 0000000000000..807127420b5c9 --- /dev/null +++ b/cli/testdata/server-config.yaml.golden @@ -0,0 +1,326 @@ +networking: + # The URL that users will use to access the Coder deployment. + accessURL: + scheme: "" + opaque: "" + user: null + host: "" + path: "" + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: "" + rawfragment: "" + # Specifies the wildcard hostname to use for workspace applications in the form + # "*.example.com". + wildcardAccessURL: + scheme: "" + opaque: "" + user: null + host: "" + path: "" + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: "" + rawfragment: "" + # Specifies whether to redirect requests that do not match the access URL host. + redirectToAccessURL: false + http: + # HTTP bind address of the server. Unset to disable the HTTP endpoint. + httpAddress: 127.0.0.1:3000 + # The maximum lifetime duration users can specify when creating an API token. + maxTokenLifetime: 3155760000000000000 + # 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. + sessionDuration: 86400000000000 + # Disable automatic session expiry bumping due to activity. This forces all + # sessions to become invalid after the session expiry duration has been reached. + disableSessionExpiryRefresh: false + # 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. + disablePasswordAuth: false + # 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. + tls: + # HTTPS bind address of the server. + address: + host: 127.0.0.1 + port: "3443" + # Whether TLS will be enabled. + enable: false + # 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. + redirectHTTP: true + # 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. + certFiles: [] + # PEM-encoded Certificate Authority file used for checking the authenticity of + # client. + clientCAFile: "" + # Policy the server will follow for TLS Client Authentication. Accepted values are + # "none", "request", "require-any", "verify-if-given", or "require-and-verify". + clientAuth: none + # Paths to the private keys for each of the certificates. It requires a + # PEM-encoded file. + keyFiles: [] + # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" + # or "tls13". + minVersion: tls12 + # Path to certificate for client TLS authentication. It requires a PEM-encoded + # file. + clientCertFile: "" + # Path to key for client TLS authentication. It requires a PEM-encoded file. + clientKeyFile: "" + # 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. + strictTransportSecurity: 0 + # 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. + strictTransportSecurityOptions: [] + # 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. + derp: + # Whether to enable or disable the embedded DERP relay server. + enable: true + # Region ID to use for the embedded DERP server. + regionID: 999 + # Region code to use for the embedded DERP server. + regionCode: coder + # Region name that for the embedded DERP server. + regionName: Coder Embedded Relay + # Addresses for STUN servers to establish P2P connections. Set empty to disable + # P2P connections. + stunAddresses: + - stun.l.google.com:19302 + # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required + # for high availability. + relayURL: + scheme: "" + opaque: "" + user: null + host: "" + path: "" + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: "" + rawfragment: "" + # URL to fetch a DERP mapping on startup. See: + # https://tailscale.com/kb/1118/custom-derp-servers/. + url: "" + # Path to read a DERP mapping from. See: + # https://tailscale.com/kb/1118/custom-derp-servers/. + configPath: "" + # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, + # True-Client-Ip, X-Forwarded-For. + proxyTrustedHeaders: [] + # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. + proxyTrustedOrigins: [] + # Controls if the 'Secure' property is set on browser session cookies. + secureAuthCookie: false + # Whether Coder only allows connections to workspaces via the browser. + browserOnly: false +# Interval to poll for scheduled workspace builds. +autobuildPollInterval: 60000000000 +introspection: + prometheus: + # Serve prometheus metrics on the address defined by prometheus address. + enable: false + # The bind address to serve prometheus metrics. + address: + host: 127.0.0.1 + port: "2112" + pprof: + # Serve pprof metrics on the address defined by pprof address. + enable: false + # The bind address to serve pprof. + address: + host: 127.0.0.1 + port: "6060" + tracing: + # 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. + enable: false + # 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. + captureLogs: false + logging: + # Output debug-level logs. + verbose: false + # Output human-readable logs to a given file. + humanPath: /dev/stderr + # Output JSON logs to a given file. + jsonPath: "" + # Output Stackdriver compatible logs to a given file. + stackdriverPath: "" +oauth2: + github: + # Client ID for Login with GitHub. + clientID: "" + # Organizations the user must be a member of to Login with GitHub. + allowedOrgs: [] + # Teams inside organizations the user must be a member of to Login with GitHub. + # Structured as: /. + allowedTeams: [] + # Whether new users can sign up with GitHub. + allowSignups: false + # Allow all logins, setting this option means allowed orgs and teams must be + # empty. + allowEveryone: false + # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. + enterpriseBaseURL: "" +oidc: + # Whether new users can sign up with OIDC. + allowSignups: true + # Client ID to use for Login with OIDC. + clientID: "" + # Email domains that clients logging in with OIDC must match. + emailDomain: [] + # Issuer URL to use for Login with OIDC. + issuerURL: "" + # Scopes to grant when authenticating with OIDC. + scopes: + - openid + - profile + - email + # Ignore the email_verified claim from the upstream provider. + ignoreEmailVerified: false + # OIDC claim field to use as the username. + usernameField: preferred_username + # OIDC claim field to use as the email. + emailField: email + # OIDC auth URL parameters to pass to the upstream provider. + authURLParams: + access_type: offline + # Change the OIDC default 'groups' claim field. By default, will be 'groups' if + # present in the oidc scopes argument. + groupField: "" + # A map of OIDC group IDs and the group in Coder it should map to. This is useful + # for when OIDC providers only return group IDs. + groupMapping: {} + # The text to show on the OpenID Connect sign in button. + signInText: OpenID Connect + # URL pointing to the icon to use on the OepnID Connect login button. + iconURL: + scheme: "" + opaque: "" + user: null + host: "" + path: "" + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: "" + rawfragment: "" +# 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. +telemetry: + # Whether telemetry is enabled or not. Coder collects anonymized usage data to + # help improve our product. + enable: false + # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized + # application tracing to help improve our product. Disabling telemetry also + # disables this option. + trace: false + # URL to send telemetry. + url: + scheme: https + opaque: "" + user: null + host: telemetry.coder.com + path: "" + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: "" + rawfragment: "" +# Tune the behavior of the provisioner, which is responsible for creating, +# updating, and deleting workspace resources. +provisioning: + # Number of provisioner daemons to create on start. If builds are stuck in queued + # state for a long time, consider increasing this. + daemons: 3 + # Time to wait before polling for a new job. + daemonPollInterval: 1000000000 + # Random jitter added to the poll interval. + daemonPollJitter: 100000000 + # Time to force cancel provisioning tasks that are stuck. + forceCancelInterval: 600000000000 +# 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. +experiments: [] +# Periodically check for new releases of Coder and inform the owner. The check is +# performed once per day. +updateCheck: false +# Expose the swagger endpoint via /swagger. +enableSwagger: false +# The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is +# set, it will be used for compatibility with systemd. +cacheDir: /home/coder/.cache/coder +# Controls whether data will be stored in an in-memory database. +inMemoryDatabase: false +# The algorithm to use for generating ssh keys. Accepted values are "ed25519", +# "ecdsa", or "rsa4096". +sshKeygenAlgorithm: ed25519 +# URL to use for agent troubleshooting when not set in the template. +agentFallbackTroubleshootingURL: + scheme: https + opaque: "" + user: null + host: coder.com + path: /docs/coder-oss/latest/templates + rawpath: "" + omithost: false + forcequery: false + rawquery: "" + fragment: troubleshooting-templates + rawfragment: "" +# Specifies whether audit logging is enabled. +auditLogging: true +# 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. +disablePathApps: false +# These options change the behavior of how clients interact with the Coder. +# Clients include the coder cli, vs code extension, and the web UI. +client: + # The SSH deployment prefix is used in the Host of the ssh config. + sshHostnamePrefix: coder. + # These SSH config options will override the default SSH config options. Provide + # options in "key=value" or "key value" format separated by commas.Using this + # incorrectly can break SSH to your deployment, use cautiously. + sshConfigOptions: [] +# Support links to display in the top right drop down menu. +supportLinks: [] +# Git Authentication providers. +gitAuthProviders: [] +# Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By +# default, this will pick the best available wgtunnel server hosted by Coder. e.g. +# "tunnel.example.com". +wgtunnelHost: "" diff --git a/codersdk/deployment.go b/codersdk/deployment.go index ebace3488709d..c90033a9fc1c8 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -364,6 +364,7 @@ func (c *DeploymentValues) Options() clibase.OptionSet { var ( deploymentGroupNetworking = clibase.Group{ Name: "Networking", + YAML: "networking", } deploymentGroupNetworkingTLS = clibase.Group{ Parent: &deploymentGroupNetworking, @@ -371,10 +372,12 @@ func (c *DeploymentValues) Options() clibase.OptionSet { 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.`, + YAML: "tls", } deploymentGroupNetworkingHTTP = clibase.Group{ Parent: &deploymentGroupNetworking, Name: "HTTP", + YAML: "http", } deploymentGroupNetworkingDERP = clibase.Group{ Parent: &deploymentGroupNetworking, @@ -383,40 +386,50 @@ func (c *DeploymentValues) Options() clibase.OptionSet { 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.`, + YAML: "derp", } deploymentGroupIntrospection = clibase.Group{ Name: "Introspection", Description: `Configure logging, tracing, and metrics exporting.`, + YAML: "introspection", } deploymentGroupIntrospectionPPROF = clibase.Group{ Parent: &deploymentGroupIntrospection, Name: "pprof", + YAML: "pprof", } deploymentGroupIntrospectionPrometheus = clibase.Group{ Parent: &deploymentGroupIntrospection, Name: "Prometheus", + YAML: "prometheus", } deploymentGroupIntrospectionTracing = clibase.Group{ Parent: &deploymentGroupIntrospection, Name: "Tracing", + YAML: "tracing", } deploymentGroupIntrospectionLogging = clibase.Group{ Parent: &deploymentGroupIntrospection, Name: "Logging", + YAML: "logging", } deploymentGroupOAuth2 = clibase.Group{ Name: "OAuth2", Description: `Configure login and user-provisioning with GitHub via oAuth2.`, + YAML: "oauth2", } deploymentGroupOAuth2GitHub = clibase.Group{ Parent: &deploymentGroupOAuth2, Name: "GitHub", + YAML: "github", } deploymentGroupOIDC = clibase.Group{ Name: "OIDC", + YAML: "oidc", } deploymentGroupTelemetry = clibase.Group{ Name: "Telemetry", + YAML: "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.`, @@ -424,14 +437,17 @@ 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.`, + YAML: "provisioning", } deploymentGroupDangerous = clibase.Group{ Name: "⚠️ Dangerous", + YAML: "dangerous", } deploymentGroupClient = clibase.Group{ Name: "Client", Description: "These options change the behavior of how clients interact with the Coder. " + "Clients include the coder cli, vs code extension, and the web UI.", + YAML: "client", } deploymentGroupConfig = clibase.Group{ Name: "Config", From 056437f4d4965f9c735ada1b7043ac9fedb328ee Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 22:33:26 +0000 Subject: [PATCH 06/35] Fixup YAML types --- cli/clibase/values.go | 33 +++++++++ cli/testdata/server-config.yaml.golden | 94 +++----------------------- 2 files changed, 44 insertions(+), 83 deletions(-) diff --git a/cli/clibase/values.go b/cli/clibase/values.go index ff3d8a743779e..bc30c3aaef7e3 100644 --- a/cli/clibase/values.go +++ b/cli/clibase/values.go @@ -194,6 +194,17 @@ func (Duration) Type() string { return "duration" } +func (d *Duration) MarshalYAML() (interface{}, error) { + return yaml.Node{ + Kind: yaml.ScalarNode, + Value: d.String(), + }, nil +} + +func (d *Duration) UnmarshalYAML(n *yaml.Node) error { + return d.Set(n.Value) +} + type URL url.URL func URLOf(u *url.URL) *URL { @@ -214,6 +225,17 @@ func (u *URL) String() string { return uu.String() } +func (u *URL) MarshalYAML() (interface{}, error) { + return yaml.Node{ + Kind: yaml.ScalarNode, + Value: u.String(), + }, nil +} + +func (u *URL) UnmarshalYAML(n *yaml.Node) error { + return u.Set(n.Value) +} + func (u *URL) MarshalJSON() ([]byte, error) { return json.Marshal(u.String()) } @@ -277,6 +299,17 @@ func (hp *HostPort) UnmarshalJSON(b []byte) error { return hp.Set(s) } +func (hp *HostPort) MarshalYAML() (interface{}, error) { + return yaml.Node{ + Kind: yaml.ScalarNode, + Value: hp.String(), + }, nil +} + +func (hp *HostPort) UnmarshalYAML(n *yaml.Node) error { + return hp.Set(n.Value) +} + func (*HostPort) Type() string { return "host:port" } diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 807127420b5c9..3cfe82f04dde3 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -1,42 +1,20 @@ networking: # The URL that users will use to access the Coder deployment. accessURL: - scheme: "" - opaque: "" - user: null - host: "" - path: "" - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: "" - rawfragment: "" # Specifies the wildcard hostname to use for workspace applications in the form # "*.example.com". wildcardAccessURL: - scheme: "" - opaque: "" - user: null - host: "" - path: "" - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: "" - rawfragment: "" # Specifies whether to redirect requests that do not match the access URL host. redirectToAccessURL: false http: # HTTP bind address of the server. Unset to disable the HTTP endpoint. httpAddress: 127.0.0.1:3000 # The maximum lifetime duration users can specify when creating an API token. - maxTokenLifetime: 3155760000000000000 + maxTokenLifetime: 876600h0m0s # 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. - sessionDuration: 86400000000000 + sessionDuration: 24h0m0s # Disable automatic session expiry bumping due to activity. This forces all # sessions to become invalid after the session expiry duration has been reached. disableSessionExpiryRefresh: false @@ -52,9 +30,7 @@ networking: # secure link, you can safely ignore these settings. tls: # HTTPS bind address of the server. - address: - host: 127.0.0.1 - port: "3443" + address: 127.0.0.1:3443 # Whether TLS will be enabled. enable: false # Whether HTTP requests will be redirected to the access URL (if it's a https URL @@ -112,17 +88,6 @@ networking: # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required # for high availability. relayURL: - scheme: "" - opaque: "" - user: null - host: "" - path: "" - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: "" - rawfragment: "" # URL to fetch a DERP mapping on startup. See: # https://tailscale.com/kb/1118/custom-derp-servers/. url: "" @@ -139,22 +104,18 @@ networking: # Whether Coder only allows connections to workspaces via the browser. browserOnly: false # Interval to poll for scheduled workspace builds. -autobuildPollInterval: 60000000000 +autobuildPollInterval: 1m0s introspection: prometheus: # Serve prometheus metrics on the address defined by prometheus address. enable: false # The bind address to serve prometheus metrics. - address: - host: 127.0.0.1 - port: "2112" + address: 127.0.0.1:2112 pprof: # Serve pprof metrics on the address defined by pprof address. enable: false # The bind address to serve pprof. - address: - host: 127.0.0.1 - port: "6060" + address: 127.0.0.1:6060 tracing: # Whether application tracing data is collected. It exports to a backend # configured by environment variables. See: @@ -223,17 +184,6 @@ oidc: signInText: OpenID Connect # URL pointing to the icon to use on the OepnID Connect login button. iconURL: - scheme: "" - opaque: "" - user: null - host: "" - path: "" - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: "" - rawfragment: "" # 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. @@ -246,18 +196,7 @@ telemetry: # disables this option. trace: false # URL to send telemetry. - url: - scheme: https - opaque: "" - user: null - host: telemetry.coder.com - path: "" - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: "" - rawfragment: "" + url: https://telemetry.coder.com # Tune the behavior of the provisioner, which is responsible for creating, # updating, and deleting workspace resources. provisioning: @@ -265,11 +204,11 @@ provisioning: # state for a long time, consider increasing this. daemons: 3 # Time to wait before polling for a new job. - daemonPollInterval: 1000000000 + daemonPollInterval: 1s # Random jitter added to the poll interval. - daemonPollJitter: 100000000 + daemonPollJitter: 100ms # Time to force cancel provisioning tasks that are stuck. - forceCancelInterval: 600000000000 + forceCancelInterval: 10m0s # 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. @@ -288,18 +227,7 @@ inMemoryDatabase: false # "ecdsa", or "rsa4096". sshKeygenAlgorithm: ed25519 # URL to use for agent troubleshooting when not set in the template. -agentFallbackTroubleshootingURL: - scheme: https - opaque: "" - user: null - host: coder.com - path: /docs/coder-oss/latest/templates - rawpath: "" - omithost: false - forcequery: false - rawquery: "" - fragment: troubleshooting-templates - rawfragment: "" +agentFallbackTroubleshootingURL: https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates # Specifies whether audit logging is enabled. auditLogging: true # Disable workspace apps that are not served from subdomains. Path-based apps can From c2428ffd7c5bba9ecfa3ee5f8f355a0aa883b797 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 30 Mar 2023 22:40:37 +0000 Subject: [PATCH 07/35] give default value in comment --- cli/clibase/yaml.go | 12 +- cli/testdata/server-config.yaml.golden | 169 ++++++++++++++----------- 2 files changed, 105 insertions(+), 76 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index eaa264c73a30e..050155211b311 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -2,6 +2,7 @@ package clibase import ( "errors" + "fmt" "github.com/mitchellh/go-wordwrap" "golang.org/x/xerrors" @@ -53,10 +54,19 @@ func (s OptionSet) ToYAML() (*yaml.Node, error) { if opt.YAML == "" { continue } + + defValue := opt.Default + if defValue == "" { + defValue = "" + } + comment := wordwrap.WrapString( + fmt.Sprintf("%s (default: %s)", opt.Description, defValue), + 80, + ) nameNode := yaml.Node{ Kind: yaml.ScalarNode, Value: opt.YAML, - HeadComment: wordwrap.WrapString(opt.Description, 80), + HeadComment: wordwrap.WrapString(comment, 80), } var valueNode yaml.Node if m, ok := opt.Value.(yaml.Marshaler); ok { diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 3cfe82f04dde3..0a72b08a60820 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -1,71 +1,77 @@ networking: - # The URL that users will use to access the Coder deployment. + # The URL that users will use to access the Coder deployment. (default: ) accessURL: # Specifies the wildcard hostname to use for workspace applications in the form - # "*.example.com". + # "*.example.com". (default: ) wildcardAccessURL: # Specifies whether to redirect requests that do not match the access URL host. + # (default: ) redirectToAccessURL: false http: - # HTTP bind address of the server. Unset to disable the HTTP endpoint. + # HTTP bind address of the server. Unset to disable the HTTP endpoint. (default: + # 127.0.0.1:3000) httpAddress: 127.0.0.1:3000 # The maximum lifetime duration users can specify when creating an API token. + # (default: 876600h0m0s) maxTokenLifetime: 876600h0m0s # 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. + # --disable-session-expiry-refresh. (default: 24h0m0s) sessionDuration: 24h0m0s # Disable automatic session expiry bumping due to activity. This forces all # sessions to become invalid after the session expiry duration has been reached. + # (default: ) disableSessionExpiryRefresh: false # 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. + # directly in the database. (default: ) disablePasswordAuth: false # 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. tls: - # HTTPS bind address of the server. + # HTTPS bind address of the server. (default: 127.0.0.1:3443) address: 127.0.0.1:3443 - # Whether TLS will be enabled. + # Whether TLS will be enabled. (default: ) enable: false # 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. + # regardless of this setting. (default: true) redirectHTTP: true # 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. + # combined file. (default: ) certFiles: [] # PEM-encoded Certificate Authority file used for checking the authenticity of - # client. + # client. (default: ) clientCAFile: "" # Policy the server will follow for TLS Client Authentication. Accepted values are # "none", "request", "require-any", "verify-if-given", or "require-and-verify". + # (default: none) clientAuth: none # Paths to the private keys for each of the certificates. It requires a - # PEM-encoded file. + # PEM-encoded file. (default: ) keyFiles: [] # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" - # or "tls13". + # or "tls13". (default: tls12) minVersion: tls12 # Path to certificate for client TLS authentication. It requires a PEM-encoded - # file. + # file. (default: ) clientCertFile: "" # Path to key for client TLS authentication. It requires a PEM-encoded file. + # (default: ) clientKeyFile: "" # 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. + # This value is the MaxAge in seconds of the header. (default: 0) strictTransportSecurity: 0 # 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. + # set to a non-zero value for these options to be used. (default: ) strictTransportSecurityOptions: [] # Most Coder deployments never have to think about DERP because all connections # between workspaces and users are peer-to-peer. However, when Coder cannot @@ -73,182 +79,195 @@ networking: # a peer to peer connection, Coder uses a distributed relay network backed by # Tailscale and WireGuard. derp: - # Whether to enable or disable the embedded DERP relay server. + # Whether to enable or disable the embedded DERP relay server. (default: true) enable: true - # Region ID to use for the embedded DERP server. + # Region ID to use for the embedded DERP server. (default: 999) regionID: 999 - # Region code to use for the embedded DERP server. + # Region code to use for the embedded DERP server. (default: coder) regionCode: coder - # Region name that for the embedded DERP server. + # Region name that for the embedded DERP server. (default: Coder Embedded Relay) regionName: Coder Embedded Relay # Addresses for STUN servers to establish P2P connections. Set empty to disable - # P2P connections. + # P2P connections. (default: stun.l.google.com:19302) stunAddresses: - stun.l.google.com:19302 # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required - # for high availability. + # for high availability. (default: ) relayURL: # URL to fetch a DERP mapping on startup. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. + # https://tailscale.com/kb/1118/custom-derp-servers/. (default: ) url: "" # Path to read a DERP mapping from. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. + # https://tailscale.com/kb/1118/custom-derp-servers/. (default: ) configPath: "" # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, - # True-Client-Ip, X-Forwarded-For. + # True-Client-Ip, X-Forwarded-For. (default: ) proxyTrustedHeaders: [] # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. + # (default: ) proxyTrustedOrigins: [] - # Controls if the 'Secure' property is set on browser session cookies. + # Controls if the 'Secure' property is set on browser session cookies. (default: + # ) secureAuthCookie: false - # Whether Coder only allows connections to workspaces via the browser. + # Whether Coder only allows connections to workspaces via the browser. (default: + # ) browserOnly: false -# Interval to poll for scheduled workspace builds. +# Interval to poll for scheduled workspace builds. (default: 1m0s) autobuildPollInterval: 1m0s introspection: prometheus: - # Serve prometheus metrics on the address defined by prometheus address. + # Serve prometheus metrics on the address defined by prometheus address. (default: + # ) enable: false - # The bind address to serve prometheus metrics. + # The bind address to serve prometheus metrics. (default: 127.0.0.1:2112) address: 127.0.0.1:2112 pprof: - # Serve pprof metrics on the address defined by pprof address. + # Serve pprof metrics on the address defined by pprof address. (default: ) enable: false - # The bind address to serve pprof. + # The bind address to serve pprof. (default: 127.0.0.1:6060) address: 127.0.0.1:6060 tracing: # 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. + # (default: ) enable: false # 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. + # logs will be included. (default: ) captureLogs: false logging: - # Output debug-level logs. + # Output debug-level logs. (default: ) verbose: false - # Output human-readable logs to a given file. + # Output human-readable logs to a given file. (default: /dev/stderr) humanPath: /dev/stderr - # Output JSON logs to a given file. + # Output JSON logs to a given file. (default: ) jsonPath: "" - # Output Stackdriver compatible logs to a given file. + # Output Stackdriver compatible logs to a given file. (default: ) stackdriverPath: "" oauth2: github: - # Client ID for Login with GitHub. + # Client ID for Login with GitHub. (default: ) clientID: "" - # Organizations the user must be a member of to Login with GitHub. + # Organizations the user must be a member of to Login with GitHub. (default: + # ) allowedOrgs: [] # Teams inside organizations the user must be a member of to Login with GitHub. - # Structured as: /. + # Structured as: /. (default: ) allowedTeams: [] - # Whether new users can sign up with GitHub. + # Whether new users can sign up with GitHub. (default: ) allowSignups: false # Allow all logins, setting this option means allowed orgs and teams must be - # empty. + # empty. (default: ) allowEveryone: false # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. + # (default: ) enterpriseBaseURL: "" oidc: - # Whether new users can sign up with OIDC. + # Whether new users can sign up with OIDC. (default: true) allowSignups: true - # Client ID to use for Login with OIDC. + # Client ID to use for Login with OIDC. (default: ) clientID: "" - # Email domains that clients logging in with OIDC must match. + # Email domains that clients logging in with OIDC must match. (default: ) emailDomain: [] - # Issuer URL to use for Login with OIDC. + # Issuer URL to use for Login with OIDC. (default: ) issuerURL: "" - # Scopes to grant when authenticating with OIDC. + # Scopes to grant when authenticating with OIDC. (default: openid,profile,email) scopes: - openid - profile - email - # Ignore the email_verified claim from the upstream provider. + # Ignore the email_verified claim from the upstream provider. (default: ) ignoreEmailVerified: false - # OIDC claim field to use as the username. + # OIDC claim field to use as the username. (default: preferred_username) usernameField: preferred_username - # OIDC claim field to use as the email. + # OIDC claim field to use as the email. (default: email) emailField: email - # OIDC auth URL parameters to pass to the upstream provider. + # OIDC auth URL parameters to pass to the upstream provider. (default: + # {"access_type": "offline"}) authURLParams: access_type: offline # Change the OIDC default 'groups' claim field. By default, will be 'groups' if - # present in the oidc scopes argument. + # present in the oidc scopes argument. (default: ) groupField: "" # A map of OIDC group IDs and the group in Coder it should map to. This is useful - # for when OIDC providers only return group IDs. + # for when OIDC providers only return group IDs. (default: {}) groupMapping: {} - # The text to show on the OpenID Connect sign in button. + # The text to show on the OpenID Connect sign in button. (default: OpenID Connect) signInText: OpenID Connect - # URL pointing to the icon to use on the OepnID Connect login button. + # URL pointing to the icon to use on the OepnID Connect login button. (default: + # ) iconURL: # 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. telemetry: # Whether telemetry is enabled or not. Coder collects anonymized usage data to - # help improve our product. + # help improve our product. (default: false) enable: false # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized # application tracing to help improve our product. Disabling telemetry also - # disables this option. + # disables this option. (default: false) trace: false - # URL to send telemetry. + # URL to send telemetry. (default: https://telemetry.coder.com) url: https://telemetry.coder.com # Tune the behavior of the provisioner, which is responsible for creating, # updating, and deleting workspace resources. provisioning: # Number of provisioner daemons to create on start. If builds are stuck in queued - # state for a long time, consider increasing this. + # state for a long time, consider increasing this. (default: 3) daemons: 3 - # Time to wait before polling for a new job. + # Time to wait before polling for a new job. (default: 1s) daemonPollInterval: 1s - # Random jitter added to the poll interval. + # Random jitter added to the poll interval. (default: 100ms) daemonPollJitter: 100ms - # Time to force cancel provisioning tasks that are stuck. + # Time to force cancel provisioning tasks that are stuck. (default: 10m0s) forceCancelInterval: 10m0s # 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. +# experiments. (default: ) experiments: [] # Periodically check for new releases of Coder and inform the owner. The check is -# performed once per day. +# performed once per day. (default: false) updateCheck: false -# Expose the swagger endpoint via /swagger. +# Expose the swagger endpoint via /swagger. (default: ) enableSwagger: false # The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is -# set, it will be used for compatibility with systemd. +# set, it will be used for compatibility with systemd. (default: +# /home/coder/.cache/coder) cacheDir: /home/coder/.cache/coder -# Controls whether data will be stored in an in-memory database. +# Controls whether data will be stored in an in-memory database. (default: +# ) inMemoryDatabase: false # The algorithm to use for generating ssh keys. Accepted values are "ed25519", -# "ecdsa", or "rsa4096". +# "ecdsa", or "rsa4096". (default: ed25519) sshKeygenAlgorithm: ed25519 -# URL to use for agent troubleshooting when not set in the template. +# URL to use for agent troubleshooting when not set in the template. (default: +# https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates) agentFallbackTroubleshootingURL: https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates -# Specifies whether audit logging is enabled. +# Specifies whether audit logging is enabled. (default: true) auditLogging: true # 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. +# --wildcard-access-url is configured. (default: ) disablePathApps: false # These options change the behavior of how clients interact with the Coder. # Clients include the coder cli, vs code extension, and the web UI. client: - # The SSH deployment prefix is used in the Host of the ssh config. + # The SSH deployment prefix is used in the Host of the ssh config. (default: + # coder.) sshHostnamePrefix: coder. # These SSH config options will override the default SSH config options. Provide # options in "key=value" or "key value" format separated by commas.Using this - # incorrectly can break SSH to your deployment, use cautiously. + # incorrectly can break SSH to your deployment, use cautiously. (default: ) sshConfigOptions: [] -# Support links to display in the top right drop down menu. +# Support links to display in the top right drop down menu. (default: ) supportLinks: [] -# Git Authentication providers. +# Git Authentication providers. (default: ) gitAuthProviders: [] # Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By # default, this will pick the best available wgtunnel server hosted by Coder. e.g. -# "tunnel.example.com". +# "tunnel.example.com". (default: ) wgtunnelHost: "" From 38fa53950a7d666497fa43f8d2088950be48f4cb Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 17:11:35 +0000 Subject: [PATCH 08/35] Add values.YAMLConfigPath --- cli/clibase/values.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cli/clibase/values.go b/cli/clibase/values.go index bc30c3aaef7e3..f4f9a5b97f2bd 100644 --- a/cli/clibase/values.go +++ b/cli/clibase/values.go @@ -415,3 +415,22 @@ func (e *Enum) Type() string { func (e *Enum) String() string { return *e.Value } + +var _ pflag.Value = (*YAMLConfigPath)(nil) + +// YAMLConfigPath is a special value type that encodes a path to a YAML +// configuration file where options are read from. +type YAMLConfigPath string + +func (p *YAMLConfigPath) Set(v string) error { + *p = YAMLConfigPath(v) + return nil +} + +func (p *YAMLConfigPath) String() string { + return string(*p) +} + +func (*YAMLConfigPath) Type() string { + return "yaml-config-path" +} From 7edea99f8a679761fabbca4abe280370f8ea10f7 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 17:51:14 +0000 Subject: [PATCH 09/35] Add YAML to core clibase parsing --- cli/clibase/cmd.go | 38 ++++++++++-- cli/clibase/cmd_test.go | 107 ++++++++++++++++++++++---------- cli/clibase/option.go | 26 +++++--- cli/clibase/option_test.go | 4 +- cli/clibase/yaml.go | 15 +++-- cli/clibase/yaml_test.go | 10 ++- coderd/coderdtest/coderdtest.go | 2 +- go.mod | 1 - go.sum | 3 - 9 files changed, 144 insertions(+), 62 deletions(-) diff --git a/cli/clibase/cmd.go b/cli/clibase/cmd.go index 2b9da500225ce..fd70b67632398 100644 --- a/cli/clibase/cmd.go +++ b/cli/clibase/cmd.go @@ -13,6 +13,7 @@ import ( "github.com/spf13/pflag" "golang.org/x/exp/slices" "golang.org/x/xerrors" + "gopkg.in/yaml.v3" ) // Cmd describes an executable command. @@ -262,17 +263,42 @@ func (inv *Invocation) run(state *runState) error { parsedArgs = inv.parsedFlags.Args() } - // Set defaults for flags that weren't set by the user. - skipDefaults := make(map[int]struct{}, len(inv.Command.Options)) + // Set value sources for flags. for i, opt := range inv.Command.Options { if fl := inv.parsedFlags.Lookup(opt.Flag); fl != nil && fl.Changed { - skipDefaults[i] = struct{}{} + inv.Command.Options[i].ValueSource = ValueSourceFlag } - if opt.envChanged { - skipDefaults[i] = struct{}{} + } + + // Read configs, if any. + for _, opt := range inv.Command.Options { + path, ok := opt.Value.(*YAMLConfigPath) + if !ok || path.String() == "" { + continue + } + + fi, err := os.OpenFile(path.String(), os.O_RDONLY, 0) + if err != nil { + return xerrors.Errorf("opening config file: %w", err) + } + //nolint:revive + defer fi.Close() + + dec := yaml.NewDecoder(fi) + + var n yaml.Node + err = dec.Decode(&n) + if err != nil { + return xerrors.Errorf("decoding config: %w", err) + } + + err = inv.Command.Options.FromYAML(&n) + if err != nil { + return xerrors.Errorf("applying config: %w", err) } } - err = inv.Command.Options.SetDefaults(skipDefaults) + + err = inv.Command.Options.SetDefaults() if err != nil { return xerrors.Errorf("setting defaults: %w", err) } diff --git a/cli/clibase/cmd_test.go b/cli/clibase/cmd_test.go index 800883f1067fc..686ddf9ed9652 100644 --- a/cli/clibase/cmd_test.go +++ b/cli/clibase/cmd_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "os" "strings" "testing" @@ -555,42 +556,80 @@ func TestCommand_EmptySlice(t *testing.T) { func TestCommand_DefaultsOverride(t *testing.T) { t.Parallel() - var got string - cmd := &clibase.Cmd{ - Options: clibase.OptionSet{ - { - Name: "url", - Flag: "url", - Default: "def.com", - Env: "URL", - Value: clibase.StringOf(&got), - }, - }, - Handler: (func(i *clibase.Invocation) error { - _, _ = fmt.Fprintf(i.Stdout, "%s", got) - return nil - }), + test := func(name string, want string, fn func(t *testing.T, inv *clibase.Invocation)) { + t.Run(name, func(t *testing.T) { + t.Parallel() + + var ( + got string + config clibase.YAMLConfigPath + ) + cmd := &clibase.Cmd{ + Options: clibase.OptionSet{ + { + Name: "url", + Flag: "url", + Default: "def.com", + Env: "URL", + Value: clibase.StringOf(&got), + YAML: "url", + }, + { + Name: "config", + Flag: "config", + Default: "", + Value: &config, + }, + }, + Handler: (func(i *clibase.Invocation) error { + _, _ = fmt.Fprintf(i.Stdout, "%s", got) + return nil + }), + } + + inv := cmd.Invoke() + stdio := fakeIO(inv) + fn(t, inv) + err := inv.Run() + require.NoError(t, err) + require.Equal(t, want, stdio.Stdout.String()) + }) } - // Base case - inv := cmd.Invoke() - stdio := fakeIO(inv) - err := inv.Run() - require.NoError(t, err) - require.Equal(t, "def.com", stdio.Stdout.String()) + test("DefaultOverNothing", "def.com", func(t *testing.T, inv *clibase.Invocation) {}) - // Flag overrides - inv = cmd.Invoke("--url", "good.com") - stdio = fakeIO(inv) - err = inv.Run() - require.NoError(t, err) - require.Equal(t, "good.com", stdio.Stdout.String()) + test("FlagOverDefault", "good.com", func(t *testing.T, inv *clibase.Invocation) { + inv.Args = []string{"--url", "good.com"} + }) - // Env overrides - inv = cmd.Invoke() - inv.Environ.Set("URL", "good.com") - stdio = fakeIO(inv) - err = inv.Run() - require.NoError(t, err) - require.Equal(t, "good.com", stdio.Stdout.String()) + test("EnvOverDefault", "good.com", func(t *testing.T, inv *clibase.Invocation) { + inv.Environ.Set("URL", "good.com") + }) + + test("FlagOverEnv", "good.com", func(t *testing.T, inv *clibase.Invocation) { + inv.Environ.Set("URL", "bad.com") + inv.Args = []string{"--url", "good.com"} + }) + + test("FlagOverYAML", "good.com", func(t *testing.T, inv *clibase.Invocation) { + fi, err := os.CreateTemp(t.TempDir(), "config.yaml") + require.NoError(t, err) + defer fi.Close() + + _, err = fi.WriteString("url: bad.com") + require.NoError(t, err) + + inv.Args = []string{"--config", fi.Name(), "--url", "good.com"} + }) + + test("YAMLOverDefault", "good.com", func(t *testing.T, inv *clibase.Invocation) { + fi, err := os.CreateTemp(t.TempDir(), "config.yaml") + require.NoError(t, err) + defer fi.Close() + + _, err = fi.WriteString("url: good.com") + require.NoError(t, err) + + inv.Args = []string{"--config", fi.Name()} + }) } diff --git a/cli/clibase/option.go b/cli/clibase/option.go index e4de6772e4143..05c811e1dc73d 100644 --- a/cli/clibase/option.go +++ b/cli/clibase/option.go @@ -8,6 +8,16 @@ import ( "golang.org/x/xerrors" ) +type ValueSource string + +const ( + ValueSourceNone ValueSource = "" + ValueSourceFlag ValueSource = "flag" + ValueSourceEnv ValueSource = "env" + ValueSourceYAML ValueSource = "yaml" + ValueSourceDefault ValueSource = "default" +) + // Option is a configuration option for a CLI application. type Option struct { Name string `json:"name,omitempty"` @@ -47,7 +57,7 @@ type Option struct { Hidden bool `json:"hidden,omitempty"` - envChanged bool + ValueSource ValueSource } // OptionSet is a group of options that can be applied to a command. @@ -135,8 +145,7 @@ func (s *OptionSet) ParseEnv(vs []EnvVar) error { continue } - opt.envChanged = true - (*s)[i] = opt + (*s)[i].ValueSource = ValueSourceEnv if err := opt.Value.Set(envVal); err != nil { merr = multierror.Append( merr, xerrors.Errorf("parse %q: %w", opt.Name, err), @@ -148,8 +157,8 @@ func (s *OptionSet) ParseEnv(vs []EnvVar) error { } // SetDefaults sets the default values for each Option, skipping values -// that have already been set as indicated by the skip map. -func (s *OptionSet) SetDefaults(skip map[int]struct{}) error { +// that already have a value source. +func (s *OptionSet) SetDefaults() error { if s == nil { return nil } @@ -158,10 +167,8 @@ func (s *OptionSet) SetDefaults(skip map[int]struct{}) error { for i, opt := range *s { // Skip values that may have already been set by the user. - if len(skip) > 0 { - if _, ok := skip[i]; ok { - continue - } + if opt.ValueSource != ValueSourceNone { + continue } if opt.Default == "" { @@ -178,6 +185,7 @@ func (s *OptionSet) SetDefaults(skip map[int]struct{}) error { ) continue } + (*s)[i].ValueSource = ValueSourceDefault if err := opt.Value.Set(opt.Default); err != nil { merr = multierror.Append( merr, xerrors.Errorf("parse %q: %w", opt.Name, err), diff --git a/cli/clibase/option_test.go b/cli/clibase/option_test.go index 6e7ef023daacb..cacd8d3a10793 100644 --- a/cli/clibase/option_test.go +++ b/cli/clibase/option_test.go @@ -49,7 +49,7 @@ func TestOptionSet_ParseFlags(t *testing.T) { }, } - err := os.SetDefaults(nil) + err := os.SetDefaults() require.NoError(t, err) err = os.FlagSet().Parse([]string{"--name", "foo", "--name", "bar"}) @@ -111,7 +111,7 @@ func TestOptionSet_ParseEnv(t *testing.T) { }, } - err := os.SetDefaults(nil) + err := os.SetDefaults() require.NoError(t, err) err = os.ParseEnv(clibase.ParseEnviron([]string{"CODER_WORKSPACE_NAME="}, "CODER_")) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 050155211b311..aabd3327385ae 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -181,7 +181,7 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { continue } - if opt.Group.YAML == "" { + if opt.Group != nil && opt.Group.YAML == "" { return xerrors.Errorf("group yaml name is empty for %q", opt.Name) } @@ -213,10 +213,18 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { continue } + opt, foundOpt := optionsByName[name] + if foundOpt { + if opt.ValueSource != ValueSourceNone { + continue + } + opt.ValueSource = ValueSourceYAML + } + switch item.Kind { case yaml.MappingNode: // Item is either a group or an option with a complex object. - if opt, ok := optionsByName[name]; ok { + if foundOpt { unmarshaler, ok := opt.Value.(yaml.Unmarshaler) if !ok { return xerrors.Errorf("complex option %q must support unmarshaling", opt.Name) @@ -237,8 +245,7 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { } merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q", name)) case yaml.ScalarNode, yaml.SequenceNode: - opt, ok := optionsByName[name] - if !ok { + if !foundOpt { merr = errors.Join(merr, xerrors.Errorf("unknown option %q", name)) continue } diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 6061732a10748..6fde50c30008b 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -41,12 +41,12 @@ func TestOptionSet_YAML(t *testing.T) { Value: &workspaceName, Default: "billie", Description: "The workspace's name.", - Group: &clibase.Group{Name: "Names"}, + Group: &clibase.Group{YAML: "names"}, YAML: "workspaceName", }, } - err := os.SetDefaults(nil) + err := os.SetDefaults() require.NoError(t, err) n, err := os.ToYAML() @@ -139,6 +139,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { os2 := slices.Clone(tc.os) for i := range os2 { os2[i].Value = tc.zeroValue() + os2[i].ValueSource = clibase.ValueSourceNone } // os2 values should be zeroed whereas tc.os should be @@ -148,6 +149,11 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { err = os2.FromYAML(&y2) require.NoError(t, err) + want := tc.os + for i := range want { + want[i].ValueSource = clibase.ValueSourceYAML + } + require.Equal(t, tc.os, os2) }) } diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index b4ccfb9816bc8..a6e8cf4a2f85f 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1090,7 +1090,7 @@ QastnN77KfUwdj3SJt44U/uh1jAIv4oSLBr8HYUkbnI8 func DeploymentValues(t *testing.T) *codersdk.DeploymentValues { var cfg codersdk.DeploymentValues opts := cfg.Options() - err := opts.SetDefaults(nil) + err := opts.SetDefaults() require.NoError(t, err) return &cfg } diff --git a/go.mod b/go.mod index 8484188b3c270..9e3b415c35293 100644 --- a/go.mod +++ b/go.mod @@ -262,7 +262,6 @@ 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/insomniacslk/dhcp v0.0.0-20221215072855-de60144f33f8 // indirect diff --git a/go.sum b/go.sum index 45a0e9f010fc7..ce8dce1e2e8d1 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ 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.20230228204227-60d22dceaf04 h1:d5MQ+iI2zk7t0HrHwBP9p7k2XfRsXnRclSe8Kpp3xOo= -cdr.dev/slog v1.4.2-0.20230228204227-60d22dceaf04/go.mod h1:YPVZsUbRMaLaPgme0RzlPWlC7fI7YmDj/j/kZLuvICs= cdr.dev/slog v1.4.2 h1:fIfiqASYQFJBZiASwL825atyzeA96NsqSxx2aL61P8I= cdr.dev/slog v1.4.2/go.mod h1:0EkH+GkFNxizNR+GAXUEdUHanxUH5t9zqPILmPM/Vn8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -1093,7 +1091,6 @@ 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= From 3ab35c115a7e9b9db5ae2936b158d929793fdea8 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 18:19:26 +0000 Subject: [PATCH 10/35] Server Test WIP --- cli/server.go | 3 +- cli/server_test.go | 25 ++++++++++++++ coderd/apidoc/docs.go | 23 +++++++++++++ coderd/apidoc/swagger.json | 17 ++++++++++ codersdk/deployment.go | 8 ++--- docs/api/general.md | 10 ++++-- docs/api/schemas.md | 59 +++++++++++++++++++++++++++------- site/src/api/typesGenerated.ts | 1 + 8 files changed, 125 insertions(+), 21 deletions(-) diff --git a/cli/server.go b/cli/server.go index c4c7242c9c119..5b7a921ad6a43 100644 --- a/cli/server.go +++ b/cli/server.go @@ -173,12 +173,11 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. defer cancel() if cfg.WriteConfig { - // TODO: this should output to a file. n, err := opts.ToYAML() if err != nil { return xerrors.Errorf("generate yaml: %w", err) } - enc := yaml.NewEncoder(inv.Stderr) + enc := yaml.NewEncoder(inv.Stdout) err = enc.Encode(n) if err != nil { return xerrors.Errorf("encode yaml: %w", err) diff --git a/cli/server_test.go b/cli/server_test.go index 62b707a6034e6..59d43d8388782 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1407,6 +1407,31 @@ func TestServer(t *testing.T) { waitFile(t, fi3, testutil.WaitSuperLong) }) }) + + t.Run("YAML", func(t *testing.T) { + t.Parallel() + + t.Run("WriteThenReadConfig", func(t *testing.T) { + const wantAccessURL = "http://most-wanted.com" + inv, cfg := clitest.New(t, + "server", + "--access-url", wantAccessURL, + ) + + w := clitest.StartWithWaiter(t, inv) + gotURL := waitAccessURL(t, cfg) + require.Equal(t, wantAccessURL, gotURL.String()) + w.RequireSuccess() + + configFile, err := os.OpenFile( + filepath.Join(t.TempDir(), "coder.yaml"), os.O_WRONLY|os.O_CREATE, 0o600) + require.NoError(t, err) + defer configFile.Close() + + inv.Stdout = configFile + clitest.Run(t, inv) + }) + }) } func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) { diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 9561e0b511a6d..7d4c513e11a8e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5579,6 +5579,9 @@ const docTemplate = `{ }, "parent": { "$ref": "#/definitions/clibase.Group" + }, + "yaml_name": { + "type": "string" } } }, @@ -5647,6 +5650,9 @@ const docTemplate = `{ "value": { "description": "Value includes the types listed in values.go." }, + "valueSource": { + "$ref": "#/definitions/clibase.ValueSource" + }, "yaml": { "description": "YAML is the YAML key used to configure this option. If unset, YAML\nconfiguring is disabled.", "type": "string" @@ -5727,6 +5733,23 @@ const docTemplate = `{ } } }, + "clibase.ValueSource": { + "type": "string", + "enum": [ + "", + "flag", + "env", + "yaml", + "default" + ], + "x-enum-varnames": [ + "ValueSourceNone", + "ValueSourceFlag", + "ValueSourceEnv", + "ValueSourceYAML", + "ValueSourceDefault" + ] + }, "coderd.SCIMUser": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index fd9c8a7e8a3ff..66eed422dbd6d 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -4940,6 +4940,9 @@ }, "parent": { "$ref": "#/definitions/clibase.Group" + }, + "yaml_name": { + "type": "string" } } }, @@ -5008,6 +5011,9 @@ "value": { "description": "Value includes the types listed in values.go." }, + "valueSource": { + "$ref": "#/definitions/clibase.ValueSource" + }, "yaml": { "description": "YAML is the YAML key used to configure this option. If unset, YAML\nconfiguring is disabled.", "type": "string" @@ -5088,6 +5094,17 @@ } } }, + "clibase.ValueSource": { + "type": "string", + "enum": ["", "flag", "env", "yaml", "default"], + "x-enum-varnames": [ + "ValueSourceNone", + "ValueSourceFlag", + "ValueSourceEnv", + "ValueSourceYAML", + "ValueSourceDefault" + ] + }, "coderd.SCIMUser": { "type": "object", "properties": { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index c90033a9fc1c8..00beebd04a952 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -162,8 +162,8 @@ type DeploymentValues struct { SSHConfig SSHConfig `json:"config_ssh,omitempty" typescript:",notnull"` WgtunnelHost clibase.String `json:"wgtunnel_host,omitempty" typescript:",notnull"` - Config clibase.String `json:"config,omitempty" typescript:",notnull"` - WriteConfig clibase.Bool `json:"write_config,omitempty" typescript:",notnull"` + Config clibase.YAMLConfigPath `json:"config,omitempty" typescript:",notnull"` + WriteConfig clibase.Bool `json:"write_config,omitempty" typescript:",notnull"` // DEPRECATED: Use HTTPAddress or TLS.Address instead. Address clibase.HostPort `json:"address,omitempty" typescript:",notnull"` @@ -1384,11 +1384,11 @@ when required by your organization's security policy.`, { Name: "Write Config", Description: ` -Write out the current server configuration to the path specified by --config.`, +Write out the current server as YAML to stdout.`, Flag: "write-config", Env: "CODER_WRITE_CONFIG", Group: &deploymentGroupConfig, - Hidden: true, + Hidden: false, Value: &c.WriteConfig, }, { diff --git a/docs/api/general.md b/docs/api/general.md index 43dafb41e5d51..f78fc8935eeea 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -376,7 +376,8 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "children": [], "description": "string", "name": "string", - "parent": {} + "parent": {}, + "yaml_name": "string" } ], "description": "string", @@ -385,13 +386,16 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "children": [{}], "description": "string", "name": "string", - "parent": {} - } + "parent": {}, + "yaml_name": "string" + }, + "yaml_name": "string" }, "hidden": true, "name": "string", "use_instead": [{}], "value": null, + "valueSource": "", "yaml": "string" } ] diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 2f76aef88bbb9..146b9a410d45e 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -359,7 +359,8 @@ "children": [], "description": "string", "name": "string", - "parent": {} + "parent": {}, + "yaml_name": "string" } ], "description": "string", @@ -368,8 +369,10 @@ "children": [{}], "description": "string", "name": "string", - "parent": {} - } + "parent": {}, + "yaml_name": "string" + }, + "yaml_name": "string" } ``` @@ -381,6 +384,7 @@ | `description` | string | false | | | | `name` | string | false | | | | `parent` | [clibase.Group](#clibasegroup) | false | | | +| `yaml_name` | string | false | | | ## clibase.HostPort @@ -417,7 +421,8 @@ "children": [], "description": "string", "name": "string", - "parent": {} + "parent": {}, + "yaml_name": "string" } ], "description": "string", @@ -426,8 +431,10 @@ "children": [{}], "description": "string", "name": "string", - "parent": {} - } + "parent": {}, + "yaml_name": "string" + }, + "yaml_name": "string" }, "hidden": true, "name": "string", @@ -448,7 +455,8 @@ "children": [], "description": "string", "name": "string", - "parent": {} + "parent": {}, + "yaml_name": "string" } ], "description": "string", @@ -457,17 +465,21 @@ "children": [{}], "description": "string", "name": "string", - "parent": {} - } + "parent": {}, + "yaml_name": "string" + }, + "yaml_name": "string" }, "hidden": true, "name": "string", "use_instead": [], "value": null, + "valueSource": "", "yaml": "string" } ], "value": null, + "valueSource": "", "yaml": "string" } ``` @@ -487,6 +499,7 @@ | `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. | +| `valueSource` | [clibase.ValueSource](#clibasevaluesource) | false | | | | `yaml` | string | false | | Yaml is the YAML key used to configure this option. If unset, YAML configuring is disabled. | ## clibase.Struct-array_codersdk_GitAuthConfig @@ -569,6 +582,24 @@ | `scheme` | string | false | | | | `user` | [url.Userinfo](#urluserinfo) | false | | username and password information | +## clibase.ValueSource + +```json +"" +``` + +### Properties + +#### Enumerated Values + +| Value | +| --------- | +| `` | +| `flag` | +| `env` | +| `yaml` | +| `default` | + ## coderd.SCIMUser ```json @@ -1946,7 +1977,8 @@ CreateParameterRequest is a structure used to create a new parameter value for a "children": [], "description": "string", "name": "string", - "parent": {} + "parent": {}, + "yaml_name": "string" } ], "description": "string", @@ -1955,13 +1987,16 @@ CreateParameterRequest is a structure used to create a new parameter value for a "children": [{}], "description": "string", "name": "string", - "parent": {} - } + "parent": {}, + "yaml_name": "string" + }, + "yaml_name": "string" }, "hidden": true, "name": "string", "use_instead": [{}], "value": null, + "valueSource": "", "yaml": "string" } ] diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index f1b40dddc60cd..fe1c11d3e2890 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -369,6 +369,7 @@ export interface DeploymentValues { readonly git_auth?: any readonly config_ssh?: SSHConfig readonly wgtunnel_host?: string + // This is likely an enum in an external package ("github.com/coder/coder/cli/clibase.YAMLConfigPath") readonly config?: string readonly write_config?: boolean // Named type "github.com/coder/coder/cli/clibase.HostPort" unknown, using "any" From 38155da373af1509c5fef9717e57f6ebba3c660e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 31 Mar 2023 19:33:19 +0000 Subject: [PATCH 11/35] More WIP --- Makefile | 2 +- cli/clitest/clitest.go | 2 ++ cli/server.go | 2 ++ cli/server_test.go | 28 +++++++++++++---- cli/testdata/coder_server_--help.golden | 7 +++++ coderd/database/models.go | 40 ++++++++++++------------- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 2 +- codersdk/deployment.go | 11 +++---- 9 files changed, 61 insertions(+), 35 deletions(-) diff --git a/Makefile b/Makefile index e251cd40576c3..6f03530e255cb 100644 --- a/Makefile +++ b/Makefile @@ -518,7 +518,7 @@ update-golden-files: cli/testdata/.gen-golden helm/tests/testdata/.gen-golden .PHONY: update-golden-files cli/testdata/.gen-golden: $(wildcard cli/testdata/*.golden) $(wildcard cli/*.tpl) $(GO_SRC_FILES) - go test ./cli -run=Test\(CommandHelp\|ServerConfig\) -update + go test ./cli -run="Test(CommandHelp|ServerYAML)" -update touch "$@" helm/tests/testdata/.gen-golden: $(wildcard helm/tests/testdata/*.golden) $(GO_SRC_FILES) diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index 7680a06981a05..5048564cc22f5 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -226,6 +226,8 @@ func StartWithWaiter(t *testing.T, inv *clibase.Invocation) *ErrorWaiter { // down Postgres. t.Logf("command %q timed out during test cleanup", inv.Command.FullName()) } + // When or not this failed the test is up to the caller. + t.Logf("command %q exited with error: %v", inv.Command.FullName(), err) errCh <- err }() diff --git a/cli/server.go b/cli/server.go index 5b7a921ad6a43..85a7e26262779 100644 --- a/cli/server.go +++ b/cli/server.go @@ -193,6 +193,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. cliui.Warnf(inv.Stderr, "YAML support is experimental and offers no compatibility guarantees.") } + println("HERE1") + // Print deprecation warnings. for _, opt := range opts { if opt.UseInstead == nil { diff --git a/cli/server_test.go b/cli/server_test.go index 59d43d8388782..2dc41d1e72973 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1412,17 +1412,35 @@ func TestServer(t *testing.T) { t.Parallel() t.Run("WriteThenReadConfig", func(t *testing.T) { - const wantAccessURL = "http://most-wanted.com" + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // First, we get the base config as set via flags (like users before + // migrating). inv, cfg := clitest.New(t, "server", - "--access-url", wantAccessURL, + "--in-memory", + "--http-address", ":0", + "--access-url", "http://example.com", + "--log-human", filepath.Join(t.TempDir(), "coder-logging-test-*"), + "--ssh-keygen-algorithm", "rsa4096", + "--cache-dir", t.TempDir(), ) - + ptytest.New(t).Attach(inv) + inv = inv.WithContext(ctx) w := clitest.StartWithWaiter(t, inv) gotURL := waitAccessURL(t, cfg) - require.Equal(t, wantAccessURL, gotURL.String()) + client := codersdk.New(gotURL) + _ = coderdtest.CreateFirstUser(t, client) + wantValues, err := client.DeploymentConfig(ctx) + require.NoError(t, err) + cancel() w.RequireSuccess() + // Next, we write the config to a file. + configFile, err := os.OpenFile( filepath.Join(t.TempDir(), "coder.yaml"), os.O_WRONLY|os.O_CREATE, 0o600) require.NoError(t, err) @@ -1492,7 +1510,7 @@ func waitAccessURL(t *testing.T, cfg config.Root) *url.URL { return accessURL } -func TestServerConfig(t *testing.T) { +func TestServerYAMLConfig(t *testing.T) { t.Parallel() var deployValues codersdk.DeploymentValues diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 56edb2c58de04..aa86c3a456724 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -57,6 +57,13 @@ Clients include the coder cli, vs code extension, and the web UI. --ssh-hostname-prefix string, $CODER_SSH_HOSTNAME_PREFIX (default: coder.) The SSH deployment prefix is used in the Host of the ssh config. +Config Options +Use a YAML configuration file when your server launch become unwieldy. + + --write-config bool, $CODER_WRITE_CONFIG + + Write out the current server as YAML to stdout. + Introspection / Logging Options --log-human string, $CODER_LOGGING_HUMAN (default: /dev/stderr) Output human-readable logs to a given file. diff --git a/coderd/database/models.go b/coderd/database/models.go index 0a929ae07005f..82ade2f95c96e 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.16.0 package database @@ -56,7 +56,7 @@ func (ns NullAPIKeyScope) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.APIKeyScope), nil + return ns.APIKeyScope, nil } func (e APIKeyScope) Valid() bool { @@ -115,7 +115,7 @@ func (ns NullAppSharingLevel) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.AppSharingLevel), nil + return ns.AppSharingLevel, nil } func (e AppSharingLevel) Valid() bool { @@ -180,7 +180,7 @@ func (ns NullAuditAction) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.AuditAction), nil + return ns.AuditAction, nil } func (e AuditAction) Valid() bool { @@ -249,7 +249,7 @@ func (ns NullBuildReason) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.BuildReason), nil + return ns.BuildReason, nil } func (e BuildReason) Valid() bool { @@ -312,7 +312,7 @@ func (ns NullLogLevel) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.LogLevel), nil + return ns.LogLevel, nil } func (e LogLevel) Valid() bool { @@ -376,7 +376,7 @@ func (ns NullLogSource) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.LogSource), nil + return ns.LogSource, nil } func (e LogSource) Valid() bool { @@ -436,7 +436,7 @@ func (ns NullLoginType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.LoginType), nil + return ns.LoginType, nil } func (e LoginType) Valid() bool { @@ -499,7 +499,7 @@ func (ns NullParameterDestinationScheme) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ParameterDestinationScheme), nil + return ns.ParameterDestinationScheme, nil } func (e ParameterDestinationScheme) Valid() bool { @@ -560,7 +560,7 @@ func (ns NullParameterScope) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ParameterScope), nil + return ns.ParameterScope, nil } func (e ParameterScope) Valid() bool { @@ -620,7 +620,7 @@ func (ns NullParameterSourceScheme) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ParameterSourceScheme), nil + return ns.ParameterSourceScheme, nil } func (e ParameterSourceScheme) Valid() bool { @@ -678,7 +678,7 @@ func (ns NullParameterTypeSystem) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ParameterTypeSystem), nil + return ns.ParameterTypeSystem, nil } func (e ParameterTypeSystem) Valid() bool { @@ -737,7 +737,7 @@ func (ns NullProvisionerJobType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ProvisionerJobType), nil + return ns.ProvisionerJobType, nil } func (e ProvisionerJobType) Valid() bool { @@ -796,7 +796,7 @@ func (ns NullProvisionerStorageMethod) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ProvisionerStorageMethod), nil + return ns.ProvisionerStorageMethod, nil } func (e ProvisionerStorageMethod) Valid() bool { @@ -852,7 +852,7 @@ func (ns NullProvisionerType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ProvisionerType), nil + return ns.ProvisionerType, nil } func (e ProvisionerType) Valid() bool { @@ -918,7 +918,7 @@ func (ns NullResourceType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.ResourceType), nil + return ns.ResourceType, nil } func (e ResourceType) Valid() bool { @@ -992,7 +992,7 @@ func (ns NullUserStatus) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.UserStatus), nil + return ns.UserStatus, nil } func (e UserStatus) Valid() bool { @@ -1057,7 +1057,7 @@ func (ns NullWorkspaceAgentLifecycleState) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.WorkspaceAgentLifecycleState), nil + return ns.WorkspaceAgentLifecycleState, nil } func (e WorkspaceAgentLifecycleState) Valid() bool { @@ -1131,7 +1131,7 @@ func (ns NullWorkspaceAppHealth) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.WorkspaceAppHealth), nil + return ns.WorkspaceAppHealth, nil } func (e WorkspaceAppHealth) Valid() bool { @@ -1194,7 +1194,7 @@ func (ns NullWorkspaceTransition) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.WorkspaceTransition), nil + return ns.WorkspaceTransition, nil } func (e WorkspaceTransition) Valid() bool { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index bdd370acca17f..4ef49a931fbf4 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.16.0 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 49e6a95489196..669c5b7a474d1 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.17.2 +// sqlc v1.16.0 package database diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 00beebd04a952..f2c8bc1015938 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -1352,11 +1352,9 @@ when required by your organization's security policy.`, Flag: "config", Env: "CODER_CONFIG_PATH", FlagShorthand: "c", - // The config parameters are hidden until they are tested and - // documented. - Hidden: true, - Group: &deploymentGroupConfig, - Value: &c.Config, + Hidden: false, + Group: &deploymentGroupConfig, + Value: &c.Config, }, { Name: "SSH Host Prefix", @@ -1384,9 +1382,8 @@ when required by your organization's security policy.`, { Name: "Write Config", Description: ` -Write out the current server as YAML to stdout.`, +Write out the current server config as YAML to stdout.`, Flag: "write-config", - Env: "CODER_WRITE_CONFIG", Group: &deploymentGroupConfig, Hidden: false, Value: &c.WriteConfig, From c72f67c5a4a5d814d991a50a51e3f3d2a7fdd793 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Sat, 1 Apr 2023 23:51:59 +0000 Subject: [PATCH 12/35] hmm --- cli/server_test.go | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/cli/server_test.go b/cli/server_test.go index 2dc41d1e72973..bff47823b94c6 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1417,9 +1417,7 @@ func TestServer(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - // First, we get the base config as set via flags (like users before - // migrating). - inv, cfg := clitest.New(t, + args := []string{ "server", "--in-memory", "--http-address", ":0", @@ -1427,27 +1425,45 @@ func TestServer(t *testing.T) { "--log-human", filepath.Join(t.TempDir(), "coder-logging-test-*"), "--ssh-keygen-algorithm", "rsa4096", "--cache-dir", t.TempDir(), + } + + // First, we get the base config as set via flags (like users before + // migrating). + inv, cfg := clitest.New(t, + args..., ) ptytest.New(t).Attach(inv) inv = inv.WithContext(ctx) w := clitest.StartWithWaiter(t, inv) gotURL := waitAccessURL(t, cfg) client := codersdk.New(gotURL) + _ = coderdtest.CreateFirstUser(t, client) wantValues, err := client.DeploymentConfig(ctx) require.NoError(t, err) cancel() w.RequireSuccess() - // Next, we write the config to a file. - - configFile, err := os.OpenFile( - filepath.Join(t.TempDir(), "coder.yaml"), os.O_WRONLY|os.O_CREATE, 0o600) + // Next, we instruct the same server to display config. + inv = inv.WithContext(testutil.Context(t, testutil.WaitMedium)) + inv.Args = append(args, "--write-config") + fi, err := os.OpenFile(testutil.TempFile(t, "", "coder-config-test-*"), os.O_WRONLY|os.O_CREATE, 0o600) + require.NoError(t, err) + defer fi.Close() + inv.Stdout = fi + t.Logf("%+v", inv.Args) + err = inv.Run() require.NoError(t, err) - defer configFile.Close() - inv.Stdout = configFile - clitest.Run(t, inv) + // Finally, we read the config back in and ensure it matches the + // original. + inv.Args = append(args, "--config="+fi.Name()) + w = clitest.StartWithWaiter(t, inv) + // The same client should work. + gotValues, err := client.DeploymentConfig(ctx) + require.NoError(t, err) + require.Equal(t, wantValues, gotValues) + w.RequireSuccess() }) }) } From 9d61ca79309757a81cd91c91925874c47b77838e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 4 Apr 2023 18:33:44 +0000 Subject: [PATCH 13/35] Cant find a clean way to do this.. --- cli/clibase/clibase.go | 14 ++----- cli/clibase/option_test.go | 4 +- cli/clibase/yaml.go | 84 ++++++++++++++++++++++++++++---------- cli/clibase/yaml_test.go | 13 ++++++ go.mod | 2 +- 5 files changed, 83 insertions(+), 34 deletions(-) diff --git a/cli/clibase/clibase.go b/cli/clibase/clibase.go index e133296c8c39f..c12f41e81652e 100644 --- a/cli/clibase/clibase.go +++ b/cli/clibase/clibase.go @@ -14,16 +14,10 @@ import ( // 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"` - YAML string `json:"yaml_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) + Parent *Group `json:"parent,omitempty"` + Name string `json:"name,omitempty"` + YAML string `json:"yaml_name,omitempty"` + Description string `json:"description,omitempty"` } // Ancestry returns the group and all of its parents, in order. diff --git a/cli/clibase/option_test.go b/cli/clibase/option_test.go index 410e14d6be6df..cacd8d3a10793 100644 --- a/cli/clibase/option_test.go +++ b/cli/clibase/option_test.go @@ -133,7 +133,7 @@ func TestOptionSet_ParseEnv(t *testing.T) { }, } - err := os.SetDefaults(nil) + err := os.SetDefaults() require.NoError(t, err) err = os.ParseEnv([]clibase.EnvVar{ @@ -157,7 +157,7 @@ func TestOptionSet_ParseEnv(t *testing.T) { }, } - err := os.SetDefaults(nil) + err := os.SetDefaults() require.NoError(t, err) err = os.ParseEnv([]clibase.EnvVar{ diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index aabd3327385ae..31a3549912f62 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/mitchellh/go-wordwrap" + "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" ) @@ -45,12 +46,12 @@ func deepMapNode(n *yaml.Node, path []string, headComment string) *yaml.Node { // the stack. // // It is isomorphic with FromYAML. -func (s OptionSet) ToYAML() (*yaml.Node, error) { +func (s *OptionSet) ToYAML() (*yaml.Node, error) { root := yaml.Node{ Kind: yaml.MappingNode, } - for _, opt := range s { + for _, opt := range *s { if opt.YAML == "" { continue } @@ -141,11 +142,61 @@ func (s OptionSet) ToYAML() (*yaml.Node, error) { // FromYAML converts the given YAML node into the option set. // It is isomorphic with ToYAML. - func (s *OptionSet) FromYAML(n *yaml.Node) error { return fromYAML(*s, nil, n) } +func (s *OptionSet) groupMembers(wantGroup *Group) ([]*Option, []*Group) { + var ( + opts map[*Option]struct{} + subgroups map[*Group]struct{} + ) + + // HACK: If the parent group has no options, its sole purpose is to contain + // other groups, so we will not find it by looking at the options. + allSubGroups := make(map[*Group]struct{}) + for _, opt := range *s { + allSubGroups[opt.Group] = struct{}{} + } + for g := range allSubGroups { + if g.Parent == nil { + continue + } + + if g.Parent != wantGroup { + continue + } + + _, parentGroupOpts := s.groupMembers(g.Parent) + if len(parentGroupOpts) > 0 { + continue + } + + subgroups[g.Parent] = struct{}{} + } + + for i := range *s { + opt := &(*s)[i] + if opt.Group == wantGroup { + opts[opt] = struct{}{} + } + + if opt.Group.Parent != wantGroup { + continue + } + + subgroups[opt.Group] = struct{}{} + + } + + for _, g := range subgroups { + if g.Parent != nil { + o, sgs := s.groupMembers(g.Parent) + } + } + return maps.Keys(opts), maps.Keys(subgroups) +} + func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { if n.Kind == yaml.DocumentNode && ofGroup == nil { // The root may be a document node. @@ -160,31 +211,22 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { return xerrors.Errorf("expected mapping node, got type %v, contents:\n%v", n.Kind, string(byt)) } + options, subgroups := os.groupMembers(ofGroup) var ( subGroupsByName = make(map[string]*Group) optionsByName = make(map[string]*Option) ) - for i, opt := range os { - if opt.YAML == "" { - continue + for _, g := range subgroups { + if g.YAML == "" { + return xerrors.Errorf("group yaml name is empty for %q", g.Name) } - - // We only want to process options that are of the identified group, - // even if that group is nil. - if opt.Group != ofGroup { - if opt.Group != nil && opt.Group.Parent == ofGroup { - if opt.Group.YAML == "" { - return xerrors.Errorf("group yaml name is empty for %q, groups: %+v", opt.Name, opt.Group) - } - subGroupsByName[opt.Group.YAML] = opt.Group - } + subGroupsByName[g.YAML] = g + } + for i, opt := range options { + if opt.YAML == "" { continue } - if opt.Group != nil && opt.Group.YAML == "" { - return xerrors.Errorf("group yaml name is empty for %q", opt.Name) - } - if _, ok := optionsByName[opt.YAML]; ok { return xerrors.Errorf("duplicate option name %q", opt.YAML) } @@ -243,7 +285,7 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { } continue } - merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q", name)) + merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q in group %v", name, ofGroup)) case yaml.ScalarNode, yaml.SequenceNode: if !foundOpt { merr = errors.Join(merr, xerrors.Errorf("unknown option %q", name)) diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 6fde50c30008b..07ec61cc839ff 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -113,6 +113,19 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { return &clibase.Struct[[]kid]{} }, }, + { + name: "DeepGroup", + os: clibase.OptionSet{ + { + YAML: "names", + Default: "jill,jack,joan", + Group: &clibase.Group{YAML: "kids", Parent: &clibase.Group{YAML: "family"}}, + }, + }, + zeroValue: func() pflag.Value { + return clibase.StringArrayOf(&[]string{}) + }, + }, } { tc := tc t.Run(tc.name, func(t *testing.T) { diff --git a/go.mod b/go.mod index dd97c0db86ffa..feb55a9e757f7 100644 --- a/go.mod +++ b/go.mod @@ -173,7 +173,7 @@ require ( require ( cloud.google.com/go/logging v1.6.1 // indirect - github.com/bep/debounce v1.2.1 // indirect + github.com/bep/debounce v1.2.1 github.com/dgraph-io/badger/v3 v3.2103.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/golang/glog v1.0.0 // indirect From 5b61b7b55321aa89c4f891d6f270bacb488db05b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 4 Apr 2023 19:45:11 +0000 Subject: [PATCH 14/35] Mediocre solution --- cli/clibase/yaml.go | 73 +++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 39 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 31a3549912f62..333aff9b1751d 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -146,55 +146,40 @@ func (s *OptionSet) FromYAML(n *yaml.Node) error { return fromYAML(*s, nil, n) } -func (s *OptionSet) groupMembers(wantGroup *Group) ([]*Option, []*Group) { - var ( - opts map[*Option]struct{} - subgroups map[*Group]struct{} - ) - - // HACK: If the parent group has no options, its sole purpose is to contain - // other groups, so we will not find it by looking at the options. - allSubGroups := make(map[*Group]struct{}) - for _, opt := range *s { - allSubGroups[opt.Group] = struct{}{} - } - for g := range allSubGroups { - if g.Parent == nil { - continue - } - - if g.Parent != wantGroup { - continue - } - - _, parentGroupOpts := s.groupMembers(g.Parent) - if len(parentGroupOpts) > 0 { +func (g *Group) filterOptionSet(s *OptionSet) []*Option { + var opts []*Option + for i := range *s { + opt := (*s)[i] + if opt.Group != g { continue } - - subgroups[g.Parent] = struct{}{} + opts = append(opts, &opt) } + return opts +} - for i := range *s { - opt := &(*s)[i] - if opt.Group == wantGroup { - opts[opt] = struct{}{} - } - - if opt.Group.Parent != wantGroup { +func (g *Group) subGroups(s *OptionSet) []*Group { + groups := make(map[*Group]struct{}) + for _, opt := range *s { + if opt.Group == nil { continue } - subgroups[opt.Group] = struct{}{} + parent := opt.Group.Parent - } + if parent == g { + groups[opt.Group] = struct{}{} + } - for _, g := range subgroups { - if g.Parent != nil { - o, sgs := s.groupMembers(g.Parent) + if parent != nil { + // HACK: We need to check the grandparent in case the group exists + // just to contain other groups. + if pp := parent.Parent; pp == g { + groups[parent] = struct{}{} + } } } - return maps.Keys(opts), maps.Keys(subgroups) + return maps.Keys(groups) } func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { @@ -211,7 +196,17 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { return xerrors.Errorf("expected mapping node, got type %v, contents:\n%v", n.Kind, string(byt)) } - options, subgroups := os.groupMembers(ofGroup) + // We're only interested in options that can be YAML-ified. + var os2 OptionSet + for _, opt := range os { + if opt.YAML == "" { + continue + } + os2 = append(os2, opt) + } + os = os2 + + options, subgroups := ofGroup.filterOptionSet(&os), ofGroup.subGroups(&os) var ( subGroupsByName = make(map[string]*Group) optionsByName = make(map[string]*Option) From 4923d92499080fcba444f23b40e354a94dfe99f3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Tue, 4 Apr 2023 20:16:24 +0000 Subject: [PATCH 15/35] hmm --- cli/clibase/yaml.go | 20 +++++++++----------- cli/server_test.go | 11 +++++++++-- codersdk/deployment.go | 8 +++++--- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 333aff9b1751d..a7331f2397b9f 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -289,27 +289,25 @@ func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { unmarshaler, _ := opt.Value.(yaml.Unmarshaler) switch { - case unmarshaler == nil && item.Kind == yaml.ScalarNode: + case unmarshaler != nil: + err := unmarshaler.UnmarshalYAML(item) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.YAML, err)) + } + case item.Kind == yaml.ScalarNode: err := opt.Value.Set(item.Value) if err != nil { - merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.Name, err)) + merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.YAML, err)) } - case unmarshaler == nil && item.Kind == yaml.SequenceNode: + case item.Kind == yaml.SequenceNode: // Item is an option with a slice value. err := item.Decode(opt.Value) if err != nil { - merr = errors.Join(merr, xerrors.Errorf("decode %q: %w", opt.Name, err)) - } - case unmarshaler != nil: - err := unmarshaler.UnmarshalYAML(item) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) + merr = errors.Join(merr, xerrors.Errorf("decode %q: %w", opt.YAML, err)) } default: panic("unreachable?") } - continue - default: return xerrors.Errorf("unexpected kind for value %v", item.Kind) } diff --git a/cli/server_test.go b/cli/server_test.go index bff47823b94c6..ce4df0bf0d2a1 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -2,6 +2,7 @@ package cli_test import ( "bufio" + "bytes" "context" "crypto/ecdsa" "crypto/elliptic" @@ -12,6 +13,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "io" "math/big" "net" "net/http" @@ -1450,7 +1452,8 @@ func TestServer(t *testing.T) { fi, err := os.OpenFile(testutil.TempFile(t, "", "coder-config-test-*"), os.O_WRONLY|os.O_CREATE, 0o600) require.NoError(t, err) defer fi.Close() - inv.Stdout = fi + var conf bytes.Buffer + inv.Stdout = io.MultiWriter(fi, &conf) t.Logf("%+v", inv.Args) err = inv.Run() require.NoError(t, err) @@ -1461,7 +1464,7 @@ func TestServer(t *testing.T) { w = clitest.StartWithWaiter(t, inv) // The same client should work. gotValues, err := client.DeploymentConfig(ctx) - require.NoError(t, err) + require.NoError(t, err, "config:\n%s", conf.String()) require.Equal(t, wantValues, gotValues) w.RequireSuccess() }) @@ -1538,6 +1541,10 @@ func TestServerYAMLConfig(t *testing.T) { n, err := opts.ToYAML() require.NoError(t, err) + // Sanity-check that we can read the config back in. + err = opts.FromYAML(n) + require.NoError(t, err) + wantByt, err := yaml.Marshal(n) require.NoError(t, err) diff --git a/codersdk/deployment.go b/codersdk/deployment.go index f2c8bc1015938..50034d14ee9c3 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -1401,9 +1401,11 @@ Write out the current server config as YAML to stdout.`, // Env handling is done in cli.ReadGitAuthFromEnvironment Name: "Git Auth Providers", Description: "Git Authentication providers.", - YAML: "gitAuthProviders", - Value: &c.GitAuthProviders, - Hidden: true, + // We need extra scrutiny to ensure this works, is documented, and + // tested before enabling. + // YAML: "gitAuthProviders", + Value: &c.GitAuthProviders, + Hidden: true, }, { Name: "Custom wgtunnel Host", From 781786b806548446f0f6a40e9c55479593d7bb85 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 17:05:46 +0000 Subject: [PATCH 16/35] New, better YAML --- cli/clibase/yaml.go | 216 ++++++++++++++++---------------------------- cli/root.go | 2 +- 2 files changed, 80 insertions(+), 138 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index a7331f2397b9f..22d6d2b53ceed 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -3,9 +3,9 @@ package clibase import ( "errors" "fmt" + "strings" "github.com/mitchellh/go-wordwrap" - "golang.org/x/exp/maps" "golang.org/x/xerrors" "gopkg.in/yaml.v3" ) @@ -140,12 +140,6 @@ func (s *OptionSet) ToYAML() (*yaml.Node, error) { return &root, nil } -// FromYAML converts the given YAML node into the option set. -// It is isomorphic with ToYAML. -func (s *OptionSet) FromYAML(n *yaml.Node) error { - return fromYAML(*s, nil, n) -} - func (g *Group) filterOptionSet(s *OptionSet) []*Option { var opts []*Option for i := range *s { @@ -158,159 +152,107 @@ func (g *Group) filterOptionSet(s *OptionSet) []*Option { return opts } -func (g *Group) subGroups(s *OptionSet) []*Group { - groups := make(map[*Group]struct{}) - for _, opt := range *s { - if opt.Group == nil { +// mapYAMLNodes converts n into a map with keys of form "group.subgroup.option" +// and values of the corresponding YAML nodes. +func mapYAMLNodes(n *yaml.Node) (map[string]*yaml.Node, error) { + if n.Kind != yaml.MappingNode { + return nil, xerrors.Errorf("expected mapping node, got type %v", n.Kind) + } + var ( + key string + m = make(map[string]*yaml.Node) + merr error + ) + for i, node := range n.Content { + if i&2 == 0 { + if node.Kind != yaml.ScalarNode { + return nil, xerrors.Errorf("expected scalar node for key, got type %v", n.Content[i].Kind) + } + key = node.Value continue } - - parent := opt.Group.Parent - - if parent == g { - groups[opt.Group] = struct{}{} - } - - if parent != nil { - // HACK: We need to check the grandparent in case the group exists - // just to contain other groups. - if pp := parent.Parent; pp == g { - groups[parent] = struct{}{} + // Even if we have a mapping node, we don't know if it's a group or a + // complex option, so we store both. + m[key] = node + if node.Kind == yaml.MappingNode { + sub, err := mapYAMLNodes(node) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("mapping node %q: %w", key, err)) + continue + } + for k, v := range sub { + m[key+"."+k] = v } } } - return maps.Keys(groups) + + return m, nil } -func fromYAML(os OptionSet, ofGroup *Group, n *yaml.Node) error { - if n.Kind == yaml.DocumentNode && ofGroup == nil { - // The root may be a document node. - if len(n.Content) != 1 { - return xerrors.Errorf("expected one content node, got %d", len(n.Content)) - } - return fromYAML(os, ofGroup, n.Content[0]) +func (o *Option) setFromYAMLNode(n *yaml.Node) error { + if um, ok := o.Value.(yaml.Unmarshaler); ok { + return um.UnmarshalYAML(n) } - if n.Kind != yaml.MappingNode { - byt, _ := yaml.Marshal(n) - return xerrors.Errorf("expected mapping node, got type %v, contents:\n%v", n.Kind, string(byt)) + switch n.Kind { + case yaml.ScalarNode: + return o.Value.Set(n.Value) + case yaml.SequenceNode: + return n.Decode(o.Value) + case yaml.MappingNode: + return xerrors.Errorf("mapping node must implement yaml.Unmarshaler") + default: + return xerrors.Errorf("unexpected node kind %v", n.Kind) } +} - // We're only interested in options that can be YAML-ified. - var os2 OptionSet - for _, opt := range os { - if opt.YAML == "" { - continue +// FromYAML converts the given YAML node into the option set. +// It is isomorphic with ToYAML. +func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { + // The rootNode will be a DocumentNode if it's read from a file. Currently, + // we don't support multiple YAML documents. + if rootNode.Kind == yaml.DocumentNode { + if len(rootNode.Content) != 1 { + return xerrors.Errorf("expected one node in document, got %d", len(rootNode.Content)) } - os2 = append(os2, opt) + rootNode = rootNode.Content[0] } - os = os2 - options, subgroups := ofGroup.filterOptionSet(&os), ofGroup.subGroups(&os) - var ( - subGroupsByName = make(map[string]*Group) - optionsByName = make(map[string]*Option) - ) - for _, g := range subgroups { - if g.YAML == "" { - return xerrors.Errorf("group yaml name is empty for %q", g.Name) - } - subGroupsByName[g.YAML] = g + m, err := mapYAMLNodes(rootNode) + if err != nil { + return xerrors.Errorf("mapping nodes: %w", err) } - for i, opt := range options { - if opt.YAML == "" { - continue - } - - if _, ok := optionsByName[opt.YAML]; ok { - return xerrors.Errorf("duplicate option name %q", opt.YAML) - } - optionsByName[opt.YAML] = &os[i] - } - - for k := range subGroupsByName { - if _, ok := optionsByName[k]; !ok { + var merr error + for _, opt := range *s { + if opt.YAML == "" { continue } - return xerrors.Errorf("there is both an option and a group with name %q", k) - } - - var ( - name string - merr error - ) - - for i, item := range n.Content { - if isName := i%2 == 0; isName { - if item.Kind != yaml.ScalarNode { - return xerrors.Errorf("expected scalar node for name, got %v", item.Kind) + var group []string + for _, g := range opt.Group.Ancestry() { + if g.YAML == "" { + return xerrors.Errorf( + "group yaml name is empty for %q, groups: %+v", + opt.Name, + opt.Group, + ) } - name = item.Value + group = append(group, g.YAML) + } + key := strings.Join(append(group, opt.YAML), ".") + node, ok := m[key] + if !ok { continue } - - opt, foundOpt := optionsByName[name] - if foundOpt { - if opt.ValueSource != ValueSourceNone { - continue - } - opt.ValueSource = ValueSourceYAML + if err := opt.setFromYAMLNode(node); err != nil { + merr = errors.Join(merr, xerrors.Errorf("setting %q: %w", opt.YAML, err)) } + delete(m, key) + } - switch item.Kind { - case yaml.MappingNode: - // Item is either a group or an option with a complex object. - if foundOpt { - unmarshaler, ok := opt.Value.(yaml.Unmarshaler) - if !ok { - return xerrors.Errorf("complex option %q must support unmarshaling", opt.Name) - } - err := unmarshaler.UnmarshalYAML(item) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.Name, err)) - } - continue - } - if g, ok := subGroupsByName[name]; ok { - // Group, recurse. - err := fromYAML(os, g, item) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("group %q: %w", g.YAML, err)) - } - continue - } - merr = errors.Join(merr, xerrors.Errorf("unknown option or subgroup %q in group %v", name, ofGroup)) - case yaml.ScalarNode, yaml.SequenceNode: - if !foundOpt { - merr = errors.Join(merr, xerrors.Errorf("unknown option %q", name)) - continue - } - - unmarshaler, _ := opt.Value.(yaml.Unmarshaler) - switch { - case unmarshaler != nil: - err := unmarshaler.UnmarshalYAML(item) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("unmarshal %q: %w", opt.YAML, err)) - } - case item.Kind == yaml.ScalarNode: - err := opt.Value.Set(item.Value) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("set %q: %w", opt.YAML, err)) - } - case item.Kind == yaml.SequenceNode: - // Item is an option with a slice value. - err := item.Decode(opt.Value) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("decode %q: %w", opt.YAML, err)) - } - default: - panic("unreachable?") - } - default: - return xerrors.Errorf("unexpected kind for value %v", item.Kind) - } + for k := range m { + merr = errors.Join(merr, xerrors.Errorf("unknown option %q", k)) } + return merr } diff --git a/cli/root.go b/cli/root.go index 59096f4900bc7..ab1dca15c4676 100644 --- a/cli/root.go +++ b/cli/root.go @@ -682,7 +682,7 @@ func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client) func (r *RootCmd) checkWarnings(i *clibase.Invocation, client *codersdk.Client) error { if r.noFeatureWarning { return nil - } +} ctx, cancel := context.WithTimeout(i.Context(), 10*time.Second) defer cancel() From b03999afab7a309f938c5054935e671f54cbfad5 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 17:40:59 +0000 Subject: [PATCH 17/35] clibase passes --- cli/clibase/yaml.go | 38 ++++++++++++++++++++++++-------------- cli/clibase/yaml_test.go | 5 ++++- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 22d6d2b53ceed..5388cd2e3bfbe 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -154,28 +154,32 @@ func (g *Group) filterOptionSet(s *OptionSet) []*Option { // mapYAMLNodes converts n into a map with keys of form "group.subgroup.option" // and values of the corresponding YAML nodes. -func mapYAMLNodes(n *yaml.Node) (map[string]*yaml.Node, error) { - if n.Kind != yaml.MappingNode { - return nil, xerrors.Errorf("expected mapping node, got type %v", n.Kind) +func mapYAMLNodes(parent *yaml.Node) (map[string]*yaml.Node, error) { + if parent.Kind != yaml.MappingNode { + return nil, xerrors.Errorf("expected mapping node, got type %v", parent.Kind) + } + if len(parent.Content)%2 != 0 { + return nil, xerrors.Errorf("expected an even number of k/v pairs, got %d", len(parent.Content)) } var ( key string m = make(map[string]*yaml.Node) merr error ) - for i, node := range n.Content { - if i&2 == 0 { - if node.Kind != yaml.ScalarNode { - return nil, xerrors.Errorf("expected scalar node for key, got type %v", n.Content[i].Kind) + for i, child := range parent.Content { + if i%2 == 0 { + if child.Kind != yaml.ScalarNode { + return nil, xerrors.Errorf("expected scalar node for key, got type %v", child.Kind) } - key = node.Value + key = child.Value continue } - // Even if we have a mapping node, we don't know if it's a group or a - // complex option, so we store both. - m[key] = node - if node.Kind == yaml.MappingNode { - sub, err := mapYAMLNodes(node) + // Even if we have a mapping node, we don't know if it's a grouped simple + // option a complex option, so we store both "key" and "group.key". Since + // we're storing pointers, the additional memory is of little concern. + m[key] = child + if child.Kind == yaml.MappingNode { + sub, err := mapYAMLNodes(child) if err != nil { merr = errors.Join(merr, xerrors.Errorf("mapping node %q: %w", key, err)) continue @@ -190,6 +194,7 @@ func mapYAMLNodes(n *yaml.Node) (map[string]*yaml.Node, error) { } func (o *Option) setFromYAMLNode(n *yaml.Node) error { + o.ValueSource = ValueSourceYAML if um, ok := o.Value.(yaml.Unmarshaler); ok { return um.UnmarshalYAML(n) } @@ -224,7 +229,8 @@ func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { } var merr error - for _, opt := range *s { + for i := range *s { + opt := &(*s)[i] if opt.YAML == "" { continue } @@ -238,12 +244,16 @@ func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { ) } group = append(group, g.YAML) + // Delete groups from the map so that we are accurately detecting + // unknown options. + delete(m, strings.Join(group, ".")) } key := strings.Join(append(group, opt.YAML), ".") node, ok := m[key] if !ok { continue } + if err := opt.setFromYAMLNode(node); err != nil { merr = errors.Join(merr, xerrors.Errorf("setting %q: %w", opt.YAML, err)) } diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 07ec61cc839ff..d0ed8584c6904 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -58,6 +58,8 @@ func TestOptionSet_YAML(t *testing.T) { }) } +// TestOptionSet_YAMLIsomorphism tests that the YAML representations of an item +// converts to the same item when read back in. func TestOptionSet_YAMLIsomorphism(t *testing.T) { t.Parallel() //nolint:unused @@ -131,6 +133,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() + // Set initial values. for i := range tc.os { tc.os[i].Value = tc.zeroValue() } @@ -157,7 +160,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { // os2 values should be zeroed whereas tc.os should be // set to defaults. - // This makes sure we aren't mixing pointers. + // This check makes sure we aren't mixing pointers. require.NotEqual(t, tc.os, os2) err = os2.FromYAML(&y2) require.NoError(t, err) From d018838df5f884c3d886df9dfed654837f513da5 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 18:02:23 +0000 Subject: [PATCH 18/35] Fix UnknownOptions errors --- .golangci.yaml | 1 + cli/clibase/cmd.go | 2 +- cli/clibase/yaml.go | 53 +++++++++++++++----------- cli/clibase/yaml_test.go | 39 +++++++++++++++---- cli/server.go | 2 +- cli/server_test.go | 4 +- cli/testdata/server-config.yaml.golden | 4 -- 7 files changed, 67 insertions(+), 38 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 424f575bd3e9a..14389aae06be0 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -194,6 +194,7 @@ issues: linters: # We use assertions rather than explicitly checking errors in tests - errcheck + - forcetypeassert fix: true max-issues-per-linter: 0 diff --git a/cli/clibase/cmd.go b/cli/clibase/cmd.go index fd70b67632398..afdfb83b0158b 100644 --- a/cli/clibase/cmd.go +++ b/cli/clibase/cmd.go @@ -292,7 +292,7 @@ func (inv *Invocation) run(state *runState) error { return xerrors.Errorf("decoding config: %w", err) } - err = inv.Command.Options.FromYAML(&n) + err = inv.Command.Options.UnmarshalYAML(&n) if err != nil { return xerrors.Errorf("applying config: %w", err) } diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 5388cd2e3bfbe..0fe062070dfe9 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -10,6 +10,11 @@ import ( "gopkg.in/yaml.v3" ) +var ( + _ yaml.Marshaler = new(OptionSet) + _ yaml.Unmarshaler = new(OptionSet) +) + // 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 { @@ -39,14 +44,14 @@ func deepMapNode(n *yaml.Node, path []string, headComment string) *yaml.Node { return deepMapNode(&valueNode, path[1:], headComment) } -// ToYAML converts the option set to a YAML node, that can be +// MarshalYAML 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. // // It is isomorphic with FromYAML. -func (s *OptionSet) ToYAML() (*yaml.Node, error) { +func (s *OptionSet) MarshalYAML() (any, error) { root := yaml.Node{ Kind: yaml.MappingNode, } @@ -140,18 +145,6 @@ func (s *OptionSet) ToYAML() (*yaml.Node, error) { return &root, nil } -func (g *Group) filterOptionSet(s *OptionSet) []*Option { - var opts []*Option - for i := range *s { - opt := (*s)[i] - if opt.Group != g { - continue - } - opts = append(opts, &opt) - } - return opts -} - // mapYAMLNodes converts n into a map with keys of form "group.subgroup.option" // and values of the corresponding YAML nodes. func mapYAMLNodes(parent *yaml.Node) (map[string]*yaml.Node, error) { @@ -211,9 +204,9 @@ func (o *Option) setFromYAMLNode(n *yaml.Node) error { } } -// FromYAML converts the given YAML node into the option set. +// UnmarshalYAML converts the given YAML node into the option set. // It is isomorphic with ToYAML. -func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { +func (s *OptionSet) UnmarshalYAML(rootNode *yaml.Node) error { // The rootNode will be a DocumentNode if it's read from a file. Currently, // we don't support multiple YAML documents. if rootNode.Kind == yaml.DocumentNode { @@ -223,11 +216,13 @@ func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { rootNode = rootNode.Content[0] } - m, err := mapYAMLNodes(rootNode) + yamlNodes, err := mapYAMLNodes(rootNode) if err != nil { return xerrors.Errorf("mapping nodes: %w", err) } + matchedNodes := make(map[string]*yaml.Node, len(yamlNodes)) + var merr error for i := range *s { opt := &(*s)[i] @@ -244,12 +239,10 @@ func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { ) } group = append(group, g.YAML) - // Delete groups from the map so that we are accurately detecting - // unknown options. - delete(m, strings.Join(group, ".")) + delete(yamlNodes, strings.Join(group, ".")) } key := strings.Join(append(group, opt.YAML), ".") - node, ok := m[key] + node, ok := yamlNodes[key] if !ok { continue } @@ -257,10 +250,24 @@ func (s *OptionSet) FromYAML(rootNode *yaml.Node) error { if err := opt.setFromYAMLNode(node); err != nil { merr = errors.Join(merr, xerrors.Errorf("setting %q: %w", opt.YAML, err)) } - delete(m, key) + matchedNodes[key] = node } - for k := range m { + // Remove all matched nodes and their descendants from yamlNodes so we + // can accurately report unknown options. + for k := range yamlNodes { + var key string + for _, part := range strings.Split(k, ".") { + if key != "" { + key += "." + } + key += part + if _, ok := matchedNodes[key]; ok { + delete(yamlNodes, k) + } + } + } + for k := range yamlNodes { merr = errors.Join(merr, xerrors.Errorf("unknown option %q", k)) } diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index d0ed8584c6904..9fc0d721a99da 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -25,9 +25,9 @@ func TestOptionSet_YAML(t *testing.T) { }, } - node, err := os.ToYAML() + node, err := os.MarshalYAML() require.NoError(t, err) - require.Len(t, node.Content, 0) + require.Len(t, node.(*yaml.Node).Content, 0) }) t.Run("SimpleString", func(t *testing.T) { @@ -49,7 +49,7 @@ func TestOptionSet_YAML(t *testing.T) { err := os.SetDefaults() require.NoError(t, err) - n, err := os.ToYAML() + n, err := os.MarshalYAML() require.NoError(t, err) // Visually inspect for now. byt, err := yaml.Marshal(n) @@ -58,8 +58,33 @@ func TestOptionSet_YAML(t *testing.T) { }) } -// TestOptionSet_YAMLIsomorphism tests that the YAML representations of an item -// converts to the same item when read back in. +func TestOptionSet_YAMLUnknownOptions(t *testing.T) { + t.Parallel() + os := clibase.OptionSet{ + { + Name: "Workspace Name", + Default: "billie", + Description: "The workspace's name.", + YAML: "workspaceName", + Value: new(clibase.String), + }, + } + + const yamlDoc = `something: else` + err := yaml.Unmarshal([]byte(yamlDoc), &os) + require.Error(t, err) + require.Empty(t, os[0].Value.String()) + + os[0].YAML = "something" + + err = yaml.Unmarshal([]byte(yamlDoc), &os) + require.NoError(t, err) + + require.Equal(t, "else", os[0].Value.String()) +} + +// TestOptionSet_YAMLIsomorphism tests that the YAML representations of an +// OptionSet converts to the same OptionSet when read back in. func TestOptionSet_YAMLIsomorphism(t *testing.T) { t.Parallel() //nolint:unused @@ -140,7 +165,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { err := tc.os.SetDefaults() require.NoError(t, err) - y, err := tc.os.ToYAML() + y, err := tc.os.MarshalYAML() require.NoError(t, err) toByt, err := yaml.Marshal(y) @@ -162,7 +187,7 @@ func TestOptionSet_YAMLIsomorphism(t *testing.T) { // set to defaults. // This check makes sure we aren't mixing pointers. require.NotEqual(t, tc.os, os2) - err = os2.FromYAML(&y2) + err = os2.UnmarshalYAML(&y2) require.NoError(t, err) want := tc.os diff --git a/cli/server.go b/cli/server.go index 6c06f2138f47b..1dc8c3668490f 100644 --- a/cli/server.go +++ b/cli/server.go @@ -175,7 +175,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. defer cancel() if cfg.WriteConfig { - n, err := opts.ToYAML() + n, err := opts.MarshalYAML() if err != nil { return xerrors.Errorf("generate yaml: %w", err) } diff --git a/cli/server_test.go b/cli/server_test.go index ce4df0bf0d2a1..09d277322440f 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1538,11 +1538,11 @@ func TestServerYAMLConfig(t *testing.T) { err := opts.SetDefaults() require.NoError(t, err) - n, err := opts.ToYAML() + n, err := opts.MarshalYAML() require.NoError(t, err) // Sanity-check that we can read the config back in. - err = opts.FromYAML(n) + err = opts.UnmarshalYAML(n) require.NoError(t, err) wantByt, err := yaml.Marshal(n) diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 0a72b08a60820..fa4fbe9deaef6 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -246,8 +246,6 @@ sshKeygenAlgorithm: ed25519 # URL to use for agent troubleshooting when not set in the template. (default: # https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates) agentFallbackTroubleshootingURL: https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates -# Specifies whether audit logging is enabled. (default: true) -auditLogging: true # 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 @@ -265,8 +263,6 @@ client: sshConfigOptions: [] # Support links to display in the top right drop down menu. (default: ) supportLinks: [] -# Git Authentication providers. (default: ) -gitAuthProviders: [] # Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By # default, this will pick the best available wgtunnel server hosted by Coder. e.g. # "tunnel.example.com". (default: ) From fe93d7f1280da26fa52f7b03541b63387faad6c3 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 19:21:33 +0000 Subject: [PATCH 19/35] Work on nil normalization --- cli/clibase/option.go | 12 ++ cli/clibase/yaml.go | 9 +- cli/root.go | 2 +- cli/server.go | 2 - cli/server_test.go | 59 ++++++-- cli/testdata/server-config.yaml.golden | 182 ++++++++++++++----------- 6 files changed, 170 insertions(+), 96 deletions(-) diff --git a/cli/clibase/option.go b/cli/clibase/option.go index 05c811e1dc73d..0d2d1fc46528e 100644 --- a/cli/clibase/option.go +++ b/cli/clibase/option.go @@ -194,3 +194,15 @@ func (s *OptionSet) SetDefaults() error { } return merr.ErrorOrNil() } + +// ByName returns the Option with the given name, or nil if no such option +// exists. +func (s *OptionSet) ByName(name string) *Option { + for i := range *s { + opt := &(*s)[i] + if opt.Name == name { + return opt + } + } + return nil +} diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 0fe062070dfe9..4e374f4297d57 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -66,7 +66,7 @@ func (s *OptionSet) MarshalYAML() (any, error) { defValue = "" } comment := wordwrap.WrapString( - fmt.Sprintf("%s (default: %s)", opt.Description, defValue), + fmt.Sprintf("%s (default: %s, type: %s)", opt.Description, defValue, opt.Value.Type()), 80, ) nameNode := yaml.Node{ @@ -75,7 +75,12 @@ func (s *OptionSet) MarshalYAML() (any, error) { HeadComment: wordwrap.WrapString(comment, 80), } var valueNode yaml.Node - if m, ok := opt.Value.(yaml.Marshaler); ok { + if opt.Value == nil { + valueNode = yaml.Node{ + Kind: yaml.ScalarNode, + Value: "null", + } + } else if m, ok := opt.Value.(yaml.Marshaler); ok { v, err := m.MarshalYAML() if err != nil { return nil, xerrors.Errorf( diff --git a/cli/root.go b/cli/root.go index ab1dca15c4676..59096f4900bc7 100644 --- a/cli/root.go +++ b/cli/root.go @@ -682,7 +682,7 @@ func (r *RootCmd) checkVersions(i *clibase.Invocation, client *codersdk.Client) func (r *RootCmd) checkWarnings(i *clibase.Invocation, client *codersdk.Client) error { if r.noFeatureWarning { return nil -} + } ctx, cancel := context.WithTimeout(i.Context(), 10*time.Second) defer cancel() diff --git a/cli/server.go b/cli/server.go index 1dc8c3668490f..fcaffbea3983b 100644 --- a/cli/server.go +++ b/cli/server.go @@ -195,8 +195,6 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. cliui.Warnf(inv.Stderr, "YAML support is experimental and offers no compatibility guarantees.") } - println("HERE1") - // Print deprecation warnings. for _, opt := range opts { if opt.UseInstead == nil { diff --git a/cli/server_test.go b/cli/server_test.go index 09d277322440f..eaa998cab7124 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -21,6 +21,7 @@ import ( "net/url" "os" "path/filepath" + "reflect" "runtime" "strconv" "strings" @@ -35,6 +36,7 @@ import ( "gopkg.in/yaml.v3" "github.com/coder/coder/cli" + "github.com/coder/coder/cli/clibase" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/config" "github.com/coder/coder/coderd/coderdtest" @@ -1441,12 +1443,13 @@ func TestServer(t *testing.T) { client := codersdk.New(gotURL) _ = coderdtest.CreateFirstUser(t, client) - wantValues, err := client.DeploymentConfig(ctx) + wantConfig, err := client.DeploymentConfig(ctx) require.NoError(t, err) cancel() w.RequireSuccess() - // Next, we instruct the same server to display config. + // Next, we instruct the same server to display the YAML config + // and then save it. inv = inv.WithContext(testutil.Context(t, testutil.WaitMedium)) inv.Args = append(args, "--write-config") fi, err := os.OpenFile(testutil.TempFile(t, "", "coder-config-test-*"), os.O_WRONLY|os.O_CREATE, 0o600) @@ -1458,19 +1461,55 @@ func TestServer(t *testing.T) { err = inv.Run() require.NoError(t, err) - // Finally, we read the config back in and ensure it matches the - // original. - inv.Args = append(args, "--config="+fi.Name()) + // Reset the context. + ctx = testutil.Context(t, testutil.WaitMedium) + // Finally, we restart the server with just the config and no flags + // and ensure that the live configuration is equivalent. + inv, cfg = clitest.New(t, "server", "--config="+fi.Name()) w = clitest.StartWithWaiter(t, inv) - // The same client should work. - gotValues, err := client.DeploymentConfig(ctx) - require.NoError(t, err, "config:\n%s", conf.String()) - require.Equal(t, wantValues, gotValues) + client = codersdk.New(waitAccessURL(t, cfg)) + _ = coderdtest.CreateFirstUser(t, client) + gotConfig, err := client.DeploymentConfig(ctx) + normalizeNilsInOptionSet(&wantConfig.Options) + normalizeNilsInOptionSet(&gotConfig.Options) + require.NoError(t, err, "config:\n%s\nargs: %+v", conf.String(), inv.Args) + gotConfig.Options.ByName("Config Path").Value.Set("") + require.EqualValues(t, wantConfig.Options, gotConfig.Options) w.RequireSuccess() }) }) } +// The YAML process sets slice values to nil if they are empty, leading to +// false negatives when comparing equality. +func normalizeNilsInOptionSet(s *clibase.OptionSet) { + for i := range *s { + opt := &(*s)[i] + if opt.Value == nil { + continue + } + + var ( + v = reflect.Indirect(reflect.ValueOf(opt.Value)) + kind = v.Type().Kind() + ) + + if opt.YAML == "supportLinks" { + fmt.Printf("supportLinks: %v, %v", kind, v.IsZero()) + } + + if kind == reflect.Slice && v.Len() > 0 { + continue + } + + if kind == reflect.Struct && !v.IsZero() { + continue + } + + opt.Value = nil + } +} + func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) { dir := t.TempDir() @@ -1542,7 +1581,7 @@ func TestServerYAMLConfig(t *testing.T) { require.NoError(t, err) // Sanity-check that we can read the config back in. - err = opts.UnmarshalYAML(n) + err = opts.UnmarshalYAML(n.(*yaml.Node)) require.NoError(t, err) wantByt, err := yaml.Marshal(n) diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index fa4fbe9deaef6..f2f113ee699e0 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -1,77 +1,79 @@ networking: - # The URL that users will use to access the Coder deployment. (default: ) + # The URL that users will use to access the Coder deployment. (default: , + # type: url) accessURL: # Specifies the wildcard hostname to use for workspace applications in the form - # "*.example.com". (default: ) + # "*.example.com". (default: , type: url) wildcardAccessURL: # Specifies whether to redirect requests that do not match the access URL host. - # (default: ) + # (default: , type: bool) redirectToAccessURL: false http: # HTTP bind address of the server. Unset to disable the HTTP endpoint. (default: - # 127.0.0.1:3000) + # 127.0.0.1:3000, type: string) httpAddress: 127.0.0.1:3000 # The maximum lifetime duration users can specify when creating an API token. - # (default: 876600h0m0s) + # (default: 876600h0m0s, type: duration) maxTokenLifetime: 876600h0m0s # 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. (default: 24h0m0s) + # --disable-session-expiry-refresh. (default: 24h0m0s, type: duration) sessionDuration: 24h0m0s # Disable automatic session expiry bumping due to activity. This forces all # sessions to become invalid after the session expiry duration has been reached. - # (default: ) + # (default: , type: bool) disableSessionExpiryRefresh: false # 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. (default: ) + # directly in the database. (default: , type: bool) disablePasswordAuth: false # 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. tls: - # HTTPS bind address of the server. (default: 127.0.0.1:3443) + # HTTPS bind address of the server. (default: 127.0.0.1:3443, type: host:port) address: 127.0.0.1:3443 - # Whether TLS will be enabled. (default: ) + # Whether TLS will be enabled. (default: , type: bool) enable: false # 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. (default: true) + # regardless of this setting. (default: true, type: bool) redirectHTTP: true # 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. (default: ) + # combined file. (default: , type: string-array) certFiles: [] # PEM-encoded Certificate Authority file used for checking the authenticity of - # client. (default: ) + # client. (default: , type: string) clientCAFile: "" # Policy the server will follow for TLS Client Authentication. Accepted values are # "none", "request", "require-any", "verify-if-given", or "require-and-verify". - # (default: none) + # (default: none, type: string) clientAuth: none # Paths to the private keys for each of the certificates. It requires a - # PEM-encoded file. (default: ) + # PEM-encoded file. (default: , type: string-array) keyFiles: [] # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" - # or "tls13". (default: tls12) + # or "tls13". (default: tls12, type: string) minVersion: tls12 # Path to certificate for client TLS authentication. It requires a PEM-encoded - # file. (default: ) + # file. (default: , type: string) clientCertFile: "" # Path to key for client TLS authentication. It requires a PEM-encoded file. - # (default: ) + # (default: , type: string) clientKeyFile: "" # 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) + # This value is the MaxAge in seconds of the header. (default: 0, type: int) strictTransportSecurity: 0 # 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. (default: ) + # set to a non-zero value for these options to be used. (default: , type: + # string-array) strictTransportSecurityOptions: [] # Most Coder deployments never have to think about DERP because all connections # between workspaces and users are peer-to-peer. However, when Coder cannot @@ -79,191 +81,209 @@ networking: # a peer to peer connection, Coder uses a distributed relay network backed by # Tailscale and WireGuard. derp: - # Whether to enable or disable the embedded DERP relay server. (default: true) + # Whether to enable or disable the embedded DERP relay server. (default: true, + # type: bool) enable: true - # Region ID to use for the embedded DERP server. (default: 999) + # Region ID to use for the embedded DERP server. (default: 999, type: int) regionID: 999 - # Region code to use for the embedded DERP server. (default: coder) + # Region code to use for the embedded DERP server. (default: coder, type: string) regionCode: coder - # Region name that for the embedded DERP server. (default: Coder Embedded Relay) + # Region name that for the embedded DERP server. (default: Coder Embedded Relay, + # type: string) regionName: Coder Embedded Relay # Addresses for STUN servers to establish P2P connections. Set empty to disable - # P2P connections. (default: stun.l.google.com:19302) + # P2P connections. (default: stun.l.google.com:19302, type: string-array) stunAddresses: - stun.l.google.com:19302 # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required - # for high availability. (default: ) + # for high availability. (default: , type: url) relayURL: # URL to fetch a DERP mapping on startup. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. (default: ) + # https://tailscale.com/kb/1118/custom-derp-servers/. (default: , type: + # string) url: "" # Path to read a DERP mapping from. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. (default: ) + # https://tailscale.com/kb/1118/custom-derp-servers/. (default: , type: + # string) configPath: "" # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, - # True-Client-Ip, X-Forwarded-For. (default: ) + # True-Client-Ip, X-Forwarded-For. (default: , type: string-array) proxyTrustedHeaders: [] # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. - # (default: ) + # (default: , type: string-array) proxyTrustedOrigins: [] # Controls if the 'Secure' property is set on browser session cookies. (default: - # ) + # , type: bool) secureAuthCookie: false # Whether Coder only allows connections to workspaces via the browser. (default: - # ) + # , type: bool) browserOnly: false -# Interval to poll for scheduled workspace builds. (default: 1m0s) +# Interval to poll for scheduled workspace builds. (default: 1m0s, type: duration) autobuildPollInterval: 1m0s introspection: prometheus: # Serve prometheus metrics on the address defined by prometheus address. (default: - # ) + # , type: bool) enable: false - # The bind address to serve prometheus metrics. (default: 127.0.0.1:2112) + # The bind address to serve prometheus metrics. (default: 127.0.0.1:2112, type: + # host:port) address: 127.0.0.1:2112 pprof: - # Serve pprof metrics on the address defined by pprof address. (default: ) + # Serve pprof metrics on the address defined by pprof address. (default: , + # type: bool) enable: false - # The bind address to serve pprof. (default: 127.0.0.1:6060) + # The bind address to serve pprof. (default: 127.0.0.1:6060, type: host:port) address: 127.0.0.1:6060 tracing: # 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. - # (default: ) + # (default: , type: bool) enable: false # 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. (default: ) + # logs will be included. (default: , type: bool) captureLogs: false logging: - # Output debug-level logs. (default: ) + # Output debug-level logs. (default: , type: bool) verbose: false - # Output human-readable logs to a given file. (default: /dev/stderr) + # Output human-readable logs to a given file. (default: /dev/stderr, type: string) humanPath: /dev/stderr - # Output JSON logs to a given file. (default: ) + # Output JSON logs to a given file. (default: , type: string) jsonPath: "" - # Output Stackdriver compatible logs to a given file. (default: ) + # Output Stackdriver compatible logs to a given file. (default: , type: + # string) stackdriverPath: "" oauth2: github: - # Client ID for Login with GitHub. (default: ) + # Client ID for Login with GitHub. (default: , type: string) clientID: "" # Organizations the user must be a member of to Login with GitHub. (default: - # ) + # , type: string-array) allowedOrgs: [] # Teams inside organizations the user must be a member of to Login with GitHub. - # Structured as: /. (default: ) + # Structured as: /. (default: , type: + # string-array) allowedTeams: [] - # Whether new users can sign up with GitHub. (default: ) + # Whether new users can sign up with GitHub. (default: , type: bool) allowSignups: false # Allow all logins, setting this option means allowed orgs and teams must be - # empty. (default: ) + # empty. (default: , type: bool) allowEveryone: false # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. - # (default: ) + # (default: , type: string) enterpriseBaseURL: "" oidc: - # Whether new users can sign up with OIDC. (default: true) + # Whether new users can sign up with OIDC. (default: true, type: bool) allowSignups: true - # Client ID to use for Login with OIDC. (default: ) + # Client ID to use for Login with OIDC. (default: , type: string) clientID: "" - # Email domains that clients logging in with OIDC must match. (default: ) + # Email domains that clients logging in with OIDC must match. (default: , + # type: string-array) emailDomain: [] - # Issuer URL to use for Login with OIDC. (default: ) + # Issuer URL to use for Login with OIDC. (default: , type: string) issuerURL: "" - # Scopes to grant when authenticating with OIDC. (default: openid,profile,email) + # Scopes to grant when authenticating with OIDC. (default: openid,profile,email, + # type: string-array) scopes: - openid - profile - email - # Ignore the email_verified claim from the upstream provider. (default: ) + # Ignore the email_verified claim from the upstream provider. (default: , + # type: bool) ignoreEmailVerified: false - # OIDC claim field to use as the username. (default: preferred_username) + # OIDC claim field to use as the username. (default: preferred_username, type: + # string) usernameField: preferred_username - # OIDC claim field to use as the email. (default: email) + # OIDC claim field to use as the email. (default: email, type: string) emailField: email # OIDC auth URL parameters to pass to the upstream provider. (default: - # {"access_type": "offline"}) + # {"access_type": "offline"}, type: struct[map[string]string]) authURLParams: access_type: offline # Change the OIDC default 'groups' claim field. By default, will be 'groups' if - # present in the oidc scopes argument. (default: ) + # present in the oidc scopes argument. (default: , type: string) groupField: "" # A map of OIDC group IDs and the group in Coder it should map to. This is useful - # for when OIDC providers only return group IDs. (default: {}) + # for when OIDC providers only return group IDs. (default: {}, type: + # struct[map[string]string]) groupMapping: {} - # The text to show on the OpenID Connect sign in button. (default: OpenID Connect) + # The text to show on the OpenID Connect sign in button. (default: OpenID Connect, + # type: string) signInText: OpenID Connect # URL pointing to the icon to use on the OepnID Connect login button. (default: - # ) + # , type: url) iconURL: # 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. telemetry: # Whether telemetry is enabled or not. Coder collects anonymized usage data to - # help improve our product. (default: false) + # help improve our product. (default: false, type: bool) enable: false # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized # application tracing to help improve our product. Disabling telemetry also - # disables this option. (default: false) + # disables this option. (default: false, type: bool) trace: false - # URL to send telemetry. (default: https://telemetry.coder.com) + # URL to send telemetry. (default: https://telemetry.coder.com, type: url) url: https://telemetry.coder.com # Tune the behavior of the provisioner, which is responsible for creating, # updating, and deleting workspace resources. provisioning: # Number of provisioner daemons to create on start. If builds are stuck in queued - # state for a long time, consider increasing this. (default: 3) + # state for a long time, consider increasing this. (default: 3, type: int) daemons: 3 - # Time to wait before polling for a new job. (default: 1s) + # Time to wait before polling for a new job. (default: 1s, type: duration) daemonPollInterval: 1s - # Random jitter added to the poll interval. (default: 100ms) + # Random jitter added to the poll interval. (default: 100ms, type: duration) daemonPollJitter: 100ms - # Time to force cancel provisioning tasks that are stuck. (default: 10m0s) + # Time to force cancel provisioning tasks that are stuck. (default: 10m0s, type: + # duration) forceCancelInterval: 10m0s # 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. (default: ) +# experiments. (default: , type: string-array) experiments: [] # Periodically check for new releases of Coder and inform the owner. The check is -# performed once per day. (default: false) +# performed once per day. (default: false, type: bool) updateCheck: false -# Expose the swagger endpoint via /swagger. (default: ) +# Expose the swagger endpoint via /swagger. (default: , type: bool) enableSwagger: false # The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is # set, it will be used for compatibility with systemd. (default: -# /home/coder/.cache/coder) +# /home/coder/.cache/coder, type: string) cacheDir: /home/coder/.cache/coder # Controls whether data will be stored in an in-memory database. (default: -# ) +# , type: bool) inMemoryDatabase: false # The algorithm to use for generating ssh keys. Accepted values are "ed25519", -# "ecdsa", or "rsa4096". (default: ed25519) +# "ecdsa", or "rsa4096". (default: ed25519, type: string) sshKeygenAlgorithm: ed25519 # URL to use for agent troubleshooting when not set in the template. (default: -# https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates) +# https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates, +# type: url) agentFallbackTroubleshootingURL: https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates # 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. (default: ) +# --wildcard-access-url is configured. (default: , type: bool) disablePathApps: false # These options change the behavior of how clients interact with the Coder. # Clients include the coder cli, vs code extension, and the web UI. client: # The SSH deployment prefix is used in the Host of the ssh config. (default: - # coder.) + # coder., type: string) sshHostnamePrefix: coder. # These SSH config options will override the default SSH config options. Provide # options in "key=value" or "key value" format separated by commas.Using this - # incorrectly can break SSH to your deployment, use cautiously. (default: ) + # incorrectly can break SSH to your deployment, use cautiously. (default: , + # type: string-array) sshConfigOptions: [] -# Support links to display in the top right drop down menu. (default: ) +# Support links to display in the top right drop down menu. (default: , +# type: struct[[]codersdk.LinkConfig]) supportLinks: [] # Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By # default, this will pick the best available wgtunnel server hosted by Coder. e.g. -# "tunnel.example.com". (default: ) +# "tunnel.example.com". (default: , type: string) wgtunnelHost: "" From 0af47bbf1dd2cc462fa85b45effd6d2f58bf6bca Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 21:01:51 +0000 Subject: [PATCH 20/35] Server tests pass! --- cli/clibase/values.go | 7 +++++++ cli/clibase/yaml.go | 6 ++++++ cli/server_test.go | 44 +++++++++---------------------------------- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/cli/clibase/values.go b/cli/clibase/values.go index f4f9a5b97f2bd..532565551566c 100644 --- a/cli/clibase/values.go +++ b/cli/clibase/values.go @@ -6,6 +6,7 @@ import ( "fmt" "net" "net/url" + "reflect" "strconv" "strings" "time" @@ -350,6 +351,12 @@ func (s *Struct[T]) MarshalYAML() (interface{}, error) { } func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error { + // HACK: for compatibility with flags, we set the value to nil if the node + // is empty and T is a slice. + if typ := reflect.TypeOf(s.Value); typ.Kind() == reflect.Slice && len(n.Content) == 0 { + reflect.ValueOf(&s.Value).Elem().Set(reflect.Zero(typ)) + return nil + } return n.Decode(&s.Value) } diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 4e374f4297d57..2f4c2b56f3e6e 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -201,6 +201,12 @@ func (o *Option) setFromYAMLNode(n *yaml.Node) error { case yaml.ScalarNode: return o.Value.Set(n.Value) case yaml.SequenceNode: + // We treat empty values as nil for consistency with other option + // mechanisms. + if len(n.Content) == 0 { + o.Value = nil + return nil + } return n.Decode(o.Value) case yaml.MappingNode: return xerrors.Errorf("mapping node must implement yaml.Unmarshaler") diff --git a/cli/server_test.go b/cli/server_test.go index eaa998cab7124..4354d186d7c37 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -21,7 +21,6 @@ import ( "net/url" "os" "path/filepath" - "reflect" "runtime" "strconv" "strings" @@ -36,7 +35,6 @@ import ( "gopkg.in/yaml.v3" "github.com/coder/coder/cli" - "github.com/coder/coder/cli/clibase" "github.com/coder/coder/cli/clitest" "github.com/coder/coder/cli/config" "github.com/coder/coder/coderd/coderdtest" @@ -1470,46 +1468,22 @@ func TestServer(t *testing.T) { client = codersdk.New(waitAccessURL(t, cfg)) _ = coderdtest.CreateFirstUser(t, client) gotConfig, err := client.DeploymentConfig(ctx) - normalizeNilsInOptionSet(&wantConfig.Options) - normalizeNilsInOptionSet(&gotConfig.Options) require.NoError(t, err, "config:\n%s\nargs: %+v", conf.String(), inv.Args) gotConfig.Options.ByName("Config Path").Value.Set("") - require.EqualValues(t, wantConfig.Options, gotConfig.Options) + // We check the options individually for better error messages. + for i := range wantConfig.Options { + assert.Equal( + t, wantConfig.Options[i], + gotConfig.Options[i], + "option %q", + wantConfig.Options[i].Name, + ) + } w.RequireSuccess() }) }) } -// The YAML process sets slice values to nil if they are empty, leading to -// false negatives when comparing equality. -func normalizeNilsInOptionSet(s *clibase.OptionSet) { - for i := range *s { - opt := &(*s)[i] - if opt.Value == nil { - continue - } - - var ( - v = reflect.Indirect(reflect.ValueOf(opt.Value)) - kind = v.Type().Kind() - ) - - if opt.YAML == "supportLinks" { - fmt.Printf("supportLinks: %v, %v", kind, v.IsZero()) - } - - if kind == reflect.Slice && v.Len() > 0 { - continue - } - - if kind == reflect.Struct && !v.IsZero() { - continue - } - - opt.Value = nil - } -} - func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) { dir := t.TempDir() From 64255b39668ad5ec3ae5662a57186143de1de2f8 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 21:23:20 +0000 Subject: [PATCH 21/35] make gen + self review cleanup --- cli/clibase/cmd.go | 16 +++----- cli/clibase/values.go | 5 ++- cli/clibase/yaml.go | 41 ++++++++++--------- cli/clitest/clitest.go | 2 +- cli/testdata/coder_server_--help.golden | 7 +++- cli/testdata/server-config.yaml.golden | 3 ++ coderd/apidoc/docs.go | 6 --- coderd/apidoc/swagger.json | 6 --- docs/api/general.md | 10 ----- docs/api/schemas.md | 53 +++---------------------- docs/cli/server.md | 17 ++++++++ 11 files changed, 64 insertions(+), 102 deletions(-) diff --git a/cli/clibase/cmd.go b/cli/clibase/cmd.go index afdfb83b0158b..1c9a848ce0ce9 100644 --- a/cli/clibase/cmd.go +++ b/cli/clibase/cmd.go @@ -270,31 +270,27 @@ func (inv *Invocation) run(state *runState) error { } } - // Read configs, if any. + // Read YAML configs, if any. for _, opt := range inv.Command.Options { path, ok := opt.Value.(*YAMLConfigPath) if !ok || path.String() == "" { continue } - fi, err := os.OpenFile(path.String(), os.O_RDONLY, 0) + byt, err := os.ReadFile(path.String()) if err != nil { - return xerrors.Errorf("opening config file: %w", err) + return xerrors.Errorf("reading yaml: %w", err) } - //nolint:revive - defer fi.Close() - - dec := yaml.NewDecoder(fi) var n yaml.Node - err = dec.Decode(&n) + err = yaml.Unmarshal(byt, &n) if err != nil { - return xerrors.Errorf("decoding config: %w", err) + return xerrors.Errorf("decoding yaml: %w", err) } err = inv.Command.Options.UnmarshalYAML(&n) if err != nil { - return xerrors.Errorf("applying config: %w", err) + return xerrors.Errorf("applying yaml: %w", err) } } diff --git a/cli/clibase/values.go b/cli/clibase/values.go index 532565551566c..55f808ad317a0 100644 --- a/cli/clibase/values.go +++ b/cli/clibase/values.go @@ -351,8 +351,9 @@ func (s *Struct[T]) MarshalYAML() (interface{}, error) { } func (s *Struct[T]) UnmarshalYAML(n *yaml.Node) error { - // HACK: for compatibility with flags, we set the value to nil if the node - // is empty and T is a slice. + // HACK: for compatibility with flags, we use nil slices instead of empty + // slices. In most cases, nil slices and empty slices are treated + // the same, so this behavior may be removed at some point. if typ := reflect.TypeOf(s.Value); typ.Kind() == reflect.Slice && len(n.Content) == 0 { reflect.ValueOf(&s.Value).Elem().Set(reflect.Zero(typ)) return nil diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 2f4c2b56f3e6e..5b7d117b9023f 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -150,8 +150,8 @@ func (s *OptionSet) MarshalYAML() (any, error) { return &root, nil } -// mapYAMLNodes converts n into a map with keys of form "group.subgroup.option" -// and values of the corresponding YAML nodes. +// mapYAMLNodes converts parent into a map with keys of form "group.subgroup.option" +// and values as the corresponding YAML nodes. func mapYAMLNodes(parent *yaml.Node) (map[string]*yaml.Node, error) { if parent.Kind != yaml.MappingNode { return nil, xerrors.Errorf("expected mapping node, got type %v", parent.Kind) @@ -161,30 +161,35 @@ func mapYAMLNodes(parent *yaml.Node) (map[string]*yaml.Node, error) { } var ( key string - m = make(map[string]*yaml.Node) + m = make(map[string]*yaml.Node, len(parent.Content)/2) merr error ) for i, child := range parent.Content { if i%2 == 0 { if child.Kind != yaml.ScalarNode { + // We immediately because the rest of the code is bound to fail + // if we don't know to expect a key or a value. return nil, xerrors.Errorf("expected scalar node for key, got type %v", child.Kind) } key = child.Value continue } - // Even if we have a mapping node, we don't know if it's a grouped simple - // option a complex option, so we store both "key" and "group.key". Since - // we're storing pointers, the additional memory is of little concern. + + // We don't know if this is a grouped simple option or complex option, + // so we store both "key" and "group.key". Since we're storing pointers, + // the additional memory is of little concern. m[key] = child - if child.Kind == yaml.MappingNode { - sub, err := mapYAMLNodes(child) - if err != nil { - merr = errors.Join(merr, xerrors.Errorf("mapping node %q: %w", key, err)) - continue - } - for k, v := range sub { - m[key+"."+k] = v - } + if child.Kind != yaml.MappingNode { + continue + } + + sub, err := mapYAMLNodes(child) + if err != nil { + merr = errors.Join(merr, xerrors.Errorf("mapping node %q: %w", key, err)) + continue + } + for k, v := range sub { + m[key+"."+k] = v } } @@ -209,7 +214,7 @@ func (o *Option) setFromYAMLNode(n *yaml.Node) error { } return n.Decode(o.Value) case yaml.MappingNode: - return xerrors.Errorf("mapping node must implement yaml.Unmarshaler") + return xerrors.Errorf("mapping nodes must implement yaml.Unmarshaler") default: return xerrors.Errorf("unexpected node kind %v", n.Kind) } @@ -218,8 +223,8 @@ func (o *Option) setFromYAMLNode(n *yaml.Node) error { // UnmarshalYAML converts the given YAML node into the option set. // It is isomorphic with ToYAML. func (s *OptionSet) UnmarshalYAML(rootNode *yaml.Node) error { - // The rootNode will be a DocumentNode if it's read from a file. Currently, - // we don't support multiple YAML documents. + // The rootNode will be a DocumentNode if it's read from a file. We do + // not support multiple documents in a single file. if rootNode.Kind == yaml.DocumentNode { if len(rootNode.Content) != 1 { return xerrors.Errorf("expected one node in document, got %d", len(rootNode.Content)) diff --git a/cli/clitest/clitest.go b/cli/clitest/clitest.go index 5048564cc22f5..1237cefc375cd 100644 --- a/cli/clitest/clitest.go +++ b/cli/clitest/clitest.go @@ -226,7 +226,7 @@ func StartWithWaiter(t *testing.T, inv *clibase.Invocation) *ErrorWaiter { // down Postgres. t.Logf("command %q timed out during test cleanup", inv.Command.FullName()) } - // When or not this failed the test is up to the caller. + // Whether or not this fails the test is left to the caller. t.Logf("command %q exited with error: %v", inv.Command.FullName(), err) errCh <- err }() diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 76ac9c504fcee..0b5eef154fd86 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -60,9 +60,12 @@ Clients include the coder cli, vs code extension, and the web UI. Config Options Use a YAML configuration file when your server launch become unwieldy. - --write-config bool, $CODER_WRITE_CONFIG + -c, --config yaml-config-path, $CODER_CONFIG_PATH + Specify a YAML file to load configuration from. + + --write-config bool - Write out the current server as YAML to stdout. + Write out the current server config as YAML to stdout. Introspection / Logging Options --log-human string, $CODER_LOGGING_HUMAN (default: /dev/stderr) diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index f2f113ee699e0..6ded301c2cfdf 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -202,6 +202,9 @@ oidc: # {"access_type": "offline"}, type: struct[map[string]string]) authURLParams: access_type: offline + # Ignore the userinfo endpoint and only use the ID token for user information. + # (default: false, type: bool) + ignoreUserInfo: false # Change the OIDC default 'groups' claim field. By default, will be 'groups' if # present in the oidc scopes argument. (default: , type: string) groupField: "" diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 4c71b8d0677ee..756ff962e771b 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5721,12 +5721,6 @@ const docTemplate = `{ "clibase.Group": { "type": "object", "properties": { - "children": { - "type": "array", - "items": { - "$ref": "#/definitions/clibase.Group" - } - }, "description": { "type": "string" }, diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8bfa6db5ea1ba..e434373cd68b7 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5066,12 +5066,6 @@ "clibase.Group": { "type": "object", "properties": { - "children": { - "type": "array", - "items": { - "$ref": "#/definitions/clibase.Group" - } - }, "description": { "type": "string" }, diff --git a/docs/api/general.md b/docs/api/general.md index 6826bbf9da793..9d0c9a996b6cc 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -371,19 +371,9 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "flag": "string", "flag_shorthand": "string", "group": { - "children": [ - { - "children": [], - "description": "string", - "name": "string", - "parent": {}, - "yaml_name": "string" - } - ], "description": "string", "name": "string", "parent": { - "children": [{}], "description": "string", "name": "string", "parent": {}, diff --git a/docs/api/schemas.md b/docs/api/schemas.md index d5d48e1d3a21b..4eb5df728a50b 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -384,19 +384,9 @@ ```json { - "children": [ - { - "children": [], - "description": "string", - "name": "string", - "parent": {}, - "yaml_name": "string" - } - ], "description": "string", "name": "string", "parent": { - "children": [{}], "description": "string", "name": "string", "parent": {}, @@ -408,13 +398,12 @@ ### Properties -| Name | Type | Required | Restrictions | Description | -| ------------- | --------------------------------------- | -------- | ------------ | ----------- | -| `children` | array of [clibase.Group](#clibasegroup) | false | | | -| `description` | string | false | | | -| `name` | string | false | | | -| `parent` | [clibase.Group](#clibasegroup) | false | | | -| `yaml_name` | string | false | | | +| Name | Type | Required | Restrictions | Description | +| ------------- | ------------------------------ | -------- | ------------ | ----------- | +| `description` | string | false | | | +| `name` | string | false | | | +| `parent` | [clibase.Group](#clibasegroup) | false | | | +| `yaml_name` | string | false | | | ## clibase.HostPort @@ -446,19 +435,9 @@ "flag": "string", "flag_shorthand": "string", "group": { - "children": [ - { - "children": [], - "description": "string", - "name": "string", - "parent": {}, - "yaml_name": "string" - } - ], "description": "string", "name": "string", "parent": { - "children": [{}], "description": "string", "name": "string", "parent": {}, @@ -480,19 +459,9 @@ "flag": "string", "flag_shorthand": "string", "group": { - "children": [ - { - "children": [], - "description": "string", - "name": "string", - "parent": {}, - "yaml_name": "string" - } - ], "description": "string", "name": "string", "parent": { - "children": [{}], "description": "string", "name": "string", "parent": {}, @@ -2028,19 +1997,9 @@ CreateParameterRequest is a structure used to create a new parameter value for a "flag": "string", "flag_shorthand": "string", "group": { - "children": [ - { - "children": [], - "description": "string", - "name": "string", - "parent": {}, - "yaml_name": "string" - } - ], "description": "string", "name": "string", "parent": { - "children": [{}], "description": "string", "name": "string", "parent": {}, diff --git a/docs/cli/server.md b/docs/cli/server.md index 26b7e3a4e9d9d..4625a698ac2e4 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -48,6 +48,15 @@ Whether Coder only allows connections to workspaces via the browser. The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. +### -c, --config + +| | | +| ----------- | ------------------------------- | +| Type | yaml-config-path | +| Environment | $CODER_CONFIG_PATH | + +Specify a YAML file to load configuration from. + ### --dangerous-allow-path-app-sharing | | | @@ -790,3 +799,11 @@ Output debug-level logs. | Environment | $CODER_WILDCARD_ACCESS_URL | Specifies the wildcard hostname to use for workspace applications in the form "\*.example.com". + +### --write-config + +| | | +| ---- | ----------------- | +| Type | bool | + +
Write out the current server config as YAML to stdout. From 99d60680ead9c48d98c4050f606a8aba1df56165 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 21:31:50 +0000 Subject: [PATCH 22/35] Generate docs --- cli/clibase/clibase.go | 2 +- cli/clibase/option.go | 14 +- coderd/apidoc/docs.go | 4 +- coderd/apidoc/swagger.json | 4 +- docs/api/general.md | 6 +- docs/api/schemas.md | 26 +-- docs/cli/server.md | 367 ++++++++++++++++++++-------------- scripts/clidocgen/command.tpl | 3 + 8 files changed, 258 insertions(+), 168 deletions(-) diff --git a/cli/clibase/clibase.go b/cli/clibase/clibase.go index c12f41e81652e..c7c27c7c5d596 100644 --- a/cli/clibase/clibase.go +++ b/cli/clibase/clibase.go @@ -16,7 +16,7 @@ import ( type Group struct { Parent *Group `json:"parent,omitempty"` Name string `json:"name,omitempty"` - YAML string `json:"yaml_name,omitempty"` + YAML string `json:"yaml,omitempty"` Description string `json:"description,omitempty"` } diff --git a/cli/clibase/option.go b/cli/clibase/option.go index 0d2d1fc46528e..076346e004ce7 100644 --- a/cli/clibase/option.go +++ b/cli/clibase/option.go @@ -2,6 +2,7 @@ package clibase import ( "os" + "strings" "github.com/hashicorp/go-multierror" "github.com/spf13/pflag" @@ -57,7 +58,18 @@ type Option struct { Hidden bool `json:"hidden,omitempty"` - ValueSource ValueSource + ValueSource ValueSource `json:"value_source,omitempty"` +} + +func (o Option) YAMLPath() string { + if o.YAML == "" { + return "" + } + var gs []string + for _, g := range o.Group.Ancestry() { + gs = append(gs, g.YAML) + } + return strings.Join(append(gs, o.YAML), ".") } // OptionSet is a group of options that can be applied to a command. diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 756ff962e771b..d665c1bcee650 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -5730,7 +5730,7 @@ const docTemplate = `{ "parent": { "$ref": "#/definitions/clibase.Group" }, - "yaml_name": { + "yaml": { "type": "string" } } @@ -5800,7 +5800,7 @@ const docTemplate = `{ "value": { "description": "Value includes the types listed in values.go." }, - "valueSource": { + "value_source": { "$ref": "#/definitions/clibase.ValueSource" }, "yaml": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index e434373cd68b7..b4745e9895301 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -5075,7 +5075,7 @@ "parent": { "$ref": "#/definitions/clibase.Group" }, - "yaml_name": { + "yaml": { "type": "string" } } @@ -5145,7 +5145,7 @@ "value": { "description": "Value includes the types listed in values.go." }, - "valueSource": { + "value_source": { "$ref": "#/definitions/clibase.ValueSource" }, "yaml": { diff --git a/docs/api/general.md b/docs/api/general.md index 9d0c9a996b6cc..8f367ddc2e611 100644 --- a/docs/api/general.md +++ b/docs/api/general.md @@ -377,15 +377,15 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "description": "string", "name": "string", "parent": {}, - "yaml_name": "string" + "yaml": "string" }, - "yaml_name": "string" + "yaml": "string" }, "hidden": true, "name": "string", "use_instead": [{}], "value": null, - "valueSource": "", + "value_source": "", "yaml": "string" } ] diff --git a/docs/api/schemas.md b/docs/api/schemas.md index 4eb5df728a50b..3bf9df5dced14 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -390,9 +390,9 @@ "description": "string", "name": "string", "parent": {}, - "yaml_name": "string" + "yaml": "string" }, - "yaml_name": "string" + "yaml": "string" } ``` @@ -403,7 +403,7 @@ | `description` | string | false | | | | `name` | string | false | | | | `parent` | [clibase.Group](#clibasegroup) | false | | | -| `yaml_name` | string | false | | | +| `yaml` | string | false | | | ## clibase.HostPort @@ -441,9 +441,9 @@ "description": "string", "name": "string", "parent": {}, - "yaml_name": "string" + "yaml": "string" }, - "yaml_name": "string" + "yaml": "string" }, "hidden": true, "name": "string", @@ -465,20 +465,20 @@ "description": "string", "name": "string", "parent": {}, - "yaml_name": "string" + "yaml": "string" }, - "yaml_name": "string" + "yaml": "string" }, "hidden": true, "name": "string", "use_instead": [], "value": null, - "valueSource": "", + "value_source": "", "yaml": "string" } ], "value": null, - "valueSource": "", + "value_source": "", "yaml": "string" } ``` @@ -498,7 +498,7 @@ | `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. | -| `valueSource` | [clibase.ValueSource](#clibasevaluesource) | false | | | +| `value_source` | [clibase.ValueSource](#clibasevaluesource) | false | | | | `yaml` | string | false | | Yaml is the YAML key used to configure this option. If unset, YAML configuring is disabled. | ## clibase.Struct-array_codersdk_GitAuthConfig @@ -2003,15 +2003,15 @@ CreateParameterRequest is a structure used to create a new parameter value for a "description": "string", "name": "string", "parent": {}, - "yaml_name": "string" + "yaml": "string" }, - "yaml_name": "string" + "yaml": "string" }, "hidden": true, "name": "string", "use_instead": [{}], "value": null, - "valueSource": "", + "value_source": "", "yaml": "string" } ] diff --git a/docs/cli/server.md b/docs/cli/server.md index 4625a698ac2e4..3b25d0352373d 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -22,19 +22,21 @@ coder server [flags] ### --access-url -| | | -| ----------- | ------------------------------ | -| Type | url | -| Environment | $CODER_ACCESS_URL | +| | | +| ----------- | --------------------------------- | +| Type | url | +| Environment | $CODER_ACCESS_URL | +| YAML | networking.accessURL | The URL that users will use to access the Coder deployment. ### --browser-only -| | | -| ----------- | -------------------------------- | -| Type | bool | -| Environment | $CODER_BROWSER_ONLY | +| | | +| ----------- | ----------------------------------- | +| Type | bool | +| Environment | $CODER_BROWSER_ONLY | +| YAML | networking.browserOnly | Whether Coder only allows connections to workspaces via the browser. @@ -44,6 +46,7 @@ Whether Coder only allows connections to workspaces via the browser. | ----------- | ----------------------------------- | | Type | string | | Environment | $CODER_CACHE_DIRECTORY | +| YAML | cacheDir | | Default | ~/.cache/coder | The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. @@ -77,10 +80,11 @@ Allow site-owners to access workspace apps from workspaces they do not own. Owne ### --derp-config-path -| | | -| ----------- | ------------------------------------ | -| Type | string | -| Environment | $CODER_DERP_CONFIG_PATH | +| | | +| ----------- | --------------------------------------- | +| Type | string | +| Environment | $CODER_DERP_CONFIG_PATH | +| YAML | networking.derp.configPath | Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/. @@ -90,6 +94,7 @@ Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp | ----------- | ----------------------------------- | | Type | string | | Environment | $CODER_DERP_CONFIG_URL | +| YAML | networking.derp.url | URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/. @@ -99,6 +104,7 @@ URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custo | ----------- | -------------------------------------- | | Type | bool | | Environment | $CODER_DERP_SERVER_ENABLE | +| YAML | networking.derp.enable | | Default | true | Whether to enable or disable the embedded DERP relay server. @@ -109,6 +115,7 @@ Whether to enable or disable the embedded DERP relay server. | ----------- | ------------------------------------------- | | Type | string | | Environment | $CODER_DERP_SERVER_REGION_CODE | +| YAML | networking.derp.regionCode | | Default | coder | Region code to use for the embedded DERP server. @@ -119,6 +126,7 @@ Region code to use for the embedded DERP server. | ----------- | ----------------------------------------- | | Type | int | | Environment | $CODER_DERP_SERVER_REGION_ID | +| YAML | networking.derp.regionID | | Default | 999 | Region ID to use for the embedded DERP server. @@ -129,6 +137,7 @@ Region ID to use for the embedded DERP server. | ----------- | ------------------------------------------- | | Type | string | | Environment | $CODER_DERP_SERVER_REGION_NAME | +| YAML | networking.derp.regionName | | Default | Coder Embedded Relay | Region name that for the embedded DERP server. @@ -139,6 +148,7 @@ Region name that for the embedded DERP server. | ----------- | ----------------------------------------- | | Type | url | | Environment | $CODER_DERP_SERVER_RELAY_URL | +| YAML | networking.derp.relayURL | An HTTP URL that is accessible by other replicas to relay DERP traffic. Required for high availability. @@ -148,16 +158,18 @@ An HTTP URL that is accessible by other replicas to relay DERP traffic. Required | ----------- | ---------------------------------------------- | | Type | string-array | | Environment | $CODER_DERP_SERVER_STUN_ADDRESSES | +| YAML | networking.derp.stunAddresses | | Default | stun.l.google.com:19302 | Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections. ### --disable-password-auth -| | | -| ----------- | ----------------------------------------- | -| Type | bool | -| Environment | $CODER_DISABLE_PASSWORD_AUTH | +| | | +| ----------- | ------------------------------------------------ | +| Type | bool | +| Environment | $CODER_DISABLE_PASSWORD_AUTH | +| YAML | networking.http.disablePasswordAuth | 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. @@ -167,15 +179,17 @@ Disable password authentication. This is recommended for security purposes in pr | ----------- | ------------------------------------- | | Type | bool | | Environment | $CODER_DISABLE_PATH_APPS | +| YAML | disablePathApps | 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. ### --disable-session-expiry-refresh -| | | -| ----------- | -------------------------------------------------- | -| Type | bool | -| Environment | $CODER_DISABLE_SESSION_EXPIRY_REFRESH | +| | | +| ----------- | -------------------------------------------------------- | +| Type | bool | +| Environment | $CODER_DISABLE_SESSION_EXPIRY_REFRESH | +| YAML | networking.http.disableSessionExpiryRefresh | Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached. @@ -185,54 +199,60 @@ Disable automatic session expiry bumping due to activity. This forces all sessio | ----------- | ------------------------------- | | Type | string-array | | Environment | $CODER_EXPERIMENTS | +| YAML | experiments | 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. ### --http-address -| | | -| ----------- | -------------------------------- | -| Type | string | -| Environment | $CODER_HTTP_ADDRESS | -| Default | 127.0.0.1:3000 | +| | | +| ----------- | ---------------------------------------- | +| Type | string | +| Environment | $CODER_HTTP_ADDRESS | +| YAML | networking.http.httpAddress | +| Default | 127.0.0.1:3000 | HTTP bind address of the server. Unset to disable the HTTP endpoint. ### --log-human -| | | -| ----------- | --------------------------------- | -| Type | string | -| Environment | $CODER_LOGGING_HUMAN | -| Default | /dev/stderr | +| | | +| ----------- | -------------------------------------------- | +| Type | string | +| Environment | $CODER_LOGGING_HUMAN | +| YAML | introspection.logging.humanPath | +| Default | /dev/stderr | Output human-readable logs to a given file. ### --log-json -| | | -| ----------- | -------------------------------- | -| Type | string | -| Environment | $CODER_LOGGING_JSON | +| | | +| ----------- | ------------------------------------------- | +| Type | string | +| Environment | $CODER_LOGGING_JSON | +| YAML | introspection.logging.jsonPath | Output JSON logs to a given file. ### --log-stackdriver -| | | -| ----------- | --------------------------------------- | -| Type | string | -| Environment | $CODER_LOGGING_STACKDRIVER | +| | | +| ----------- | -------------------------------------------------- | +| Type | string | +| Environment | $CODER_LOGGING_STACKDRIVER | +| YAML | introspection.logging.stackdriverPath | Output Stackdriver compatible logs to a given file. ### --max-token-lifetime -| | | -| ----------- | -------------------------------------- | -| Type | duration | -| Environment | $CODER_MAX_TOKEN_LIFETIME | -| Default | 876600h0m0s | +| | | +| ----------- | --------------------------------------------- | +| Type | duration | +| Environment | $CODER_MAX_TOKEN_LIFETIME | +| YAML | networking.http.maxTokenLifetime | +| Default | 876600h0m0s | The maximum lifetime duration users can specify when creating an API token. @@ -242,6 +262,7 @@ The maximum lifetime duration users can specify when creating an API token. | ----------- | ------------------------------------------------ | | Type | bool | | Environment | $CODER_OAUTH2_GITHUB_ALLOW_EVERYONE | +| YAML | oauth2.github.allowEveryone | Allow all logins, setting this option means allowed orgs and teams must be empty. @@ -251,6 +272,7 @@ Allow all logins, setting this option means allowed orgs and teams must be empty | ----------- | ----------------------------------------------- | | Type | bool | | Environment | $CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS | +| YAML | oauth2.github.allowSignups | Whether new users can sign up with GitHub. @@ -260,6 +282,7 @@ Whether new users can sign up with GitHub. | ----------- | ---------------------------------------------- | | Type | string-array | | Environment | $CODER_OAUTH2_GITHUB_ALLOWED_ORGS | +| YAML | oauth2.github.allowedOrgs | Organizations the user must be a member of to Login with GitHub. @@ -269,6 +292,7 @@ Organizations the user must be a member of to Login with GitHub. | ----------- | ----------------------------------------------- | | Type | string-array | | Environment | $CODER_OAUTH2_GITHUB_ALLOWED_TEAMS | +| YAML | oauth2.github.allowedTeams | Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /. @@ -278,6 +302,7 @@ Teams inside organizations the user must be a member of to Login with GitHub. St | ----------- | ------------------------------------------- | | Type | string | | Environment | $CODER_OAUTH2_GITHUB_CLIENT_ID | +| YAML | oauth2.github.clientID | Client ID for Login with GitHub. @@ -296,6 +321,7 @@ Client secret for Login with GitHub. | ----------- | ----------------------------------------------------- | | Type | string | | Environment | $CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL | +| YAML | oauth2.github.enterpriseBaseURL | Base URL of a GitHub Enterprise deployment to use for Login with GitHub. @@ -305,6 +331,7 @@ Base URL of a GitHub Enterprise deployment to use for Login with GitHub. | ----------- | -------------------------------------- | | Type | bool | | Environment | $CODER_OIDC_ALLOW_SIGNUPS | +| YAML | oidc.allowSignups | | Default | true | Whether new users can sign up with OIDC. @@ -315,6 +342,7 @@ Whether new users can sign up with OIDC. | ----------- | ---------------------------------------- | | Type | struct[map[string]string] | | Environment | $CODER_OIDC_AUTH_URL_PARAMS | +| YAML | oidc.authURLParams | | Default | {"access_type": "offline"} | OIDC auth URL parameters to pass to the upstream provider. @@ -325,6 +353,7 @@ OIDC auth URL parameters to pass to the upstream provider. | ----------- | ---------------------------------- | | Type | string | | Environment | $CODER_OIDC_CLIENT_ID | +| YAML | oidc.clientID | Client ID to use for Login with OIDC. @@ -343,6 +372,7 @@ Client secret to use for Login with OIDC. | ----------- | ------------------------------------- | | Type | string-array | | Environment | $CODER_OIDC_EMAIL_DOMAIN | +| YAML | oidc.emailDomain | Email domains that clients logging in with OIDC must match. @@ -352,6 +382,7 @@ Email domains that clients logging in with OIDC must match. | ----------- | ------------------------------------ | | Type | string | | Environment | $CODER_OIDC_EMAIL_FIELD | +| YAML | oidc.emailField | | Default | email | OIDC claim field to use as the email. @@ -362,6 +393,7 @@ OIDC claim field to use as the email. | ----------- | ------------------------------------ | | Type | string | | Environment | $CODER_OIDC_GROUP_FIELD | +| YAML | oidc.groupField | Change the OIDC default 'groups' claim field. By default, will be 'groups' if present in the oidc scopes argument. @@ -371,6 +403,7 @@ Change the OIDC default 'groups' claim field. By default, will be 'groups' if pr | ----------- | -------------------------------------- | | Type | struct[map[string]string] | | Environment | $CODER_OIDC_GROUP_MAPPING | +| YAML | oidc.groupMapping | | Default | {} | A map of OIDC group IDs and the group in Coder it should map to. This is useful for when OIDC providers only return group IDs. @@ -381,6 +414,7 @@ A map of OIDC group IDs and the group in Coder it should map to. This is useful | ----------- | --------------------------------- | | Type | url | | Environment | $CODER_OIDC_ICON_URL | +| YAML | oidc.iconURL | URL pointing to the icon to use on the OepnID Connect login button. @@ -390,6 +424,7 @@ URL pointing to the icon to use on the OepnID Connect login button. | ----------- | ---------------------------------------------- | | Type | bool | | Environment | $CODER_OIDC_IGNORE_EMAIL_VERIFIED | +| YAML | oidc.ignoreEmailVerified | Ignore the email_verified claim from the upstream provider. @@ -399,6 +434,7 @@ Ignore the email_verified claim from the upstream provider. | ----------- | ---------------------------------------- | | Type | bool | | Environment | $CODER_OIDC_IGNORE_USERINFO | +| YAML | oidc.ignoreUserInfo | | Default | false | Ignore the userinfo endpoint and only use the ID token for user information. @@ -409,6 +445,7 @@ Ignore the userinfo endpoint and only use the ID token for user information. | ----------- | ----------------------------------- | | Type | string | | Environment | $CODER_OIDC_ISSUER_URL | +| YAML | oidc.issuerURL | Issuer URL to use for Login with OIDC. @@ -418,6 +455,7 @@ Issuer URL to use for Login with OIDC. | ----------- | --------------------------------- | | Type | string-array | | Environment | $CODER_OIDC_SCOPES | +| YAML | oidc.scopes | | Default | openid,profile,email | Scopes to grant when authenticating with OIDC. @@ -428,6 +466,7 @@ Scopes to grant when authenticating with OIDC. | ----------- | ------------------------------------- | | Type | string | | Environment | $CODER_OIDC_SIGN_IN_TEXT | +| YAML | oidc.signInText | | Default | OpenID Connect | The text to show on the OpenID Connect sign in button. @@ -438,6 +477,7 @@ The text to show on the OpenID Connect sign in button. | ----------- | --------------------------------------- | | Type | string | | Environment | $CODER_OIDC_USERNAME_FIELD | +| YAML | oidc.usernameField | | Default | preferred_username | OIDC claim field to use as the username. @@ -453,39 +493,43 @@ URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded f ### --pprof-address -| | | -| ----------- | --------------------------------- | -| Type | host:port | -| Environment | $CODER_PPROF_ADDRESS | -| Default | 127.0.0.1:6060 | +| | | +| ----------- | ---------------------------------------- | +| Type | host:port | +| Environment | $CODER_PPROF_ADDRESS | +| YAML | introspection.pprof.address | +| Default | 127.0.0.1:6060 | The bind address to serve pprof. ### --pprof-enable -| | | -| ----------- | -------------------------------- | -| Type | bool | -| Environment | $CODER_PPROF_ENABLE | +| | | +| ----------- | --------------------------------------- | +| Type | bool | +| Environment | $CODER_PPROF_ENABLE | +| YAML | introspection.pprof.enable | Serve pprof metrics on the address defined by pprof address. ### --prometheus-address -| | | -| ----------- | -------------------------------------- | -| Type | host:port | -| Environment | $CODER_PROMETHEUS_ADDRESS | -| Default | 127.0.0.1:2112 | +| | | +| ----------- | --------------------------------------------- | +| Type | host:port | +| Environment | $CODER_PROMETHEUS_ADDRESS | +| YAML | introspection.prometheus.address | +| Default | 127.0.0.1:2112 | The bind address to serve prometheus metrics. ### --prometheus-enable -| | | -| ----------- | ------------------------------------- | -| Type | bool | -| Environment | $CODER_PROMETHEUS_ENABLE | +| | | +| ----------- | -------------------------------------------- | +| Type | bool | +| Environment | $CODER_PROMETHEUS_ENABLE | +| YAML | introspection.prometheus.enable | Serve prometheus metrics on the address defined by prometheus address. @@ -495,6 +539,7 @@ Serve prometheus metrics on the address defined by prometheus address. | ----------- | ---------------------------------------------------- | | Type | duration | | Environment | $CODER_PROVISIONER_DAEMON_POLL_INTERVAL | +| YAML | provisioning.daemonPollInterval | | Default | 1s | Time to wait before polling for a new job. @@ -505,6 +550,7 @@ Time to wait before polling for a new job. | ----------- | -------------------------------------------------- | | Type | duration | | Environment | $CODER_PROVISIONER_DAEMON_POLL_JITTER | +| YAML | provisioning.daemonPollJitter | | Default | 100ms | Random jitter added to the poll interval. @@ -515,6 +561,7 @@ Random jitter added to the poll interval. | ----------- | --------------------------------------- | | Type | int | | Environment | $CODER_PROVISIONER_DAEMONS | +| YAML | provisioning.daemons | | Default | 3 | Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. @@ -525,34 +572,38 @@ Number of provisioner daemons to create on start. If builds are stuck in queued | ----------- | ----------------------------------------------------- | | Type | duration | | Environment | $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL | +| YAML | provisioning.forceCancelInterval | | Default | 10m0s | Time to force cancel provisioning tasks that are stuck. ### --proxy-trusted-headers -| | | -| ----------- | ----------------------------------------- | -| Type | string-array | -| Environment | $CODER_PROXY_TRUSTED_HEADERS | +| | | +| ----------- | ------------------------------------------- | +| Type | string-array | +| Environment | $CODER_PROXY_TRUSTED_HEADERS | +| YAML | networking.proxyTrustedHeaders | Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, True-Client-Ip, X-Forwarded-For. ### --proxy-trusted-origins -| | | -| ----------- | ----------------------------------------- | -| Type | string-array | -| Environment | $CODER_PROXY_TRUSTED_ORIGINS | +| | | +| ----------- | ------------------------------------------- | +| Type | string-array | +| Environment | $CODER_PROXY_TRUSTED_ORIGINS | +| YAML | networking.proxyTrustedOrigins | Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. ### --redirect-to-access-url -| | | -| ----------- | ------------------------------------------ | -| Type | bool | -| Environment | $CODER_REDIRECT_TO_ACCESS_URL | +| | | +| ----------- | ------------------------------------------- | +| Type | bool | +| Environment | $CODER_REDIRECT_TO_ACCESS_URL | +| YAML | networking.redirectToAccessURL | Specifies whether to redirect requests that do not match the access URL host. @@ -567,20 +618,22 @@ Enables SCIM and sets the authentication header for the built-in SCIM server. Ne ### --secure-auth-cookie -| | | -| ----------- | -------------------------------------- | -| Type | bool | -| Environment | $CODER_SECURE_AUTH_COOKIE | +| | | +| ----------- | ---------------------------------------- | +| Type | bool | +| Environment | $CODER_SECURE_AUTH_COOKIE | +| YAML | networking.secureAuthCookie | Controls if the 'Secure' property is set on browser session cookies. ### --session-duration -| | | -| ----------- | ------------------------------------ | -| Type | duration | -| Environment | $CODER_SESSION_DURATION | -| Default | 24h0m0s | +| | | +| ----------- | -------------------------------------------- | +| Type | duration | +| Environment | $CODER_SESSION_DURATION | +| YAML | networking.http.sessionDuration | +| Default | 24h0m0s | 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. @@ -590,6 +643,7 @@ The token expiry duration for browser sessions. Sessions may last longer if they | ----------- | -------------------------------------- | | Type | string-array | | Environment | $CODER_SSH_CONFIG_OPTIONS | +| YAML | client.sshConfigOptions | These SSH config options will override the default SSH config options. Provide options in "key=value" or "key value" format separated by commas.Using this incorrectly can break SSH to your deployment, use cautiously. @@ -599,6 +653,7 @@ These SSH config options will override the default SSH config options. Provide o | ----------- | --------------------------------------- | | Type | string | | Environment | $CODER_SSH_HOSTNAME_PREFIX | +| YAML | client.sshHostnamePrefix | | Default | coder. | The SSH deployment prefix is used in the Host of the ssh config. @@ -609,26 +664,29 @@ The SSH deployment prefix is used in the Host of the ssh config. | ----------- | ---------------------------------------- | | Type | string | | Environment | $CODER_SSH_KEYGEN_ALGORITHM | +| YAML | sshKeygenAlgorithm | | Default | ed25519 | The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". ### --strict-transport-security -| | | -| ----------- | --------------------------------------------- | -| Type | int | -| Environment | $CODER_STRICT_TRANSPORT_SECURITY | -| Default | 0 | +| | | +| ----------- | --------------------------------------------------- | +| Type | int | +| Environment | $CODER_STRICT_TRANSPORT_SECURITY | +| YAML | networking.tls.strictTransportSecurity | +| Default | 0 | 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. ### --strict-transport-security-options -| | | -| ----------- | ----------------------------------------------------- | -| Type | string-array | -| Environment | $CODER_STRICT_TRANSPORT_SECURITY_OPTIONS | +| | | +| ----------- | ---------------------------------------------------------- | +| Type | string-array | +| Environment | $CODER_STRICT_TRANSPORT_SECURITY_OPTIONS | +| YAML | networking.tls.strictTransportSecurityOptions | 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. @@ -638,6 +696,7 @@ Two optional fields can be set in the Strict-Transport-Security header; 'include | ----------- | ---------------------------------- | | Type | bool | | Environment | $CODER_SWAGGER_ENABLE | +| YAML | enableSwagger | Expose the swagger endpoint via /swagger. @@ -647,6 +706,7 @@ Expose the swagger endpoint via /swagger. | ----------- | ------------------------------------ | | Type | bool | | Environment | $CODER_TELEMETRY_ENABLE | +| YAML | telemetry.enable | | Default | true | Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product. @@ -657,100 +717,111 @@ Whether telemetry is enabled or not. Coder collects anonymized usage data to hel | ----------- | ----------------------------------- | | Type | bool | | Environment | $CODER_TELEMETRY_TRACE | +| YAML | telemetry.trace | | Default | true | Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option. ### --tls-address -| | | -| ----------- | ------------------------------- | -| Type | host:port | -| Environment | $CODER_TLS_ADDRESS | -| Default | 127.0.0.1:3443 | +| | | +| ----------- | ----------------------------------- | +| Type | host:port | +| Environment | $CODER_TLS_ADDRESS | +| YAML | networking.tls.address | +| Default | 127.0.0.1:3443 | HTTPS bind address of the server. ### --tls-cert-file -| | | -| ----------- | --------------------------------- | -| Type | string-array | -| Environment | $CODER_TLS_CERT_FILE | +| | | +| ----------- | ------------------------------------- | +| Type | string-array | +| Environment | $CODER_TLS_CERT_FILE | +| YAML | networking.tls.certFiles | 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. ### --tls-client-auth -| | | -| ----------- | ----------------------------------- | -| Type | string | -| Environment | $CODER_TLS_CLIENT_AUTH | -| Default | none | +| | | +| ----------- | -------------------------------------- | +| Type | string | +| Environment | $CODER_TLS_CLIENT_AUTH | +| YAML | networking.tls.clientAuth | +| Default | none | Policy the server will follow for TLS Client Authentication. Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify". ### --tls-client-ca-file -| | | -| ----------- | -------------------------------------- | -| Type | string | -| Environment | $CODER_TLS_CLIENT_CA_FILE | +| | | +| ----------- | ---------------------------------------- | +| Type | string | +| Environment | $CODER_TLS_CLIENT_CA_FILE | +| YAML | networking.tls.clientCAFile | PEM-encoded Certificate Authority file used for checking the authenticity of client. ### --tls-client-cert-file -| | | -| ----------- | ---------------------------------------- | -| Type | string | -| Environment | $CODER_TLS_CLIENT_CERT_FILE | +| | | +| ----------- | ------------------------------------------ | +| Type | string | +| Environment | $CODER_TLS_CLIENT_CERT_FILE | +| YAML | networking.tls.clientCertFile | Path to certificate for client TLS authentication. It requires a PEM-encoded file. ### --tls-client-key-file -| | | -| ----------- | --------------------------------------- | -| Type | string | -| Environment | $CODER_TLS_CLIENT_KEY_FILE | +| | | +| ----------- | ----------------------------------------- | +| Type | string | +| Environment | $CODER_TLS_CLIENT_KEY_FILE | +| YAML | networking.tls.clientKeyFile | Path to key for client TLS authentication. It requires a PEM-encoded file. ### --tls-enable -| | | -| ----------- | ------------------------------ | -| Type | bool | -| Environment | $CODER_TLS_ENABLE | +| | | +| ----------- | ---------------------------------- | +| Type | bool | +| Environment | $CODER_TLS_ENABLE | +| YAML | networking.tls.enable | Whether TLS will be enabled. ### --tls-key-file -| | | -| ----------- | -------------------------------- | -| Type | string-array | -| Environment | $CODER_TLS_KEY_FILE | +| | | +| ----------- | ------------------------------------ | +| Type | string-array | +| Environment | $CODER_TLS_KEY_FILE | +| YAML | networking.tls.keyFiles | Paths to the private keys for each of the certificates. It requires a PEM-encoded file. ### --tls-min-version -| | | -| ----------- | ----------------------------------- | -| Type | string | -| Environment | $CODER_TLS_MIN_VERSION | -| Default | tls12 | +| | | +| ----------- | -------------------------------------- | +| Type | string | +| Environment | $CODER_TLS_MIN_VERSION | +| YAML | networking.tls.minVersion | +| Default | tls12 | Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13". ### --trace -| | | -| ----------- | -------------------------------- | -| Type | bool | -| Environment | $CODER_TRACE_ENABLE | +| | | +| ----------- | ----------------------------------------- | +| Type | bool | +| Environment | $CODER_TRACE_ENABLE | +| YAML | introspection.tracing.enable | 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. @@ -765,10 +836,11 @@ Enables trace exporting to Honeycomb.io using the provided API Key. ### --trace-logs -| | | -| ----------- | ------------------------------ | -| Type | bool | -| Environment | $CODER_TRACE_LOGS | +| | | +| ----------- | ---------------------------------------------- | +| Type | bool | +| Environment | $CODER_TRACE_LOGS | +| YAML | introspection.tracing.captureLogs | 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. @@ -778,25 +850,28 @@ Enables capturing of logs as events in traces. This is useful for debugging, but | ----------- | -------------------------------- | | Type | bool | | Environment | $CODER_UPDATE_CHECK | +| YAML | updateCheck | | Default | false | Periodically check for new releases of Coder and inform the owner. The check is performed once per day. ### -v, --verbose -| | | -| ----------- | --------------------------- | -| Type | bool | -| Environment | $CODER_VERBOSE | +| | | +| ----------- | ------------------------------------------ | +| Type | bool | +| Environment | $CODER_VERBOSE | +| YAML | introspection.logging.verbose | Output debug-level logs. ### --wildcard-access-url -| | | -| ----------- | --------------------------------------- | -| Type | url | -| Environment | $CODER_WILDCARD_ACCESS_URL | +| | | +| ----------- | ----------------------------------------- | +| Type | url | +| Environment | $CODER_WILDCARD_ACCESS_URL | +| YAML | networking.wildcardAccessURL | Specifies the wildcard hostname to use for workspace applications in the form "\*.example.com". diff --git a/scripts/clidocgen/command.tpl b/scripts/clidocgen/command.tpl index 2feaf344aad2e..09ca1b1f94324 100644 --- a/scripts/clidocgen/command.tpl +++ b/scripts/clidocgen/command.tpl @@ -47,6 +47,9 @@ Aliases: {{- with $opt.Env }} | Environment | {{ (print "$" .) | wrapCode }} | {{- end }} +{{- with $opt.YAMLPath }} +| YAML | {{ . | wrapCode }} | +{{- end }} {{- with $opt.Default }} | Default | {{- . | wrapCode }} | {{ "" }} From 300484b4008b4ad19c5c8a88e04f1f31e6d38d12 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 21:35:57 +0000 Subject: [PATCH 23/35] make gen --- coderd/database/models.go | 40 +++++++++++++++++----------------- coderd/database/querier.go | 2 +- coderd/database/queries.sql.go | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/coderd/database/models.go b/coderd/database/models.go index 8df3db1e955ed..a0b11b2d3ba26 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.16.0 +// sqlc v1.17.2 package database @@ -56,7 +56,7 @@ func (ns NullAPIKeyScope) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.APIKeyScope, nil + return string(ns.APIKeyScope), nil } func (e APIKeyScope) Valid() bool { @@ -115,7 +115,7 @@ func (ns NullAppSharingLevel) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.AppSharingLevel, nil + return string(ns.AppSharingLevel), nil } func (e AppSharingLevel) Valid() bool { @@ -180,7 +180,7 @@ func (ns NullAuditAction) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.AuditAction, nil + return string(ns.AuditAction), nil } func (e AuditAction) Valid() bool { @@ -249,7 +249,7 @@ func (ns NullBuildReason) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.BuildReason, nil + return string(ns.BuildReason), nil } func (e BuildReason) Valid() bool { @@ -312,7 +312,7 @@ func (ns NullLogLevel) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.LogLevel, nil + return string(ns.LogLevel), nil } func (e LogLevel) Valid() bool { @@ -376,7 +376,7 @@ func (ns NullLogSource) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.LogSource, nil + return string(ns.LogSource), nil } func (e LogSource) Valid() bool { @@ -436,7 +436,7 @@ func (ns NullLoginType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.LoginType, nil + return string(ns.LoginType), nil } func (e LoginType) Valid() bool { @@ -499,7 +499,7 @@ func (ns NullParameterDestinationScheme) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ParameterDestinationScheme, nil + return string(ns.ParameterDestinationScheme), nil } func (e ParameterDestinationScheme) Valid() bool { @@ -560,7 +560,7 @@ func (ns NullParameterScope) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ParameterScope, nil + return string(ns.ParameterScope), nil } func (e ParameterScope) Valid() bool { @@ -620,7 +620,7 @@ func (ns NullParameterSourceScheme) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ParameterSourceScheme, nil + return string(ns.ParameterSourceScheme), nil } func (e ParameterSourceScheme) Valid() bool { @@ -678,7 +678,7 @@ func (ns NullParameterTypeSystem) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ParameterTypeSystem, nil + return string(ns.ParameterTypeSystem), nil } func (e ParameterTypeSystem) Valid() bool { @@ -737,7 +737,7 @@ func (ns NullProvisionerJobType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ProvisionerJobType, nil + return string(ns.ProvisionerJobType), nil } func (e ProvisionerJobType) Valid() bool { @@ -796,7 +796,7 @@ func (ns NullProvisionerStorageMethod) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ProvisionerStorageMethod, nil + return string(ns.ProvisionerStorageMethod), nil } func (e ProvisionerStorageMethod) Valid() bool { @@ -852,7 +852,7 @@ func (ns NullProvisionerType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ProvisionerType, nil + return string(ns.ProvisionerType), nil } func (e ProvisionerType) Valid() bool { @@ -918,7 +918,7 @@ func (ns NullResourceType) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.ResourceType, nil + return string(ns.ResourceType), nil } func (e ResourceType) Valid() bool { @@ -992,7 +992,7 @@ func (ns NullUserStatus) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.UserStatus, nil + return string(ns.UserStatus), nil } func (e UserStatus) Valid() bool { @@ -1057,7 +1057,7 @@ func (ns NullWorkspaceAgentLifecycleState) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.WorkspaceAgentLifecycleState, nil + return string(ns.WorkspaceAgentLifecycleState), nil } func (e WorkspaceAgentLifecycleState) Valid() bool { @@ -1131,7 +1131,7 @@ func (ns NullWorkspaceAppHealth) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.WorkspaceAppHealth, nil + return string(ns.WorkspaceAppHealth), nil } func (e WorkspaceAppHealth) Valid() bool { @@ -1194,7 +1194,7 @@ func (ns NullWorkspaceTransition) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return ns.WorkspaceTransition, nil + return string(ns.WorkspaceTransition), nil } func (e WorkspaceTransition) Valid() bool { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index fd8e2372640cf..5151aead8064c 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.16.0 +// sqlc v1.17.2 package database diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 8edd99fd23580..3a8dda0fc4e39 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.16.0 +// sqlc v1.17.2 package database From 4cf5a21c257ac0ec0d2a2e1bb0163a661e7ede63 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 23:17:18 +0000 Subject: [PATCH 24/35] Add --debug-options --- cli/clibase/yaml.go | 6 +++++- cli/root.go | 27 +++++++++++++++++++++++++++ docs/cli.md | 8 ++++++++ 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 5b7d117b9023f..19eb9590ba2a7 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -257,16 +257,20 @@ func (s *OptionSet) UnmarshalYAML(rootNode *yaml.Node) error { group = append(group, g.YAML) delete(yamlNodes, strings.Join(group, ".")) } + key := strings.Join(append(group, opt.YAML), ".") node, ok := yamlNodes[key] if !ok { continue } + matchedNodes[key] = node + if opt.ValueSource != ValueSourceNone { + continue + } if err := opt.setFromYAMLNode(node); err != nil { merr = errors.Join(merr, xerrors.Errorf("setting %q: %w", opt.YAML, err)) } - matchedNodes[key] = node } // Remove all matched nodes and their descendants from yamlNodes so we diff --git a/cli/root.go b/cli/root.go index 070b8fa0db441..368520ac08f70 100644 --- a/cli/root.go +++ b/cli/root.go @@ -16,6 +16,7 @@ import ( "runtime" "strings" "syscall" + "text/tabwriter" "time" "golang.org/x/exp/slices" @@ -250,6 +251,26 @@ func (r *RootCmd) Command(subcommands []*clibase.Cmd) (*clibase.Cmd, error) { return nil, merr } + var debugOptions bool + + // Add a wrapper to every command to enable debugging options. + cmd.Walk(func(cmd *clibase.Cmd) { + h := cmd.Handler + cmd.Handler = func(i *clibase.Invocation) error { + if !debugOptions { + return h(i) + } + + tw := tabwriter.NewWriter(i.Stdout, 0, 0, 4, ' ', 0) + _, _ = fmt.Fprintf(tw, "Option\tValue Source\n") + for _, opt := range cmd.Options { + _, _ = fmt.Fprintf(tw, "%q\t%v\n", opt.Name, opt.ValueSource) + } + tw.Flush() + return nil + } + }) + if r.agentURL == nil { r.agentURL = new(url.URL) } @@ -269,6 +290,12 @@ func (r *RootCmd) Command(subcommands []*clibase.Cmd) (*clibase.Cmd, error) { Value: clibase.URLOf(r.clientURL), Group: globalGroup, }, + { + Flag: "debug-options", + Description: "Print all options, how they're set, then exit.", + Value: clibase.BoolOf(&debugOptions), + Group: globalGroup, + }, { Flag: varToken, Env: envSessionToken, diff --git a/docs/cli.md b/docs/cli.md index be16ae30cc53d..92e1cece5a086 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -59,6 +59,14 @@ Coder — A tool for provisioning self-hosted development environments with Terr ## Options +### --debug-options + +| | | +| ---- | ----------------- | +| Type | bool | + +Print all options, how they're set, then exit. + ### --global-config | | | From abc92d9f30732c49ca02539caf4ec0fa1cb9c924 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Thu, 6 Apr 2023 23:26:30 +0000 Subject: [PATCH 25/35] Normalize golden files --- cli/root_test.go | 60 ++++++++++++++------------ cli/server_test.go | 1 + cli/testdata/coder_--help.golden | 3 ++ cli/testdata/server-config.yaml.golden | 4 +- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/cli/root_test.go b/cli/root_test.go index 22c1c3e36ae85..5689691226aa2 100644 --- a/cli/root_test.go +++ b/cli/root_test.go @@ -107,20 +107,7 @@ ExtractCommandPathsLoop: actual = bytes.ReplaceAll(actual, []byte(k), []byte(v)) } - // Replace any timestamps with a placeholder. - actual = timestampRegex.ReplaceAll(actual, []byte("[timestamp]")) - - homeDir, err := os.UserHomeDir() - require.NoError(t, err) - - configDir := config.DefaultDir() - actual = bytes.ReplaceAll(actual, []byte(configDir), []byte("~/.config/coderv2")) - - actual = bytes.ReplaceAll(actual, []byte(codersdk.DefaultCacheDir()), []byte("[cache dir]")) - - // The home directory changes depending on the test environment. - actual = bytes.ReplaceAll(actual, []byte(homeDir), []byte("~")) - + actual = normalizeGoldenFile(t, actual) goldenPath := filepath.Join("testdata", strings.Replace(tt.name, " ", "_", -1)+".golden") if *updateGoldenFiles { t.Logf("update golden file for: %q: %s", tt.name, goldenPath) @@ -131,19 +118,7 @@ ExtractCommandPathsLoop: expected, err := os.ReadFile(goldenPath) require.NoError(t, err, "read golden file, run \"make update-golden-files\" and commit the changes") - // Normalize files to tolerate different operating systems. - for _, r := range []struct { - old string - new string - }{ - {"\r\n", "\n"}, - {`~\.cache\coder`, "~/.cache/coder"}, - {`C:\Users\RUNNER~1\AppData\Local\Temp`, "/tmp"}, - {os.TempDir(), "/tmp"}, - } { - expected = bytes.ReplaceAll(expected, []byte(r.old), []byte(r.new)) - actual = bytes.ReplaceAll(actual, []byte(r.old), []byte(r.new)) - } + expected = normalizeGoldenFile(t, expected) require.Equal( t, string(expected), string(actual), "golden file mismatch: %s, run \"make update-golden-files\", verify and commit the changes", @@ -153,6 +128,37 @@ ExtractCommandPathsLoop: } } +// normalizeGoldenFiles replaces any strings that are system or timing dependent +// with a placeholder so that the golden files can be compared with a simple +// equality check. +func normalizeGoldenFile(t *testing.T, byt []byte) []byte { + // Replace any timestamps with a placeholder. + byt = timestampRegex.ReplaceAll(byt, []byte("[timestamp]")) + + homeDir, err := os.UserHomeDir() + require.NoError(t, err) + + configDir := config.DefaultDir() + byt = bytes.ReplaceAll(byt, []byte(configDir), []byte("~/.config/coderv2")) + + byt = bytes.ReplaceAll(byt, []byte(codersdk.DefaultCacheDir()), []byte("[cache dir]")) + + // The home directory changes depending on the test environment. + byt = bytes.ReplaceAll(byt, []byte(homeDir), []byte("~")) + for _, r := range []struct { + old string + new string + }{ + {"\r\n", "\n"}, + {`~\.cache\coder`, "~/.cache/coder"}, + {`C:\Users\RUNNER~1\AppData\Local\Temp`, "/tmp"}, + {os.TempDir(), "/tmp"}, + } { + byt = bytes.ReplaceAll(byt, []byte(r.old), []byte(r.new)) + } + return byt +} + func extractVisibleCommandPaths(cmdPath []string, cmds []*clibase.Cmd) [][]string { var cmdPaths [][]string for _, c := range cmds { diff --git a/cli/server_test.go b/cli/server_test.go index 0a49b50af1321..029a66e07c471 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1569,6 +1569,7 @@ func TestServerYAMLConfig(t *testing.T) { goldenPath := filepath.Join("testdata", "server-config.yaml.golden") + wantByt = normalizeGoldenFile(t, wantByt) if *updateGoldenFiles { require.NoError(t, os.WriteFile(goldenPath, wantByt, 0o600)) return diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index db7208fe19fae..cca6cf17b352b 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -47,6 +47,9 @@ Coder v0.0.0-devel — A tool for provisioning self-hosted development environme Global options are applied to all commands. They can be set using environment variables or flags. + --debug-options bool + Print all options, how they're set, then exit. + --global-config string, $CODER_CONFIG_DIR (default: ~/.config/coderv2) Path to the global `coder` config directory. diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 6ded301c2cfdf..2855b82ef2ea9 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -255,8 +255,8 @@ updateCheck: false enableSwagger: false # The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is # set, it will be used for compatibility with systemd. (default: -# /home/coder/.cache/coder, type: string) -cacheDir: /home/coder/.cache/coder +# [cache dir], type: string) +cacheDir: [cache dir] # Controls whether data will be stored in an in-memory database. (default: # , type: bool) inMemoryDatabase: false From a958324fea6b66311564d9af3fd28886ef35afcc Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 00:55:51 +0000 Subject: [PATCH 26/35] fix log path --- cli/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/server_test.go b/cli/server_test.go index 029a66e07c471..0bbd378678191 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1430,7 +1430,7 @@ func TestServer(t *testing.T) { "--in-memory", "--http-address", ":0", "--access-url", "http://example.com", - "--log-human", filepath.Join(t.TempDir(), "coder-logging-test-*"), + "--log-human", filepath.Join(t.TempDir(), "coder-logging-test-human"), "--ssh-keygen-algorithm", "rsa4096", "--cache-dir", t.TempDir(), } From 8ead9fcb72317f1811224179fa312ef88ea1331f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 01:05:55 +0000 Subject: [PATCH 27/35] minor fix --- codersdk/deployment_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/codersdk/deployment_test.go b/codersdk/deployment_test.go index ea48c1fbddd22..454995dd22418 100644 --- a/codersdk/deployment_test.go +++ b/codersdk/deployment_test.go @@ -30,6 +30,7 @@ func TestDeploymentValues_HighlyConfigurable(t *testing.T) { }, "Write Config": { yaml: true, + env: true, }, // Dangerous values? Not sure we should help users // persistent their configuration. From 6eb19f636d3dfc8acadc37d0da06a72f22466b31 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 01:11:18 +0000 Subject: [PATCH 28/35] Fix mutability bug in PrepareAll --- cli/clibase/cmd.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cli/clibase/cmd.go b/cli/clibase/cmd.go index 1c9a848ce0ce9..7d3547dd8839d 100644 --- a/cli/clibase/cmd.go +++ b/cli/clibase/cmd.go @@ -77,10 +77,8 @@ func (c *Cmd) PrepareAll() error { } var merr error - slices.SortFunc(c.Options, func(a, b Option) bool { - return a.Flag < b.Flag - }) - for _, opt := range c.Options { + for i := range c.Options { + opt := &c.Options[i] if opt.Name == "" { switch { case opt.Flag != "": @@ -103,6 +101,10 @@ func (c *Cmd) PrepareAll() error { } } } + + slices.SortFunc(c.Options, func(a, b Option) bool { + return a.Name < b.Name + }) slices.SortFunc(c.Children, func(a, b *Cmd) bool { return a.Name() < b.Name() }) From f77460f6bbca3abc40fa7f65b8f3669231c0725e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 18:43:09 +0000 Subject: [PATCH 29/35] Address review comments --- cli/clibase/yaml.go | 4 +- cli/clibase/yaml_test.go | 1 + cli/testdata/server-config.yaml.golden | 248 ++++++++++++--------- docs/cli/server.md | 290 ++++++++++++------------- 4 files changed, 294 insertions(+), 249 deletions(-) diff --git a/cli/clibase/yaml.go b/cli/clibase/yaml.go index 19eb9590ba2a7..fbb1529b3bf4d 100644 --- a/cli/clibase/yaml.go +++ b/cli/clibase/yaml.go @@ -66,13 +66,13 @@ func (s *OptionSet) MarshalYAML() (any, error) { defValue = "" } comment := wordwrap.WrapString( - fmt.Sprintf("%s (default: %s, type: %s)", opt.Description, defValue, opt.Value.Type()), + fmt.Sprintf("%s\n(default: %s, type: %s)", opt.Description, defValue, opt.Value.Type()), 80, ) nameNode := yaml.Node{ Kind: yaml.ScalarNode, Value: opt.YAML, - HeadComment: wordwrap.WrapString(comment, 80), + HeadComment: comment, } var valueNode yaml.Node if opt.Value == nil { diff --git a/cli/clibase/yaml_test.go b/cli/clibase/yaml_test.go index 9fc0d721a99da..d14bfc7c75ea6 100644 --- a/cli/clibase/yaml_test.go +++ b/cli/clibase/yaml_test.go @@ -87,6 +87,7 @@ func TestOptionSet_YAMLUnknownOptions(t *testing.T) { // OptionSet converts to the same OptionSet when read back in. func TestOptionSet_YAMLIsomorphism(t *testing.T) { t.Parallel() + // This is used to form a generic. //nolint:unused type kid struct { Name string `yaml:"name"` diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 2855b82ef2ea9..c1bfffc686a0f 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -1,23 +1,25 @@ networking: - # The URL that users will use to access the Coder deployment. (default: , - # type: url) + # The URL that users will use to access the Coder deployment. + # (default: , type: url) accessURL: # Specifies the wildcard hostname to use for workspace applications in the form - # "*.example.com". (default: , type: url) + # "*.example.com". + # (default: , type: url) wildcardAccessURL: # Specifies whether to redirect requests that do not match the access URL host. # (default: , type: bool) redirectToAccessURL: false http: - # HTTP bind address of the server. Unset to disable the HTTP endpoint. (default: - # 127.0.0.1:3000, type: string) + # HTTP bind address of the server. Unset to disable the HTTP endpoint. + # (default: 127.0.0.1:3000, type: string) httpAddress: 127.0.0.1:3000 # The maximum lifetime duration users can specify when creating an API token. # (default: 876600h0m0s, type: duration) maxTokenLifetime: 876600h0m0s # 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. (default: 24h0m0s, type: duration) + # --disable-session-expiry-refresh. + # (default: 24h0m0s, type: duration) sessionDuration: 24h0m0s # Disable automatic session expiry bumping due to activity. This forces all # sessions to become invalid after the session expiry duration has been reached. @@ -28,52 +30,62 @@ networking: # 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. (default: , type: bool) + # directly in the database. + # (default: , type: bool) disablePasswordAuth: false # 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. tls: - # HTTPS bind address of the server. (default: 127.0.0.1:3443, type: host:port) + # HTTPS bind address of the server. + # (default: 127.0.0.1:3443, type: host:port) address: 127.0.0.1:3443 - # Whether TLS will be enabled. (default: , type: bool) + # Whether TLS will be enabled. + # (default: , type: bool) enable: false # 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. (default: true, type: bool) + # regardless of this setting. + # (default: true, type: bool) redirectHTTP: true # 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. (default: , type: string-array) + # combined file. + # (default: , type: string-array) certFiles: [] # PEM-encoded Certificate Authority file used for checking the authenticity of - # client. (default: , type: string) + # client. + # (default: , type: string) clientCAFile: "" # Policy the server will follow for TLS Client Authentication. Accepted values are # "none", "request", "require-any", "verify-if-given", or "require-and-verify". # (default: none, type: string) clientAuth: none # Paths to the private keys for each of the certificates. It requires a - # PEM-encoded file. (default: , type: string-array) + # PEM-encoded file. + # (default: , type: string-array) keyFiles: [] # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" - # or "tls13". (default: tls12, type: string) + # or "tls13". + # (default: tls12, type: string) minVersion: tls12 # Path to certificate for client TLS authentication. It requires a PEM-encoded - # file. (default: , type: string) + # file. + # (default: , type: string) clientCertFile: "" # Path to key for client TLS authentication. It requires a PEM-encoded file. # (default: , type: string) clientKeyFile: "" # 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, type: int) + # This value is the MaxAge in seconds of the header. + # (default: 0, type: int) strictTransportSecurity: 0 # 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. (default: , type: - # string-array) + # set to a non-zero value for these options to be used. + # (default: , type: string-array) strictTransportSecurityOptions: [] # Most Coder deployments never have to think about DERP because all connections # between workspaces and users are peer-to-peer. However, when Coder cannot @@ -81,58 +93,65 @@ networking: # a peer to peer connection, Coder uses a distributed relay network backed by # Tailscale and WireGuard. derp: - # Whether to enable or disable the embedded DERP relay server. (default: true, - # type: bool) + # Whether to enable or disable the embedded DERP relay server. + # (default: true, type: bool) enable: true - # Region ID to use for the embedded DERP server. (default: 999, type: int) + # Region ID to use for the embedded DERP server. + # (default: 999, type: int) regionID: 999 - # Region code to use for the embedded DERP server. (default: coder, type: string) + # Region code to use for the embedded DERP server. + # (default: coder, type: string) regionCode: coder - # Region name that for the embedded DERP server. (default: Coder Embedded Relay, - # type: string) + # Region name that for the embedded DERP server. + # (default: Coder Embedded Relay, type: string) regionName: Coder Embedded Relay # Addresses for STUN servers to establish P2P connections. Set empty to disable - # P2P connections. (default: stun.l.google.com:19302, type: string-array) + # P2P connections. + # (default: stun.l.google.com:19302, type: string-array) stunAddresses: - stun.l.google.com:19302 # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required - # for high availability. (default: , type: url) + # for high availability. + # (default: , type: url) relayURL: # URL to fetch a DERP mapping on startup. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. (default: , type: - # string) + # https://tailscale.com/kb/1118/custom-derp-servers/. + # (default: , type: string) url: "" # Path to read a DERP mapping from. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. (default: , type: - # string) + # https://tailscale.com/kb/1118/custom-derp-servers/. + # (default: , type: string) configPath: "" # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, - # True-Client-Ip, X-Forwarded-For. (default: , type: string-array) + # True-Client-Ip, X-Forwarded-For. + # (default: , type: string-array) proxyTrustedHeaders: [] # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. # (default: , type: string-array) proxyTrustedOrigins: [] - # Controls if the 'Secure' property is set on browser session cookies. (default: - # , type: bool) + # Controls if the 'Secure' property is set on browser session cookies. + # (default: , type: bool) secureAuthCookie: false - # Whether Coder only allows connections to workspaces via the browser. (default: - # , type: bool) + # Whether Coder only allows connections to workspaces via the browser. + # (default: , type: bool) browserOnly: false -# Interval to poll for scheduled workspace builds. (default: 1m0s, type: duration) +# Interval to poll for scheduled workspace builds. +# (default: 1m0s, type: duration) autobuildPollInterval: 1m0s introspection: prometheus: - # Serve prometheus metrics on the address defined by prometheus address. (default: - # , type: bool) + # Serve prometheus metrics on the address defined by prometheus address. + # (default: , type: bool) enable: false - # The bind address to serve prometheus metrics. (default: 127.0.0.1:2112, type: - # host:port) + # The bind address to serve prometheus metrics. + # (default: 127.0.0.1:2112, type: host:port) address: 127.0.0.1:2112 pprof: - # Serve pprof metrics on the address defined by pprof address. (default: , - # type: bool) + # Serve pprof metrics on the address defined by pprof address. + # (default: , type: bool) enable: false - # The bind address to serve pprof. (default: 127.0.0.1:6060, type: host:port) + # The bind address to serve pprof. + # (default: 127.0.0.1:6060, type: host:port) address: 127.0.0.1:6060 tracing: # Whether application tracing data is collected. It exports to a backend @@ -143,150 +162,175 @@ introspection: # 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. (default: , type: bool) + # logs will be included. + # (default: , type: bool) captureLogs: false logging: - # Output debug-level logs. (default: , type: bool) + # Output debug-level logs. + # (default: , type: bool) verbose: false - # Output human-readable logs to a given file. (default: /dev/stderr, type: string) + # Output human-readable logs to a given file. + # (default: /dev/stderr, type: string) humanPath: /dev/stderr - # Output JSON logs to a given file. (default: , type: string) + # Output JSON logs to a given file. + # (default: , type: string) jsonPath: "" - # Output Stackdriver compatible logs to a given file. (default: , type: - # string) + # Output Stackdriver compatible logs to a given file. + # (default: , type: string) stackdriverPath: "" oauth2: github: - # Client ID for Login with GitHub. (default: , type: string) + # Client ID for Login with GitHub. + # (default: , type: string) clientID: "" - # Organizations the user must be a member of to Login with GitHub. (default: - # , type: string-array) + # Organizations the user must be a member of to Login with GitHub. + # (default: , type: string-array) allowedOrgs: [] # Teams inside organizations the user must be a member of to Login with GitHub. - # Structured as: /. (default: , type: - # string-array) + # Structured as: /. + # (default: , type: string-array) allowedTeams: [] - # Whether new users can sign up with GitHub. (default: , type: bool) + # Whether new users can sign up with GitHub. + # (default: , type: bool) allowSignups: false # Allow all logins, setting this option means allowed orgs and teams must be - # empty. (default: , type: bool) + # empty. + # (default: , type: bool) allowEveryone: false # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. # (default: , type: string) enterpriseBaseURL: "" oidc: - # Whether new users can sign up with OIDC. (default: true, type: bool) + # Whether new users can sign up with OIDC. + # (default: true, type: bool) allowSignups: true - # Client ID to use for Login with OIDC. (default: , type: string) + # Client ID to use for Login with OIDC. + # (default: , type: string) clientID: "" - # Email domains that clients logging in with OIDC must match. (default: , - # type: string-array) + # Email domains that clients logging in with OIDC must match. + # (default: , type: string-array) emailDomain: [] - # Issuer URL to use for Login with OIDC. (default: , type: string) + # Issuer URL to use for Login with OIDC. + # (default: , type: string) issuerURL: "" - # Scopes to grant when authenticating with OIDC. (default: openid,profile,email, - # type: string-array) + # Scopes to grant when authenticating with OIDC. + # (default: openid,profile,email, type: string-array) scopes: - openid - profile - email - # Ignore the email_verified claim from the upstream provider. (default: , - # type: bool) + # Ignore the email_verified claim from the upstream provider. + # (default: , type: bool) ignoreEmailVerified: false - # OIDC claim field to use as the username. (default: preferred_username, type: - # string) + # OIDC claim field to use as the username. + # (default: preferred_username, type: string) usernameField: preferred_username - # OIDC claim field to use as the email. (default: email, type: string) + # OIDC claim field to use as the email. + # (default: email, type: string) emailField: email - # OIDC auth URL parameters to pass to the upstream provider. (default: - # {"access_type": "offline"}, type: struct[map[string]string]) + # OIDC auth URL parameters to pass to the upstream provider. + # (default: {"access_type": "offline"}, type: struct[map[string]string]) authURLParams: access_type: offline # Ignore the userinfo endpoint and only use the ID token for user information. # (default: false, type: bool) ignoreUserInfo: false # Change the OIDC default 'groups' claim field. By default, will be 'groups' if - # present in the oidc scopes argument. (default: , type: string) + # present in the oidc scopes argument. + # (default: , type: string) groupField: "" # A map of OIDC group IDs and the group in Coder it should map to. This is useful - # for when OIDC providers only return group IDs. (default: {}, type: - # struct[map[string]string]) + # for when OIDC providers only return group IDs. + # (default: {}, type: struct[map[string]string]) groupMapping: {} - # The text to show on the OpenID Connect sign in button. (default: OpenID Connect, - # type: string) + # The text to show on the OpenID Connect sign in button. + # (default: OpenID Connect, type: string) signInText: OpenID Connect - # URL pointing to the icon to use on the OepnID Connect login button. (default: - # , type: url) + # URL pointing to the icon to use on the OepnID Connect login button. + # (default: , type: url) iconURL: # 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. telemetry: # Whether telemetry is enabled or not. Coder collects anonymized usage data to - # help improve our product. (default: false, type: bool) + # help improve our product. + # (default: false, type: bool) enable: false # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized # application tracing to help improve our product. Disabling telemetry also - # disables this option. (default: false, type: bool) + # disables this option. + # (default: false, type: bool) trace: false - # URL to send telemetry. (default: https://telemetry.coder.com, type: url) + # URL to send telemetry. + # (default: https://telemetry.coder.com, type: url) url: https://telemetry.coder.com # Tune the behavior of the provisioner, which is responsible for creating, # updating, and deleting workspace resources. provisioning: # Number of provisioner daemons to create on start. If builds are stuck in queued - # state for a long time, consider increasing this. (default: 3, type: int) + # state for a long time, consider increasing this. + # (default: 3, type: int) daemons: 3 - # Time to wait before polling for a new job. (default: 1s, type: duration) + # Time to wait before polling for a new job. + # (default: 1s, type: duration) daemonPollInterval: 1s - # Random jitter added to the poll interval. (default: 100ms, type: duration) + # Random jitter added to the poll interval. + # (default: 100ms, type: duration) daemonPollJitter: 100ms - # Time to force cancel provisioning tasks that are stuck. (default: 10m0s, type: - # duration) + # Time to force cancel provisioning tasks that are stuck. + # (default: 10m0s, type: duration) forceCancelInterval: 10m0s # 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. (default: , type: string-array) +# experiments. +# (default: , type: string-array) experiments: [] # Periodically check for new releases of Coder and inform the owner. The check is -# performed once per day. (default: false, type: bool) +# performed once per day. +# (default: false, type: bool) updateCheck: false -# Expose the swagger endpoint via /swagger. (default: , type: bool) +# Expose the swagger endpoint via /swagger. +# (default: , type: bool) enableSwagger: false # The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is -# set, it will be used for compatibility with systemd. (default: -# [cache dir], type: string) +# set, it will be used for compatibility with systemd. +# (default: [cache dir], type: string) cacheDir: [cache dir] -# Controls whether data will be stored in an in-memory database. (default: -# , type: bool) +# Controls whether data will be stored in an in-memory database. +# (default: , type: bool) inMemoryDatabase: false # The algorithm to use for generating ssh keys. Accepted values are "ed25519", -# "ecdsa", or "rsa4096". (default: ed25519, type: string) +# "ecdsa", or "rsa4096". +# (default: ed25519, type: string) sshKeygenAlgorithm: ed25519 -# URL to use for agent troubleshooting when not set in the template. (default: +# URL to use for agent troubleshooting when not set in the template. +# (default: # https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates, # type: url) agentFallbackTroubleshootingURL: https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates # 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. (default: , type: bool) +# --wildcard-access-url is configured. +# (default: , type: bool) disablePathApps: false # These options change the behavior of how clients interact with the Coder. # Clients include the coder cli, vs code extension, and the web UI. client: - # The SSH deployment prefix is used in the Host of the ssh config. (default: - # coder., type: string) + # The SSH deployment prefix is used in the Host of the ssh config. + # (default: coder., type: string) sshHostnamePrefix: coder. # These SSH config options will override the default SSH config options. Provide # options in "key=value" or "key value" format separated by commas.Using this - # incorrectly can break SSH to your deployment, use cautiously. (default: , - # type: string-array) + # incorrectly can break SSH to your deployment, use cautiously. + # (default: , type: string-array) sshConfigOptions: [] -# Support links to display in the top right drop down menu. (default: , -# type: struct[[]codersdk.LinkConfig]) +# Support links to display in the top right drop down menu. +# (default: , type: struct[[]codersdk.LinkConfig]) supportLinks: [] # Hostname of HTTPS server that runs https://github.com/coder/wgtunnel. By # default, this will pick the best available wgtunnel server hosted by Coder. e.g. -# "tunnel.example.com". (default: , type: string) +# "tunnel.example.com". +# (default: , type: string) wgtunnelHost: "" diff --git a/docs/cli/server.md b/docs/cli/server.md index 3b25d0352373d..0d0f061c669bf 100644 --- a/docs/cli/server.md +++ b/docs/cli/server.md @@ -51,6 +51,16 @@ Whether Coder only allows connections to workspaces via the browser. The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd. +### --trace-logs + +| | | +| ----------- | ---------------------------------------------- | +| Type | bool | +| Environment | $CODER_TRACE_LOGS | +| YAML | introspection.tracing.captureLogs | + +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. + ### -c, --config | | | @@ -193,6 +203,16 @@ Disable workspace apps that are not served from subdomains. Path-based apps can Disable automatic session expiry bumping due to activity. This forces all sessions to become invalid after the session expiry duration has been reached. +### --swagger-enable + +| | | +| ----------- | ---------------------------------- | +| Type | bool | +| Environment | $CODER_SWAGGER_ENABLE | +| YAML | enableSwagger | + +Expose the swagger endpoint via /swagger. + ### --experiments | | | @@ -203,6 +223,17 @@ Disable automatic session expiry bumping due to activity. This forces all sessio 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. +### --provisioner-force-cancel-interval + +| | | +| ----------- | ----------------------------------------------------- | +| Type | duration | +| Environment | $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL | +| YAML | provisioning.forceCancelInterval | +| Default | 10m0s | + +Time to force cancel provisioning tasks that are stuck. + ### --http-address | | | @@ -235,16 +266,6 @@ Output human-readable logs to a given file. Output JSON logs to a given file. -### --log-stackdriver - -| | | -| ----------- | -------------------------------------------------- | -| Type | string | -| Environment | $CODER_LOGGING_STACKDRIVER | -| YAML | introspection.logging.stackdriverPath | - -Output Stackdriver compatible logs to a given file. - ### --max-token-lifetime | | | @@ -408,16 +429,6 @@ Change the OIDC default 'groups' claim field. By default, will be 'groups' if pr A map of OIDC group IDs and the group in Coder it should map to. This is useful for when OIDC providers only return group IDs. -### --oidc-icon-url - -| | | -| ----------- | --------------------------------- | -| Type | url | -| Environment | $CODER_OIDC_ICON_URL | -| YAML | oidc.iconURL | - -URL pointing to the icon to use on the OepnID Connect login button. - ### --oidc-ignore-email-verified | | | @@ -460,17 +471,6 @@ Issuer URL to use for Login with OIDC. Scopes to grant when authenticating with OIDC. -### --oidc-sign-in-text - -| | | -| ----------- | ------------------------------------- | -| Type | string | -| Environment | $CODER_OIDC_SIGN_IN_TEXT | -| YAML | oidc.signInText | -| Default | OpenID Connect | - -The text to show on the OpenID Connect sign in button. - ### --oidc-username-field | | | @@ -482,35 +482,57 @@ The text to show on the OpenID Connect sign in button. OIDC claim field to use as the username. -### --postgres-url +### --oidc-sign-in-text | | | | ----------- | ------------------------------------- | | Type | string | -| Environment | $CODER_PG_CONNECTION_URL | +| Environment | $CODER_OIDC_SIGN_IN_TEXT | +| YAML | oidc.signInText | +| Default | OpenID Connect | -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". +The text to show on the OpenID Connect sign in button. -### --pprof-address +### --oidc-icon-url -| | | -| ----------- | ---------------------------------------- | -| Type | host:port | -| Environment | $CODER_PPROF_ADDRESS | -| YAML | introspection.pprof.address | -| Default | 127.0.0.1:6060 | +| | | +| ----------- | --------------------------------- | +| Type | url | +| Environment | $CODER_OIDC_ICON_URL | +| YAML | oidc.iconURL | -The bind address to serve pprof. +URL pointing to the icon to use on the OepnID Connect login button. -### --pprof-enable +### --provisioner-daemon-poll-interval -| | | -| ----------- | --------------------------------------- | -| Type | bool | -| Environment | $CODER_PPROF_ENABLE | -| YAML | introspection.pprof.enable | +| | | +| ----------- | ---------------------------------------------------- | +| Type | duration | +| Environment | $CODER_PROVISIONER_DAEMON_POLL_INTERVAL | +| YAML | provisioning.daemonPollInterval | +| Default | 1s | -Serve pprof metrics on the address defined by pprof address. +Time to wait before polling for a new job. + +### --provisioner-daemon-poll-jitter + +| | | +| ----------- | -------------------------------------------------- | +| Type | duration | +| Environment | $CODER_PROVISIONER_DAEMON_POLL_JITTER | +| YAML | provisioning.daemonPollJitter | +| Default | 100ms | + +Random jitter added to the poll interval. + +### --postgres-url + +| | | +| ----------- | ------------------------------------- | +| Type | string | +| Environment | $CODER_PG_CONNECTION_URL | + +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". ### --prometheus-address @@ -533,28 +555,6 @@ The bind address to serve prometheus metrics. Serve prometheus metrics on the address defined by prometheus address. -### --provisioner-daemon-poll-interval - -| | | -| ----------- | ---------------------------------------------------- | -| Type | duration | -| Environment | $CODER_PROVISIONER_DAEMON_POLL_INTERVAL | -| YAML | provisioning.daemonPollInterval | -| Default | 1s | - -Time to wait before polling for a new job. - -### --provisioner-daemon-poll-jitter - -| | | -| ----------- | -------------------------------------------------- | -| Type | duration | -| Environment | $CODER_PROVISIONER_DAEMON_POLL_JITTER | -| YAML | provisioning.daemonPollJitter | -| Default | 100ms | - -Random jitter added to the poll interval. - ### --provisioner-daemons | | | @@ -566,17 +566,6 @@ Random jitter added to the poll interval. Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. -### --provisioner-force-cancel-interval - -| | | -| ----------- | ----------------------------------------------------- | -| Type | duration | -| Environment | $CODER_PROVISIONER_FORCE_CANCEL_INTERVAL | -| YAML | provisioning.forceCancelInterval | -| Default | 10m0s | - -Time to force cancel provisioning tasks that are stuck. - ### --proxy-trusted-headers | | | @@ -616,27 +605,6 @@ Specifies whether to redirect requests that do not match the access URL host. Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication. -### --secure-auth-cookie - -| | | -| ----------- | ---------------------------------------- | -| Type | bool | -| Environment | $CODER_SECURE_AUTH_COOKIE | -| YAML | networking.secureAuthCookie | - -Controls if the 'Secure' property is set on browser session cookies. - -### --session-duration - -| | | -| ----------- | -------------------------------------------- | -| Type | duration | -| Environment | $CODER_SESSION_DURATION | -| YAML | networking.http.sessionDuration | -| Default | 24h0m0s | - -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. - ### --ssh-config-options | | | @@ -669,6 +637,37 @@ The SSH deployment prefix is used in the Host of the ssh config. The algorithm to use for generating ssh keys. Accepted values are "ed25519", "ecdsa", or "rsa4096". +### --secure-auth-cookie + +| | | +| ----------- | ---------------------------------------- | +| Type | bool | +| Environment | $CODER_SECURE_AUTH_COOKIE | +| YAML | networking.secureAuthCookie | + +Controls if the 'Secure' property is set on browser session cookies. + +### --session-duration + +| | | +| ----------- | -------------------------------------------- | +| Type | duration | +| Environment | $CODER_SESSION_DURATION | +| YAML | networking.http.sessionDuration | +| Default | 24h0m0s | + +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. + +### --log-stackdriver + +| | | +| ----------- | -------------------------------------------------- | +| Type | string | +| Environment | $CODER_LOGGING_STACKDRIVER | +| YAML | introspection.logging.stackdriverPath | + +Output Stackdriver compatible logs to a given file. + ### --strict-transport-security | | | @@ -690,38 +689,6 @@ Controls if the 'Strict-Transport-Security' header is set on all static file res 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. -### --swagger-enable - -| | | -| ----------- | ---------------------------------- | -| Type | bool | -| Environment | $CODER_SWAGGER_ENABLE | -| YAML | enableSwagger | - -Expose the swagger endpoint via /swagger. - -### --telemetry - -| | | -| ----------- | ------------------------------------ | -| Type | bool | -| Environment | $CODER_TELEMETRY_ENABLE | -| YAML | telemetry.enable | -| Default | true | - -Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product. - -### --telemetry-trace - -| | | -| ----------- | ----------------------------------- | -| Type | bool | -| Environment | $CODER_TELEMETRY_TRACE | -| YAML | telemetry.trace | -| Default | true | - -Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option. - ### --tls-address | | | @@ -815,6 +782,28 @@ Paths to the private keys for each of the certificates. It requires a PEM-encode Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13". +### --telemetry + +| | | +| ----------- | ------------------------------------ | +| Type | bool | +| Environment | $CODER_TELEMETRY_ENABLE | +| YAML | telemetry.enable | +| Default | true | + +Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product. + +### --telemetry-trace + +| | | +| ----------- | ----------------------------------- | +| Type | bool | +| Environment | $CODER_TELEMETRY_TRACE | +| YAML | telemetry.trace | +| Default | true | + +Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option. + ### --trace | | | @@ -834,16 +823,6 @@ Whether application tracing data is collected. It exports to a backend configure Enables trace exporting to Honeycomb.io using the provided API Key. -### --trace-logs - -| | | -| ----------- | ---------------------------------------------- | -| Type | bool | -| Environment | $CODER_TRACE_LOGS | -| YAML | introspection.tracing.captureLogs | - -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. - ### --update-check | | | @@ -882,3 +861,24 @@ Specifies the wildcard hostname to use for workspace applications in the form "\ | Type | bool |
Write out the current server config as YAML to stdout. + +### --pprof-address + +| | | +| ----------- | ---------------------------------------- | +| Type | host:port | +| Environment | $CODER_PPROF_ADDRESS | +| YAML | introspection.pprof.address | +| Default | 127.0.0.1:6060 | + +The bind address to serve pprof. + +### --pprof-enable + +| | | +| ----------- | --------------------------------------- | +| Type | bool | +| Environment | $CODER_PPROF_ENABLE | +| YAML | introspection.pprof.enable | + +Serve pprof metrics on the address defined by pprof address. From 2a96c7046786c740782909e92950178a788b304e Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 18:45:05 +0000 Subject: [PATCH 30/35] Fix windows? --- cli/server_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/server_test.go b/cli/server_test.go index 0bbd378678191..39337468c0510 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1577,6 +1577,7 @@ func TestServerYAMLConfig(t *testing.T) { got, err := os.ReadFile(goldenPath) require.NoError(t, err) + got = normalizeGoldenFile(t, got) require.Equal(t, string(wantByt), string(got)) } From 7ae7cadfa7255bdd348528f2ad2f74da32dd8fa4 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 19:20:45 +0000 Subject: [PATCH 31/35] Small improvements --- cli/server.go | 1 + coderd/rbac/error.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/server.go b/cli/server.go index 071bea18b73af..18ebf0ca576eb 100644 --- a/cli/server.go +++ b/cli/server.go @@ -181,6 +181,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. return xerrors.Errorf("generate yaml: %w", err) } enc := yaml.NewEncoder(inv.Stdout) + enc.SetIndent(2) err = enc.Encode(n) if err != nil { return xerrors.Errorf("encode yaml: %w", err) diff --git a/coderd/rbac/error.go b/coderd/rbac/error.go index dafd08af2e6b7..8e855eb7f8b49 100644 --- a/coderd/rbac/error.go +++ b/coderd/rbac/error.go @@ -10,7 +10,7 @@ const ( // errUnauthorized is the error message that should be returned to // clients when an action is forbidden. It is intentionally vague to prevent // disclosing information that a client should not have access to. - errUnauthorized = "forbidden" + errUnauthorized = "rbac: forbidden" ) // UnauthorizedError is the error type for authorization errors From 09d5a353473aea69aa36c2730b91f06cf46b9c4b Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 19:24:14 +0000 Subject: [PATCH 32/35] Reduce YAML ident to 2 --- cli/server_test.go | 7 +- cli/testdata/server-config.yaml.golden | 540 ++++++++++++------------- 2 files changed, 276 insertions(+), 271 deletions(-) diff --git a/cli/server_test.go b/cli/server_test.go index 39337468c0510..e89a77dafdae4 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1564,9 +1564,14 @@ func TestServerYAMLConfig(t *testing.T) { err = opts.UnmarshalYAML(n.(*yaml.Node)) require.NoError(t, err) - wantByt, err := yaml.Marshal(n) + var wantBuf bytes.Buffer + enc := yaml.NewEncoder(&wantBuf) + enc.SetIndent(2) + err = enc.Encode(n) require.NoError(t, err) + wantByt := wantBuf.Bytes() + goldenPath := filepath.Join("testdata", "server-config.yaml.golden") wantByt = normalizeGoldenFile(t, wantByt) diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index c1bfffc686a0f..c552999c7e8a1 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -1,286 +1,286 @@ networking: - # The URL that users will use to access the Coder deployment. - # (default: , type: url) - accessURL: - # Specifies the wildcard hostname to use for workspace applications in the form - # "*.example.com". - # (default: , type: url) - wildcardAccessURL: - # Specifies whether to redirect requests that do not match the access URL host. + # The URL that users will use to access the Coder deployment. + # (default: , type: url) + accessURL: + # Specifies the wildcard hostname to use for workspace applications in the form + # "*.example.com". + # (default: , type: url) + wildcardAccessURL: + # Specifies whether to redirect requests that do not match the access URL host. + # (default: , type: bool) + redirectToAccessURL: false + http: + # HTTP bind address of the server. Unset to disable the HTTP endpoint. + # (default: 127.0.0.1:3000, type: string) + httpAddress: 127.0.0.1:3000 + # The maximum lifetime duration users can specify when creating an API token. + # (default: 876600h0m0s, type: duration) + maxTokenLifetime: 876600h0m0s + # 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. + # (default: 24h0m0s, type: duration) + sessionDuration: 24h0m0s + # Disable automatic session expiry bumping due to activity. This forces all + # sessions to become invalid after the session expiry duration has been reached. # (default: , type: bool) - redirectToAccessURL: false - http: - # HTTP bind address of the server. Unset to disable the HTTP endpoint. - # (default: 127.0.0.1:3000, type: string) - httpAddress: 127.0.0.1:3000 - # The maximum lifetime duration users can specify when creating an API token. - # (default: 876600h0m0s, type: duration) - maxTokenLifetime: 876600h0m0s - # 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. - # (default: 24h0m0s, type: duration) - sessionDuration: 24h0m0s - # Disable automatic session expiry bumping due to activity. This forces all - # sessions to become invalid after the session expiry duration has been reached. - # (default: , type: bool) - disableSessionExpiryRefresh: false - # 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. - # (default: , type: bool) - disablePasswordAuth: false - # 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. - tls: - # HTTPS bind address of the server. - # (default: 127.0.0.1:3443, type: host:port) - address: 127.0.0.1:3443 - # Whether TLS will be enabled. - # (default: , type: bool) - enable: false - # 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. - # (default: true, type: bool) - redirectHTTP: true - # 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. - # (default: , type: string-array) - certFiles: [] - # PEM-encoded Certificate Authority file used for checking the authenticity of - # client. - # (default: , type: string) - clientCAFile: "" - # Policy the server will follow for TLS Client Authentication. Accepted values are - # "none", "request", "require-any", "verify-if-given", or "require-and-verify". - # (default: none, type: string) - clientAuth: none - # Paths to the private keys for each of the certificates. It requires a - # PEM-encoded file. - # (default: , type: string-array) - keyFiles: [] - # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" - # or "tls13". - # (default: tls12, type: string) - minVersion: tls12 - # Path to certificate for client TLS authentication. It requires a PEM-encoded - # file. - # (default: , type: string) - clientCertFile: "" - # Path to key for client TLS authentication. It requires a PEM-encoded file. - # (default: , type: string) - clientKeyFile: "" - # 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, type: int) - strictTransportSecurity: 0 - # 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. - # (default: , type: string-array) - strictTransportSecurityOptions: [] - # 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. - derp: - # Whether to enable or disable the embedded DERP relay server. - # (default: true, type: bool) - enable: true - # Region ID to use for the embedded DERP server. - # (default: 999, type: int) - regionID: 999 - # Region code to use for the embedded DERP server. - # (default: coder, type: string) - regionCode: coder - # Region name that for the embedded DERP server. - # (default: Coder Embedded Relay, type: string) - regionName: Coder Embedded Relay - # Addresses for STUN servers to establish P2P connections. Set empty to disable - # P2P connections. - # (default: stun.l.google.com:19302, type: string-array) - stunAddresses: - - stun.l.google.com:19302 - # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required - # for high availability. - # (default: , type: url) - relayURL: - # URL to fetch a DERP mapping on startup. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. - # (default: , type: string) - url: "" - # Path to read a DERP mapping from. See: - # https://tailscale.com/kb/1118/custom-derp-servers/. - # (default: , type: string) - configPath: "" - # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, - # True-Client-Ip, X-Forwarded-For. - # (default: , type: string-array) - proxyTrustedHeaders: [] - # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. - # (default: , type: string-array) - proxyTrustedOrigins: [] - # Controls if the 'Secure' property is set on browser session cookies. + disableSessionExpiryRefresh: false + # 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. # (default: , type: bool) - secureAuthCookie: false - # Whether Coder only allows connections to workspaces via the browser. + disablePasswordAuth: false + # 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. + tls: + # HTTPS bind address of the server. + # (default: 127.0.0.1:3443, type: host:port) + address: 127.0.0.1:3443 + # Whether TLS will be enabled. # (default: , type: bool) - browserOnly: false + enable: false + # 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. + # (default: true, type: bool) + redirectHTTP: true + # 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. + # (default: , type: string-array) + certFiles: [] + # PEM-encoded Certificate Authority file used for checking the authenticity of + # client. + # (default: , type: string) + clientCAFile: "" + # Policy the server will follow for TLS Client Authentication. Accepted values are + # "none", "request", "require-any", "verify-if-given", or "require-and-verify". + # (default: none, type: string) + clientAuth: none + # Paths to the private keys for each of the certificates. It requires a + # PEM-encoded file. + # (default: , type: string-array) + keyFiles: [] + # Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" + # or "tls13". + # (default: tls12, type: string) + minVersion: tls12 + # Path to certificate for client TLS authentication. It requires a PEM-encoded + # file. + # (default: , type: string) + clientCertFile: "" + # Path to key for client TLS authentication. It requires a PEM-encoded file. + # (default: , type: string) + clientKeyFile: "" + # 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, type: int) + strictTransportSecurity: 0 + # 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. + # (default: , type: string-array) + strictTransportSecurityOptions: [] + # 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. + derp: + # Whether to enable or disable the embedded DERP relay server. + # (default: true, type: bool) + enable: true + # Region ID to use for the embedded DERP server. + # (default: 999, type: int) + regionID: 999 + # Region code to use for the embedded DERP server. + # (default: coder, type: string) + regionCode: coder + # Region name that for the embedded DERP server. + # (default: Coder Embedded Relay, type: string) + regionName: Coder Embedded Relay + # Addresses for STUN servers to establish P2P connections. Set empty to disable + # P2P connections. + # (default: stun.l.google.com:19302, type: string-array) + stunAddresses: + - stun.l.google.com:19302 + # An HTTP URL that is accessible by other replicas to relay DERP traffic. Required + # for high availability. + # (default: , type: url) + relayURL: + # URL to fetch a DERP mapping on startup. See: + # https://tailscale.com/kb/1118/custom-derp-servers/. + # (default: , type: string) + url: "" + # Path to read a DERP mapping from. See: + # https://tailscale.com/kb/1118/custom-derp-servers/. + # (default: , type: string) + configPath: "" + # Headers to trust for forwarding IP addresses. e.g. Cf-Connecting-Ip, + # True-Client-Ip, X-Forwarded-For. + # (default: , type: string-array) + proxyTrustedHeaders: [] + # Origin addresses to respect "proxy-trusted-headers". e.g. 192.168.1.0/24. + # (default: , type: string-array) + proxyTrustedOrigins: [] + # Controls if the 'Secure' property is set on browser session cookies. + # (default: , type: bool) + secureAuthCookie: false + # Whether Coder only allows connections to workspaces via the browser. + # (default: , type: bool) + browserOnly: false # Interval to poll for scheduled workspace builds. # (default: 1m0s, type: duration) autobuildPollInterval: 1m0s introspection: - prometheus: - # Serve prometheus metrics on the address defined by prometheus address. - # (default: , type: bool) - enable: false - # The bind address to serve prometheus metrics. - # (default: 127.0.0.1:2112, type: host:port) - address: 127.0.0.1:2112 - pprof: - # Serve pprof metrics on the address defined by pprof address. - # (default: , type: bool) - enable: false - # The bind address to serve pprof. - # (default: 127.0.0.1:6060, type: host:port) - address: 127.0.0.1:6060 - tracing: - # 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. - # (default: , type: bool) - enable: false - # 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. - # (default: , type: bool) - captureLogs: false - logging: - # Output debug-level logs. - # (default: , type: bool) - verbose: false - # Output human-readable logs to a given file. - # (default: /dev/stderr, type: string) - humanPath: /dev/stderr - # Output JSON logs to a given file. - # (default: , type: string) - jsonPath: "" - # Output Stackdriver compatible logs to a given file. - # (default: , type: string) - stackdriverPath: "" + prometheus: + # Serve prometheus metrics on the address defined by prometheus address. + # (default: , type: bool) + enable: false + # The bind address to serve prometheus metrics. + # (default: 127.0.0.1:2112, type: host:port) + address: 127.0.0.1:2112 + pprof: + # Serve pprof metrics on the address defined by pprof address. + # (default: , type: bool) + enable: false + # The bind address to serve pprof. + # (default: 127.0.0.1:6060, type: host:port) + address: 127.0.0.1:6060 + tracing: + # 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. + # (default: , type: bool) + enable: false + # 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. + # (default: , type: bool) + captureLogs: false + logging: + # Output debug-level logs. + # (default: , type: bool) + verbose: false + # Output human-readable logs to a given file. + # (default: /dev/stderr, type: string) + humanPath: /dev/stderr + # Output JSON logs to a given file. + # (default: , type: string) + jsonPath: "" + # Output Stackdriver compatible logs to a given file. + # (default: , type: string) + stackdriverPath: "" oauth2: - github: - # Client ID for Login with GitHub. - # (default: , type: string) - clientID: "" - # Organizations the user must be a member of to Login with GitHub. - # (default: , type: string-array) - allowedOrgs: [] - # Teams inside organizations the user must be a member of to Login with GitHub. - # Structured as: /. - # (default: , type: string-array) - allowedTeams: [] - # Whether new users can sign up with GitHub. - # (default: , type: bool) - allowSignups: false - # Allow all logins, setting this option means allowed orgs and teams must be - # empty. - # (default: , type: bool) - allowEveryone: false - # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. - # (default: , type: string) - enterpriseBaseURL: "" -oidc: - # Whether new users can sign up with OIDC. - # (default: true, type: bool) - allowSignups: true - # Client ID to use for Login with OIDC. + github: + # Client ID for Login with GitHub. # (default: , type: string) clientID: "" - # Email domains that clients logging in with OIDC must match. + # Organizations the user must be a member of to Login with GitHub. # (default: , type: string-array) - emailDomain: [] - # Issuer URL to use for Login with OIDC. - # (default: , type: string) - issuerURL: "" - # Scopes to grant when authenticating with OIDC. - # (default: openid,profile,email, type: string-array) - scopes: - - openid - - profile - - email - # Ignore the email_verified claim from the upstream provider. + allowedOrgs: [] + # Teams inside organizations the user must be a member of to Login with GitHub. + # Structured as: /. + # (default: , type: string-array) + allowedTeams: [] + # Whether new users can sign up with GitHub. + # (default: , type: bool) + allowSignups: false + # Allow all logins, setting this option means allowed orgs and teams must be + # empty. # (default: , type: bool) - ignoreEmailVerified: false - # OIDC claim field to use as the username. - # (default: preferred_username, type: string) - usernameField: preferred_username - # OIDC claim field to use as the email. - # (default: email, type: string) - emailField: email - # OIDC auth URL parameters to pass to the upstream provider. - # (default: {"access_type": "offline"}, type: struct[map[string]string]) - authURLParams: - access_type: offline - # Ignore the userinfo endpoint and only use the ID token for user information. - # (default: false, type: bool) - ignoreUserInfo: false - # Change the OIDC default 'groups' claim field. By default, will be 'groups' if - # present in the oidc scopes argument. + allowEveryone: false + # Base URL of a GitHub Enterprise deployment to use for Login with GitHub. # (default: , type: string) - groupField: "" - # A map of OIDC group IDs and the group in Coder it should map to. This is useful - # for when OIDC providers only return group IDs. - # (default: {}, type: struct[map[string]string]) - groupMapping: {} - # The text to show on the OpenID Connect sign in button. - # (default: OpenID Connect, type: string) - signInText: OpenID Connect - # URL pointing to the icon to use on the OepnID Connect login button. - # (default: , type: url) - iconURL: + enterpriseBaseURL: "" +oidc: + # Whether new users can sign up with OIDC. + # (default: true, type: bool) + allowSignups: true + # Client ID to use for Login with OIDC. + # (default: , type: string) + clientID: "" + # Email domains that clients logging in with OIDC must match. + # (default: , type: string-array) + emailDomain: [] + # Issuer URL to use for Login with OIDC. + # (default: , type: string) + issuerURL: "" + # Scopes to grant when authenticating with OIDC. + # (default: openid,profile,email, type: string-array) + scopes: + - openid + - profile + - email + # Ignore the email_verified claim from the upstream provider. + # (default: , type: bool) + ignoreEmailVerified: false + # OIDC claim field to use as the username. + # (default: preferred_username, type: string) + usernameField: preferred_username + # OIDC claim field to use as the email. + # (default: email, type: string) + emailField: email + # OIDC auth URL parameters to pass to the upstream provider. + # (default: {"access_type": "offline"}, type: struct[map[string]string]) + authURLParams: + access_type: offline + # Ignore the userinfo endpoint and only use the ID token for user information. + # (default: false, type: bool) + ignoreUserInfo: false + # Change the OIDC default 'groups' claim field. By default, will be 'groups' if + # present in the oidc scopes argument. + # (default: , type: string) + groupField: "" + # A map of OIDC group IDs and the group in Coder it should map to. This is useful + # for when OIDC providers only return group IDs. + # (default: {}, type: struct[map[string]string]) + groupMapping: {} + # The text to show on the OpenID Connect sign in button. + # (default: OpenID Connect, type: string) + signInText: OpenID Connect + # URL pointing to the icon to use on the OepnID Connect login button. + # (default: , type: url) + iconURL: # 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. telemetry: - # Whether telemetry is enabled or not. Coder collects anonymized usage data to - # help improve our product. - # (default: false, type: bool) - enable: false - # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized - # application tracing to help improve our product. Disabling telemetry also - # disables this option. - # (default: false, type: bool) - trace: false - # URL to send telemetry. - # (default: https://telemetry.coder.com, type: url) - url: https://telemetry.coder.com + # Whether telemetry is enabled or not. Coder collects anonymized usage data to + # help improve our product. + # (default: false, type: bool) + enable: false + # Whether Opentelemetry traces are sent to Coder. Coder collects anonymized + # application tracing to help improve our product. Disabling telemetry also + # disables this option. + # (default: false, type: bool) + trace: false + # URL to send telemetry. + # (default: https://telemetry.coder.com, type: url) + url: https://telemetry.coder.com # Tune the behavior of the provisioner, which is responsible for creating, # updating, and deleting workspace resources. provisioning: - # Number of provisioner daemons to create on start. If builds are stuck in queued - # state for a long time, consider increasing this. - # (default: 3, type: int) - daemons: 3 - # Time to wait before polling for a new job. - # (default: 1s, type: duration) - daemonPollInterval: 1s - # Random jitter added to the poll interval. - # (default: 100ms, type: duration) - daemonPollJitter: 100ms - # Time to force cancel provisioning tasks that are stuck. - # (default: 10m0s, type: duration) - forceCancelInterval: 10m0s + # Number of provisioner daemons to create on start. If builds are stuck in queued + # state for a long time, consider increasing this. + # (default: 3, type: int) + daemons: 3 + # Time to wait before polling for a new job. + # (default: 1s, type: duration) + daemonPollInterval: 1s + # Random jitter added to the poll interval. + # (default: 100ms, type: duration) + daemonPollJitter: 100ms + # Time to force cancel provisioning tasks that are stuck. + # (default: 10m0s, type: duration) + forceCancelInterval: 10m0s # 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. @@ -318,14 +318,14 @@ disablePathApps: false # These options change the behavior of how clients interact with the Coder. # Clients include the coder cli, vs code extension, and the web UI. client: - # The SSH deployment prefix is used in the Host of the ssh config. - # (default: coder., type: string) - sshHostnamePrefix: coder. - # These SSH config options will override the default SSH config options. Provide - # options in "key=value" or "key value" format separated by commas.Using this - # incorrectly can break SSH to your deployment, use cautiously. - # (default: , type: string-array) - sshConfigOptions: [] + # The SSH deployment prefix is used in the Host of the ssh config. + # (default: coder., type: string) + sshHostnamePrefix: coder. + # These SSH config options will override the default SSH config options. Provide + # options in "key=value" or "key value" format separated by commas.Using this + # incorrectly can break SSH to your deployment, use cautiously. + # (default: , type: string-array) + sshConfigOptions: [] # Support links to display in the top right drop down menu. # (default: , type: struct[[]codersdk.LinkConfig]) supportLinks: [] From d6506c6afbfb1048fe2070b07c04aa544359e3c5 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 19:47:59 +0000 Subject: [PATCH 33/35] Log mystery error --- coderd/rbac/error.go | 8 ++++++++ coderd/users.go | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/coderd/rbac/error.go b/coderd/rbac/error.go index 8e855eb7f8b49..60fef7ca460da 100644 --- a/coderd/rbac/error.go +++ b/coderd/rbac/error.go @@ -2,6 +2,7 @@ package rbac import ( "errors" + "fmt" "github.com/open-policy-agent/opa/rego" ) @@ -56,6 +57,13 @@ func (UnauthorizedError) Error() string { return errUnauthorized } +func (e *UnauthorizedError) LongError() string { + return fmt.Sprintf( + "%s: (subject: %v), (action: %v), (object: %v), (output: %v)", + errUnauthorized, e.subject, e.action, e.object, e.output, + ) +} + // Internal allows the internal error message to be logged. func (e *UnauthorizedError) Internal() error { return e.internal diff --git a/coderd/users.go b/coderd/users.go index c39b33b931f9b..06264ee3ca77c 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" + "cdr.dev/slog" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" @@ -1023,6 +1024,15 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create PublicKey: publicKey, }) if err != nil { + var unauthError rbac.UnauthorizedError + if errors.As(err, &unauthError) { + // This should be impossible. + api.Logger.Error( + ctx, + "unexpected unauthorize error when inserting ssh key", + slog.F("error", unauthError.LongError()), + ) + } return xerrors.Errorf("insert user gitsshkey: %w", err) } _, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ From 551e55dacec828ce41b3e599f85c1dd798653a7f Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 20:04:10 +0000 Subject: [PATCH 34/35] fixup! Log mystery error --- coderd/rbac/error.go | 16 ++++++++++------ coderd/users.go | 10 ---------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/coderd/rbac/error.go b/coderd/rbac/error.go index 60fef7ca460da..9492c0dff8d96 100644 --- a/coderd/rbac/error.go +++ b/coderd/rbac/error.go @@ -2,6 +2,7 @@ package rbac import ( "errors" + "flag" "fmt" "github.com/open-policy-agent/opa/rego" @@ -52,18 +53,21 @@ func (e UnauthorizedError) Unwrap() error { return e.internal } -// Error implements the error interface. -func (UnauthorizedError) Error() string { - return errUnauthorized -} - -func (e *UnauthorizedError) LongError() string { +func (e *UnauthorizedError) longError() string { return fmt.Sprintf( "%s: (subject: %v), (action: %v), (object: %v), (output: %v)", errUnauthorized, e.subject, e.action, e.object, e.output, ) } +// Error implements the error interface. +func (e UnauthorizedError) Error() string { + if flag.Lookup("test.v") != nil { + return e.longError() + } + return errUnauthorized +} + // Internal allows the internal error message to be logged. func (e *UnauthorizedError) Internal() error { return e.internal diff --git a/coderd/users.go b/coderd/users.go index 06264ee3ca77c..c39b33b931f9b 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -7,7 +7,6 @@ import ( "fmt" "net/http" - "cdr.dev/slog" "github.com/go-chi/chi/v5" "github.com/go-chi/render" "github.com/google/uuid" @@ -1024,15 +1023,6 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create PublicKey: publicKey, }) if err != nil { - var unauthError rbac.UnauthorizedError - if errors.As(err, &unauthError) { - // This should be impossible. - api.Logger.Error( - ctx, - "unexpected unauthorize error when inserting ssh key", - slog.F("error", unauthError.LongError()), - ) - } return xerrors.Errorf("insert user gitsshkey: %w", err) } _, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ From c3f33172cdf7fbe083f4b27fdcf78c42e1149d68 Mon Sep 17 00:00:00 2001 From: Ammar Bandukwala Date: Fri, 7 Apr 2023 20:54:22 +0000 Subject: [PATCH 35/35] ecdsa --- cli/server_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/server_test.go b/cli/server_test.go index e89a77dafdae4..55205e6f9b043 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -1431,7 +1431,8 @@ func TestServer(t *testing.T) { "--http-address", ":0", "--access-url", "http://example.com", "--log-human", filepath.Join(t.TempDir(), "coder-logging-test-human"), - "--ssh-keygen-algorithm", "rsa4096", + // We use ecdsa here because it's the fastest alternative algorithm. + "--ssh-keygen-algorithm", "ecdsa", "--cache-dir", t.TempDir(), }