Skip to content
Merged
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ openstatus monitors trigger 123
# Report an incident
openstatus status-report create --title "API degradation" --status investigating --page-id 1

# Report an incident with per-component impact
openstatus sr create --title "API down" --status investigating --message "Investigating" \
--page-id 1 --impact comp_api=major_outage --impact comp_db=degraded

# Update an incident and change impact
openstatus sr add-update sr_42 --status monitoring --message "Recovering" \
--impact comp_api=degraded,comp_db=operational

# Run synthetic tests from config
openstatus run
```
Expand Down
36 changes: 19 additions & 17 deletions docs/openstatus-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,16 +328,17 @@ $ openstatus [GLOBAL FLAGS] status-report create [COMMAND FLAGS] [ARGUMENTS...]

The following flags are supported:

| Name | Description | Type | Default value | Environment variables |
|-----------------------------|------------------------------------------------------------------|--------|:-------------:|:----------------------:|
| `--access-token="…"` (`-t`) | OpenStatus API Access Token | string | | `OPENSTATUS_API_TOKEN` |
| `--title="…"` | Title of the status report | string | | *none* |
| `--status="…"` | Initial status (investigating, identified, monitoring, resolved) | string | | *none* |
| `--message="…"` | Initial message describing the incident | string | | *none* |
| `--page-id="…"` | Status page ID to associate with this report | string | | *none* |
| `--component-ids="…"` | Comma-separated page component IDs | string | | *none* |
| `--notify` | Notify subscribers about this status report | bool | `false` | *none* |
| `--date="…"` | Date when the event occurred (RFC 3339 format, defaults to now) | string | | *none* |
| Name | Description | Type | Default value | Environment variables |
|-----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|:-------------:|:----------------------:|
| `--access-token="…"` (`-t`) | OpenStatus API Access Token | string | | `OPENSTATUS_API_TOKEN` |
| `--title="…"` | Title of the status report | string | | *none* |
| `--status="…"` | Initial status (investigating, identified, monitoring, resolved) | string | | *none* |
| `--message="…"` | Initial message describing the incident | string | | *none* |
| `--page-id="…"` | Status page ID to associate with this report | string | | *none* |
| `--component-ids="…"` | Comma-separated page component IDs (legacy; mutually exclusive with --impact) | string | | *none* |
| `--impact="…"` | Per-component impact, repeatable: --impact <component_id>=<level> (level: operational, degraded, partial_outage, major_outage). Mutually exclusive with --component-ids | string | | *none* |
| `--notify` | Notify subscribers about this status report | bool | `false` | *none* |
| `--date="…"` | Date when the event occurred (RFC 3339 format, defaults to now) | string | | *none* |

### `status-report update` subcommand

Expand Down Expand Up @@ -393,13 +394,14 @@ $ openstatus [GLOBAL FLAGS] status-report add-update [COMMAND FLAGS] [ARGUMENTS.

The following flags are supported:

| Name | Description | Type | Default value | Environment variables |
|-----------------------------|--------------------------------------------------------------|--------|:-------------:|:----------------------:|
| `--access-token="…"` (`-t`) | OpenStatus API Access Token | string | | `OPENSTATUS_API_TOKEN` |
| `--status="…"` | New status (investigating, identified, monitoring, resolved) | string | | *none* |
| `--message="…"` | Message describing what changed | string | | *none* |
| `--date="…"` | Date for the update (RFC 3339 format, defaults to now) | string | | *none* |
| `--notify` | Notify subscribers about this update | bool | `false` | *none* |
| Name | Description | Type | Default value | Environment variables |
|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|:-------------:|:----------------------:|
| `--access-token="…"` (`-t`) | OpenStatus API Access Token | string | | `OPENSTATUS_API_TOKEN` |
| `--status="…"` | New status (investigating, identified, monitoring, resolved) | string | | *none* |
| `--message="…"` | Message describing what changed | string | | *none* |
| `--date="…"` | Date for the update (RFC 3339 format, defaults to now) | string | | *none* |
| `--impact="…"` | Per-component impact, repeatable: --impact <component_id>=<level> (level: operational, degraded, partial_outage, major_outage). Can also add new affected components | string | | *none* |
| `--notify` | Notify subscribers about this update | bool | `false` | *none* |

### `maintenance` command (aliases: `mt`)

Expand Down
31 changes: 28 additions & 3 deletions docs/openstatus.1
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ The following flags are supported:
.PP
.TS
tab(@);
lw(14.3n) lw(32.5n) lw(3.9n) cw(7.4n) cw(11.8n).
lw(8.3n) lw(48.3n) lw(2.3n) cw(4.3n) cw(6.9n).
T{
Name
T}@T{
Expand Down Expand Up @@ -926,7 +926,20 @@ T}
T{
\f[CR]\-\-component\-ids=\(dq\&...\(dq\f[R]
T}@T{
Comma\-separated page component IDs
Comma\-separated page component IDs (legacy; mutually exclusive with
\(enimpact)
T}@T{
string
T}@T{
T}@T{
\f[I]none\f[R]
T}
T{
\f[CR]\-\-impact=\(dq\&...\(dq\f[R]
T}@T{
Per\-component impact, repeatable: \(enimpact = (level: operational,
degraded, partial_outage, major_outage).
Mutually exclusive with \(encomponent\-ids
T}@T{
string
T}@T{
Expand Down Expand Up @@ -1088,7 +1101,7 @@ The following flags are supported:
.PP
.TS
tab(@);
lw(14.7n) lw(31.4n) lw(4.1n) cw(7.6n) cw(12.2n).
lw(8.4n) lw(48.0n) lw(2.3n) cw(4.3n) cw(6.9n).
T{
Name
T}@T{
Expand Down Expand Up @@ -1142,6 +1155,18 @@ T}@T{
\f[I]none\f[R]
T}
T{
\f[CR]\-\-impact=\(dq\&...\(dq\f[R]
T}@T{
Per\-component impact, repeatable: \(enimpact = (level: operational,
degraded, partial_outage, major_outage).
Can also add new affected components
T}@T{
string
T}@T{
T}@T{
\f[I]none\f[R]
T}
T{
\f[CR]\-\-notify\f[R]
T}@T{
Notify subscribers about this update
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require github.com/urfave/cli/v3 v3.9.0 // direct

require (
buf.build/gen/go/openstatus/api/connectrpc/gosimple v1.19.2-20260512200453-7d7b7047611f.1
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260512200453-7d7b7047611f.1
connectrpc.com/connect v1.19.2
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260611194648-df8af73df8e6.1
connectrpc.com/connect v1.20.0
github.com/briandowns/spinner v1.23.2
github.com/charmbracelet/huh v1.0.0
github.com/fatih/color v1.19.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ buf.build/gen/go/gnostic/gnostic/protocolbuffers/go v1.36.11-20230414000709-087b
buf.build/gen/go/gnostic/gnostic/protocolbuffers/go v1.36.11-20230414000709-087bc8072ce4.1/go.mod h1:/t9AeRQQp2iNkiGHDLfHLW3SzNpYpNPGRZ+Ih8+SOUs=
buf.build/gen/go/openstatus/api/connectrpc/gosimple v1.19.2-20260512200453-7d7b7047611f.1 h1:NCdo1Xp+waRtzNvXDE9Io5gqzvvMIn3zItVFsxxmBc0=
buf.build/gen/go/openstatus/api/connectrpc/gosimple v1.19.2-20260512200453-7d7b7047611f.1/go.mod h1:pUXTguUtnl60N+cwJnHkPmsdnt0WniHDO9V37KslI4U=
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260512200453-7d7b7047611f.1 h1:l4scOgYz5A41nIYE/WglmiGdwlZNWruvuyahqhfAKSs=
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260512200453-7d7b7047611f.1/go.mod h1:GRsD/In1AV3/RC92zWVAjBpj57hJIQm7Rph0rLOYYPg=
connectrpc.com/connect v1.19.2 h1:McQ83FGdzL+t60peksi0gXC7MQ/iLKgLduAnThbM0mo=
connectrpc.com/connect v1.19.2/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260611194648-df8af73df8e6.1 h1:CCLwLRzDx0s4FfXfbVUy5SBB5m4nGfV9SYTrScUvHqU=
buf.build/gen/go/openstatus/api/protocolbuffers/go v1.36.11-20260611194648-df8af73df8e6.1/go.mod h1:GRsD/In1AV3/RC92zWVAjBpj57hJIQm7Rph0rLOYYPg=
connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ=
connectrpc.com/connect v1.20.0/go.mod h1:A2ygJrukXwWy32vkCAAHNVguZrqZ+jeZ9rGRnGR4dN4=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Get started:
openstatus run Run synthetic tests

https://docs.openstatus.dev | https://github.com/openstatusHQ/cli/issues/new`,
Version: "v1.2.0",
Version: "v1.2.1",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "json",
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ func Test_NewApp(t *testing.T) {
t.Errorf("Expected app name 'openstatus', got %s", app.Name)
}

