diff --git a/cli/notifications.go b/cli/notifications.go new file mode 100644 index 0000000000000..055a4bfa65e3b --- /dev/null +++ b/cli/notifications.go @@ -0,0 +1,85 @@ +package cli + +import ( + "fmt" + + "golang.org/x/xerrors" + + "github.com/coder/serpent" + + "github.com/coder/coder/v2/codersdk" +) + +func (r *RootCmd) notifications() *serpent.Command { + cmd := &serpent.Command{ + Use: "notifications", + Short: "Manage Coder notifications", + Long: "Administrators can use these commands to change notification settings.\n" + FormatExamples( + Example{ + Description: "Pause Coder notifications. Administrators can temporarily stop notifiers from dispatching messages in case of the target outage (for example: unavailable SMTP server or Webhook not responding).", + Command: "coder notifications pause", + }, + Example{ + Description: "Resume Coder notifications", + Command: "coder notifications resume", + }, + ), + Aliases: []string{"notification"}, + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*serpent.Command{ + r.pauseNotifications(), + r.resumeNotifications(), + }, + } + return cmd +} + +func (r *RootCmd) pauseNotifications() *serpent.Command { + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "pause", + Short: "Pause notifications", + Middleware: serpent.Chain( + serpent.RequireNArgs(0), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ + NotifierPaused: true, + }) + if err != nil { + return xerrors.Errorf("unable to pause notifications: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stderr, "Notifications are now paused.") + return nil + }, + } + return cmd +} + +func (r *RootCmd) resumeNotifications() *serpent.Command { + client := new(codersdk.Client) + cmd := &serpent.Command{ + Use: "resume", + Short: "Resume notifications", + Middleware: serpent.Chain( + serpent.RequireNArgs(0), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ + NotifierPaused: false, + }) + if err != nil { + return xerrors.Errorf("unable to resume notifications: %w", err) + } + + _, _ = fmt.Fprintln(inv.Stderr, "Notifications are now resumed.") + return nil + }, + } + return cmd +} diff --git a/cli/notifications_test.go b/cli/notifications_test.go new file mode 100644 index 0000000000000..9ea4d7072e4c3 --- /dev/null +++ b/cli/notifications_test.go @@ -0,0 +1,102 @@ +package cli_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli/clitest" + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/testutil" +) + +func TestNotifications(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + command string + expectPaused bool + }{ + { + name: "PauseNotifications", + command: "pause", + expectPaused: true, + }, + { + name: "ResumeNotifications", + command: "resume", + expectPaused: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + // given + ownerClient, db := coderdtest.NewWithDatabase(t, nil) + _ = coderdtest.CreateFirstUser(t, ownerClient) + + // when + inv, root := clitest.New(t, "notifications", tt.command) + clitest.SetupConfig(t, ownerClient, root) + + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + require.NoError(t, err) + + // then + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + t.Cleanup(cancel) + settingsJSON, err := db.GetNotificationsSettings(ctx) + require.NoError(t, err) + + var settings codersdk.NotificationsSettings + err = json.Unmarshal([]byte(settingsJSON), &settings) + require.NoError(t, err) + require.Equal(t, tt.expectPaused, settings.NotifierPaused) + }) + } +} + +func TestPauseNotifications_RegularUser(t *testing.T) { + t.Parallel() + + // given + ownerClient, db := coderdtest.NewWithDatabase(t, nil) + owner := coderdtest.CreateFirstUser(t, ownerClient) + anotherClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + // when + inv, root := clitest.New(t, "notifications", "pause") + clitest.SetupConfig(t, anotherClient, root) + + var buf bytes.Buffer + inv.Stdout = &buf + err := inv.Run() + var sdkError *codersdk.Error + require.Error(t, err) + require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error") + assert.Equal(t, http.StatusForbidden, sdkError.StatusCode()) + assert.Contains(t, sdkError.Message, "Insufficient permissions to update notifications settings.") + + // then + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort) + t.Cleanup(cancel) + settingsJSON, err := db.GetNotificationsSettings(ctx) + require.NoError(t, err) + + var settings codersdk.NotificationsSettings + err = json.Unmarshal([]byte(settingsJSON), &settings) + require.NoError(t, err) + require.False(t, settings.NotifierPaused) // still running +} diff --git a/cli/root.go b/cli/root.go index 4e615eaa6e7af..579f3c4c29202 100644 --- a/cli/root.go +++ b/cli/root.go @@ -87,6 +87,8 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { r.login(), r.logout(), r.netcheck(), + r.notifications(), + r.organizations(), r.portForward(), r.publickey(), r.resetPassword(), @@ -95,7 +97,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { r.tokens(), r.users(), r.version(defaultVersionInfo), - r.organizations(), // Workspace Commands r.autoupdate(), @@ -120,11 +121,11 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { r.whoami(), // Hidden + r.expCmd(), r.gitssh(), + r.support(), r.vscodeSSH(), r.workspaceAgent(), - r.expCmd(), - r.support(), } } diff --git a/cli/testdata/coder_--help.golden b/cli/testdata/coder_--help.golden index a576797d8a48d..494ed7decb492 100644 --- a/cli/testdata/coder_--help.golden +++ b/cli/testdata/coder_--help.golden @@ -27,6 +27,7 @@ SUBCOMMANDS: login Authenticate with Coder deployment logout Unauthenticate your local session netcheck Print network debug information for DERP and STUN + notifications Manage Coder notifications open Open a workspace ping Ping a workspace port-forward Forward ports from a workspace to the local machine. For diff --git a/cli/testdata/coder_notifications_--help.golden b/cli/testdata/coder_notifications_--help.golden new file mode 100644 index 0000000000000..b54e98543da7b --- /dev/null +++ b/cli/testdata/coder_notifications_--help.golden @@ -0,0 +1,28 @@ +coder v0.0.0-devel + +USAGE: + coder notifications + + Manage Coder notifications + + Aliases: notification + + Administrators can use these commands to change notification settings. + - Pause Coder notifications. Administrators can temporarily stop notifiers + from + dispatching messages in case of the target outage (for example: unavailable + SMTP + server or Webhook not responding).: + + $ coder notifications pause + + - Resume Coder notifications: + + $ coder notifications resume + +SUBCOMMANDS: + pause Pause notifications + resume Resume notifications + +——— +Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_notifications_pause_--help.golden b/cli/testdata/coder_notifications_pause_--help.golden new file mode 100644 index 0000000000000..fc3f2621ad788 --- /dev/null +++ b/cli/testdata/coder_notifications_pause_--help.golden @@ -0,0 +1,9 @@ +coder v0.0.0-devel + +USAGE: + coder notifications pause + + Pause notifications + +——— +Run `coder --help` for a list of global options. diff --git a/cli/testdata/coder_notifications_resume_--help.golden b/cli/testdata/coder_notifications_resume_--help.golden new file mode 100644 index 0000000000000..ea69e1e789a2e --- /dev/null +++ b/cli/testdata/coder_notifications_resume_--help.golden @@ -0,0 +1,9 @@ +coder v0.0.0-devel + +USAGE: + coder notifications resume + + Resume notifications + +——— +Run `coder --help` for a list of global options. diff --git a/docs/cli.md b/docs/cli.md index f38bc0e3e133a..ab97ca9cc4d10 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -30,6 +30,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr | [login](./cli/login.md) | Authenticate with Coder deployment | | [logout](./cli/logout.md) | Unauthenticate your local session | | [netcheck](./cli/netcheck.md) | Print network debug information for DERP and STUN | +| [notifications](./cli/notifications.md) | Manage Coder notifications | | [port-forward](./cli/port-forward.md) | Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R". | | [publickey](./cli/publickey.md) | Output your Coder public key used for Git operations | | [reset-password](./cli/reset-password.md) | Directly connect to the database to reset a user's password | diff --git a/docs/cli/notifications.md b/docs/cli/notifications.md new file mode 100644 index 0000000000000..59e74b4324357 --- /dev/null +++ b/docs/cli/notifications.md @@ -0,0 +1,37 @@ + + +# notifications + +Manage Coder notifications + +Aliases: + +- notification + +## Usage + +```console +coder notifications +``` + +## Description + +```console +Administrators can use these commands to change notification settings. + - Pause Coder notifications. Administrators can temporarily stop notifiers from +dispatching messages in case of the target outage (for example: unavailable SMTP +server or Webhook not responding).: + + $ coder notifications pause + + - Resume Coder notifications: + + $ coder notifications resume +``` + +## Subcommands + +| Name | Purpose | +| ------------------------------------------------ | -------------------- | +| [pause](./notifications_pause.md) | Pause notifications | +| [resume](./notifications_resume.md) | Resume notifications | diff --git a/docs/cli/notifications_pause.md b/docs/cli/notifications_pause.md new file mode 100644 index 0000000000000..0cb2b101d474c --- /dev/null +++ b/docs/cli/notifications_pause.md @@ -0,0 +1,11 @@ + + +# notifications pause + +Pause notifications + +## Usage + +```console +coder notifications pause +``` diff --git a/docs/cli/notifications_resume.md b/docs/cli/notifications_resume.md new file mode 100644 index 0000000000000..a8dc17453a383 --- /dev/null +++ b/docs/cli/notifications_resume.md @@ -0,0 +1,11 @@ + + +# notifications resume + +Resume notifications + +## Usage + +```console +coder notifications resume +``` diff --git a/docs/manifest.json b/docs/manifest.json index dc887921b2b20..82dd73ada47c8 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -755,6 +755,21 @@ "description": "Print network debug information for DERP and STUN", "path": "cli/netcheck.md" }, + { + "title": "notifications", + "description": "Manage Coder notifications", + "path": "cli/notifications.md" + }, + { + "title": "notifications pause", + "description": "Pause notifications", + "path": "cli/notifications_pause.md" + }, + { + "title": "notifications resume", + "description": "Resume notifications", + "path": "cli/notifications_resume.md" + }, { "title": "open", "description": "Open a workspace",