diff --git a/cli/server.go b/cli/server.go index b64b337964ba2..bb34265a2c0e8 100644 --- a/cli/server.go +++ b/cli/server.go @@ -1026,6 +1026,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd. } } + coderd.LogOAuth2ProviderState(ctx, logger, options.Database, vals.OAuth2.Provider) + options.RuntimeConfig = runtimeconfig.NewManager() // This should be output before the logs start streaming. diff --git a/cli/server_test.go b/cli/server_test.go index 956ab3ef56a33..5f3e93baa3439 100644 --- a/cli/server_test.go +++ b/cli/server_test.go @@ -381,7 +381,9 @@ func TestServer(t *testing.T) { out := pty.ReadAll() numLines := countLines(string(out)) t.Logf("numLines: %d", numLines) - require.Less(t, numLines, 20, "expected less than 20 lines of output (terminal width 80), got %d", numLines) + // The OAuth2 provider state line is logged on every start and wraps + // to two rows at this width. + require.Less(t, numLines, 22, "expected less than 22 lines of output (terminal width 80), got %d", numLines) }) t.Run("OAuth2GitHubDefaultProvider", func(t *testing.T) { diff --git a/coderd/oauth2_provider_state.go b/coderd/oauth2_provider_state.go new file mode 100644 index 0000000000000..80f64807f1a82 --- /dev/null +++ b/coderd/oauth2_provider_state.go @@ -0,0 +1,40 @@ +package coderd + +import ( + "context" + "fmt" + + "cdr.dev/slog/v3" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbauthz" + "github.com/coder/coder/v2/codersdk" +) + +// oauth2ProviderDisabledWithAppsMessage is logged at startup when the +// provider is off but applications are still registered. +const oauth2ProviderDisabledWithAppsMessage = "The OAuth2 provider is disabled but %d OAuth2 application(s) are registered. Existing applications, secrets and user authorizations are preserved but cannot be used until the provider is enabled. Set CODER_OAUTH2_PROVIDER_ENABLE=true to enable it." + +// LogOAuth2ProviderState logs whether the OAuth2 provider is enabled so +// every start leaves one line recording the value of +// CODER_OAUTH2_PROVIDER_ENABLE. When the provider is disabled it also warns +// if applications are still registered. Errors are logged and startup +// continues. +func LogOAuth2ProviderState(ctx context.Context, logger slog.Logger, db database.Store, cfg codersdk.OAuth2ProviderConfig) { + flag := slog.F("flag", "CODER_OAUTH2_PROVIDER_ENABLE") + if cfg.Enable.Value() { + logger.Info(ctx, "oauth2 provider enabled", flag) + return + } + logger.Info(ctx, "oauth2 provider disabled", flag) + + //nolint:gocritic // Startup-only read; no user actor is present. + apps, err := db.GetOAuth2ProviderApps(dbauthz.AsSystemRestricted(ctx)) + if err != nil { + logger.Warn(ctx, "oauth2 provider: list registered applications", slog.Error(err)) + return + } + if len(apps) == 0 { + return + } + logger.Warn(ctx, fmt.Sprintf(oauth2ProviderDisabledWithAppsMessage, len(apps))) +} diff --git a/coderd/oauth2_provider_state_internal_test.go b/coderd/oauth2_provider_state_internal_test.go new file mode 100644 index 0000000000000..8aaad89004538 --- /dev/null +++ b/coderd/oauth2_provider_state_internal_test.go @@ -0,0 +1,72 @@ +package coderd + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "golang.org/x/xerrors" + + "cdr.dev/slog/v3" + "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbmock" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/testutil" + "github.com/coder/serpent" +) + +func TestLogOAuth2ProviderState(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + name string + enabled bool + apps []database.OAuth2ProviderApp + queryErr error + wantInfo string + wantWarn []string + }{ + { + name: "Enabled", + enabled: true, + wantInfo: "oauth2 provider enabled", + }, + { + name: "DisabledNoApps", + wantInfo: "oauth2 provider disabled", + }, + { + name: "DisabledWithApps", + apps: []database.OAuth2ProviderApp{{}, {}}, + wantInfo: "oauth2 provider disabled", + wantWarn: []string{fmt.Sprintf(oauth2ProviderDisabledWithAppsMessage, 2)}, + }, + { + name: "DisabledQueryError", + queryErr: xerrors.New("boom"), + wantInfo: "oauth2 provider disabled", + wantWarn: []string{"oauth2 provider: list registered applications"}, + }, + } { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + ctx := testutil.Context(t, testutil.WaitShort) + rec := &logRecorder{} + db := dbmock.NewMockStore(gomock.NewController(t)) + // The query runs only while the provider is disabled. An + // unexpected call fails the test. + if !tc.enabled { + db.EXPECT().GetOAuth2ProviderApps(gomock.Any()).Return(tc.apps, tc.queryErr) + } + + cfg := codersdk.OAuth2ProviderConfig{Enable: serpent.Bool(tc.enabled)} + LogOAuth2ProviderState(ctx, slog.Make(rec), db, cfg) + + require.Equal(t, []string{tc.wantInfo}, rec.messages(slog.LevelInfo), + "exactly one info line per start") + require.Equal(t, tc.wantWarn, rec.messages(slog.LevelWarn)) + }) + } +}