if app.Version != "v1.2.0" {
t.Errorf("Expected version 'v1.2.0', got %s", app.Version)
if app.Version != "v1.2.1" {
t.Errorf("Expected version 'v1.2.1', got %s", app.Version)
}

if !app.Suggest {
Expand Down
54 changes: 54 additions & 0 deletions internal/statusreport/impact_flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package statusreport

import (
"fmt"
"strings"

status_reportv1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/status_report/v1"
)

func parseImpactsFlag(raw []string) ([]*status_reportv1.ComponentImpact, error) {
if len(raw) == 0 {
return nil, nil
}

var out []*status_reportv1.ComponentImpact
seen := make(map[string]struct{})

for _, entry := range raw {
entry = strings.TrimSpace(entry)
if entry == "" {
return nil, fmt.Errorf("invalid --impact value: empty entry")
}

key, value, ok := strings.Cut(entry, "=")
if !ok {
return nil, fmt.Errorf("invalid --impact value %q: expected <component_id>=<level>", entry)
}
key = strings.TrimSpace(key)
value = strings.TrimSpace(value)
if key == "" {
return nil, fmt.Errorf("invalid --impact value %q: empty component id", entry)
}
if value == "" {
return nil, fmt.Errorf("invalid --impact value %q: empty impact level", entry)
}

level, err := impactToSDK(value)
if err != nil {
return nil, fmt.Errorf("invalid --impact value %q: %w", entry, err)
}

if _, dup := seen[key]; dup {
return nil, fmt.Errorf("invalid --impact: component %q specified more than once", key)
}
seen[key] = struct{}{}

ci := &status_reportv1.ComponentImpact{}
ci.SetPageComponentId(key)
ci.SetImpact(level)
out = append(out, ci)
}

return out, nil
}
85 changes: 85 additions & 0 deletions internal/statusreport/statusreport.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,91 @@ func statusColor(s string) string {
}
}

func impactToSDK(s string) (status_reportv1.PageComponentImpact, error) {
switch s {
case "operational":
return status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_OPERATIONAL, nil
case "degraded", "degraded_performance":
return status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_DEGRADED_PERFORMANCE, nil
case "partial_outage":
return status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_PARTIAL_OUTAGE, nil
case "major_outage":
return status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_MAJOR_OUTAGE, nil
default:
return status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_UNSPECIFIED,
fmt.Errorf("invalid impact %q: must be one of operational, degraded, partial_outage, major_outage", s)
}
}

func impactToString(i status_reportv1.PageComponentImpact) string {
switch i {
case status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_OPERATIONAL:
return "operational"
case status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_DEGRADED_PERFORMANCE:
return "degraded"
case status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_PARTIAL_OUTAGE:
return "partial_outage"
case status_reportv1.PageComponentImpact_PAGE_COMPONENT_IMPACT_MAJOR_OUTAGE:
return "major_outage"
default:
return ""
}
}

func impactColor(s string) string {
switch s {
case "operational":
return color.GreenString(s)
case "degraded":
return color.YellowString(s)
case "partial_outage":
return color.MagentaString(s)
case "major_outage":
return color.RedString(s)
default:
return s
}
}

func currentImpacts(updates []*status_reportv1.StatusReportUpdate) map[string]status_reportv1.PageComponentImpact {
if len(updates) == 0 {
return nil
}
out := make(map[string]status_reportv1.PageComponentImpact)
for _, u := range updates {
for _, ci := range u.GetComponentImpacts() {
out[ci.GetPageComponentId()] = ci.GetImpact()
}
}
return out
}

func affectedOrder(report *status_reportv1.StatusReport) []string {
if report == nil {
return nil
}
seen := make(map[string]struct{})
out := make([]string, 0, len(report.GetPageComponentIds()))
for _, id := range report.GetPageComponentIds() {
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
for _, u := range report.GetUpdates() {
for _, ci := range u.GetComponentImpacts() {
id := ci.GetPageComponentId()
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
}
return out
}

func StatusReportCmd() *cli.Command {
return &cli.Command{
Name: "status-report",
Expand Down
Loading
Loading