Skip to content

Commit afd3629

Browse files
feat(status-pages): add third-party outage signals from updog.ai
Add `pup status-pages third-party` subcommand that fetches and displays third-party service outage signals from updog.ai, giving users visibility into external service health that may affect their Datadog integrations. - Fetch outage data from https://updog.ai/data/third-party-outages.json - Support --provider flag for case-insensitive provider filtering - Support --active flag to show only providers with unresolved outages - No Datadog authentication required (public third-party data) - Full test coverage with httptest server mocking Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d22f8cb commit afd3629

3 files changed

Lines changed: 550 additions & 1 deletion

File tree

cmd/status_pages_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ func TestStatusPagesCmd(t *testing.T) {
2929
}
3030

3131
func TestStatusPagesCmd_Subcommands(t *testing.T) {
32-
expectedCommands := []string{"pages", "components", "degradations"}
32+
expectedCommands := []string{"pages", "components", "degradations", "third-party"}
3333
commands := statusPagesCmd.Commands()
3434
commandMap := make(map[string]bool)
3535
for _, cmd := range commands {

cmd/status_pages_third_party.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2024-present Datadog, Inc.
5+
6+
package cmd
7+
8+
import (
9+
"encoding/json"
10+
"fmt"
11+
"io"
12+
"net/http"
13+
"strings"
14+
"time"
15+
16+
"github.com/spf13/cobra"
17+
)
18+
19+
const thirdPartyOutagesURL = "https://updog.ai/data/third-party-outages.json"
20+
21+
// httpClient is the HTTP client used for third-party API calls (injectable for testing).
22+
var httpClient = &http.Client{Timeout: 30 * time.Second}
23+
24+
// Third-party outages response types
25+
26+
type thirdPartyOutagesResponse struct {
27+
Data thirdPartyOutagesData `json:"data"`
28+
}
29+
30+
type thirdPartyOutagesData struct {
31+
Attributes thirdPartyOutagesAttributes `json:"attributes"`
32+
ID string `json:"id"`
33+
Type string `json:"type"`
34+
}
35+
36+
type thirdPartyOutagesAttributes struct {
37+
ProviderData []thirdPartyProvider `json:"provider_data"`
38+
}
39+
40+
type thirdPartyProvider struct {
41+
ProviderName string `json:"provider_name"`
42+
ProviderService string `json:"provider_service,omitempty"`
43+
DisplayName string `json:"display_name"`
44+
IntegrationID string `json:"integration_id"`
45+
StatusURL string `json:"status_url"`
46+
MonitoringStartDate int64 `json:"monitoring_start_date"`
47+
MonitoredAPIPatterns []string `json:"monitored_api_patterns"`
48+
Outages []thirdPartyOutage `json:"outages"`
49+
}
50+
51+
type thirdPartyOutage struct {
52+
Start int64 `json:"start"`
53+
End int64 `json:"end"`
54+
Status string `json:"status"`
55+
ImpactedRegion string `json:"impacted_region,omitempty"`
56+
}
57+
58+
var (
59+
thirdPartyProviderFilter string
60+
thirdPartyActiveOnly bool
61+
)
62+
63+
var statusPagesThirdPartyCmd = &cobra.Command{
64+
Use: "third-party",
65+
Short: "View third-party service outage signals",
66+
Long: `View third-party service outage signals from updog.ai.
67+
68+
Shows current and historical outage data for third-party services that may
69+
affect your Datadog integrations, including cloud providers, SaaS platforms,
70+
and other infrastructure dependencies.
71+
72+
EXAMPLES:
73+
# List all third-party outage signals
74+
pup status-pages third-party
75+
76+
# Filter by provider
77+
pup status-pages third-party --provider=aws
78+
79+
# Show only active outages
80+
pup status-pages third-party --active
81+
82+
AUTHENTICATION:
83+
This command does not require Datadog authentication.
84+
Data is sourced from https://updog.ai.`,
85+
RunE: runStatusPagesThirdParty,
86+
}
87+
88+
func init() {
89+
statusPagesThirdPartyCmd.Flags().StringVar(&thirdPartyProviderFilter, "provider", "", "Filter by provider name (case-insensitive substring match)")
90+
statusPagesThirdPartyCmd.Flags().BoolVar(&thirdPartyActiveOnly, "active", false, "Show only providers with active (unresolved) outages")
91+
statusPagesCmd.AddCommand(statusPagesThirdPartyCmd)
92+
}
93+
94+
func fetchThirdPartyOutages() (*thirdPartyOutagesResponse, error) {
95+
resp, err := httpClient.Get(thirdPartyOutagesURL)
96+
if err != nil {
97+
return nil, fmt.Errorf("failed to fetch third-party outages: %w", err)
98+
}
99+
defer resp.Body.Close()
100+
101+
if resp.StatusCode != http.StatusOK {
102+
return nil, fmt.Errorf("unexpected status from updog.ai: %d", resp.StatusCode)
103+
}
104+
105+
body, err := io.ReadAll(resp.Body)
106+
if err != nil {
107+
return nil, fmt.Errorf("failed to read response: %w", err)
108+
}
109+
110+
var result thirdPartyOutagesResponse
111+
if err := json.Unmarshal(body, &result); err != nil {
112+
return nil, fmt.Errorf("failed to parse response: %w", err)
113+
}
114+
115+
return &result, nil
116+
}
117+
118+
func filterProviders(providers []thirdPartyProvider, nameFilter string, activeOnly bool) []thirdPartyProvider {
119+
if nameFilter == "" && !activeOnly {
120+
return providers
121+
}
122+
123+
filter := strings.ToLower(nameFilter)
124+
var filtered []thirdPartyProvider
125+
for _, p := range providers {
126+
if nameFilter != "" {
127+
name := strings.ToLower(p.ProviderName)
128+
display := strings.ToLower(p.DisplayName)
129+
if !strings.Contains(name, filter) && !strings.Contains(display, filter) {
130+
continue
131+
}
132+
}
133+
if activeOnly {
134+
hasActive := false
135+
for _, o := range p.Outages {
136+
if o.Status != "resolved" {
137+
hasActive = true
138+
break
139+
}
140+
}
141+
if !hasActive {
142+
continue
143+
}
144+
}
145+
filtered = append(filtered, p)
146+
}
147+
return filtered
148+
}
149+
150+
func runStatusPagesThirdParty(cmd *cobra.Command, args []string) error {
151+
data, err := fetchThirdPartyOutages()
152+
if err != nil {
153+
return err
154+
}
155+
156+
providers := filterProviders(data.Data.Attributes.ProviderData, thirdPartyProviderFilter, thirdPartyActiveOnly)
157+
158+
return formatAndPrint(providers, nil)
159+
}

0 commit comments

Comments
 (0)