Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/cli. OpenStatus CLI www.openstatus.dev
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551package monitors
import ( "encoding/json" "fmt" "net/http" "strings"
monitorv1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/monitor/v1" "buf.build/gen/go/openstatus/api/connectrpc/gosimple/openstatus/monitor/v1/monitorv1connect" "connectrpc.com/connect" "github.com/openstatusHQ/cli/internal/api" "github.com/openstatusHQ/cli/internal/config" "github.com/urfave/cli/v3")
func NewMonitorClient(apiKey string) monitorv1connect.MonitorServiceClient { return monitorv1connect.NewMonitorServiceClient( api.DefaultHTTPClient, api.ConnectBaseURL, connect.WithInterceptors(api.NewAuthInterceptor(apiKey)), connect.WithProtoJSON(), )}
func NewMonitorClientWithHTTPClient(httpClient *http.Client, apiKey string) monitorv1connect.MonitorServiceClient { return monitorv1connect.NewMonitorServiceClient( httpClient, api.ConnectBaseURL, connect.WithInterceptors(api.NewAuthInterceptor(apiKey)), connect.WithProtoJSON(), )}
// Helper functions to convert SDK types to CLI display types
// periodicityToString converts SDK Periodicity enum to stringfunc periodicityToString(p monitorv1.Periodicity) string { switch p { case monitorv1.Periodicity_PERIODICITY_30S: return "30s" case monitorv1.Periodicity_PERIODICITY_1M: return "1m" case monitorv1.Periodicity_PERIODICITY_5M: return "5m" case monitorv1.Periodicity_PERIODICITY_10M: return "10m" case monitorv1.Periodicity_PERIODICITY_30M: return "30m" case monitorv1.Periodicity_PERIODICITY_1H: return "1h" default: return "unknown" }}
// httpMethodToString converts SDK HTTPMethod enum to stringfunc httpMethodToString(m monitorv1.HTTPMethod) string { switch m { case monitorv1.HTTPMethod_HTTP_METHOD_GET: return "GET" case monitorv1.HTTPMethod_HTTP_METHOD_POST: return "POST" case monitorv1.HTTPMethod_HTTP_METHOD_HEAD: return "HEAD" case monitorv1.HTTPMethod_HTTP_METHOD_PUT: return "PUT" case monitorv1.HTTPMethod_HTTP_METHOD_PATCH: return "PATCH" case monitorv1.HTTPMethod_HTTP_METHOD_DELETE: return "DELETE" case monitorv1.HTTPMethod_HTTP_METHOD_TRACE: return "TRACE" case monitorv1.HTTPMethod_HTTP_METHOD_CONNECT: return "CONNECT" case monitorv1.HTTPMethod_HTTP_METHOD_OPTIONS: return "OPTIONS" default: return "" }}
// regionToString converts SDK Region enum to stringfunc regionToString(r monitorv1.Region) string { switch r { // Fly.io regions case monitorv1.Region_REGION_FLY_AMS: return "ams" case monitorv1.Region_REGION_FLY_ARN: return "arn" case monitorv1.Region_REGION_FLY_BOM: return "bom" case monitorv1.Region_REGION_FLY_CDG: return "cdg" case monitorv1.Region_REGION_FLY_DFW: return "dfw" case monitorv1.Region_REGION_FLY_EWR: return "ewr" case monitorv1.Region_REGION_FLY_FRA: return "fra" case monitorv1.Region_REGION_FLY_GRU: return "gru" case monitorv1.Region_REGION_FLY_IAD: return "iad" case monitorv1.Region_REGION_FLY_JNB: return "jnb" case monitorv1.Region_REGION_FLY_LAX: return "lax" case monitorv1.Region_REGION_FLY_LHR: return "lhr" case monitorv1.Region_REGION_FLY_NRT: return "nrt" case monitorv1.Region_REGION_FLY_ORD: return "ord" case monitorv1.Region_REGION_FLY_SJC: return "sjc" case monitorv1.Region_REGION_FLY_SIN: return "sin" case monitorv1.Region_REGION_FLY_SYD: return "syd" case monitorv1.Region_REGION_FLY_YYZ: return "yyz" // Koyeb regions case monitorv1.Region_REGION_KOYEB_FRA: return string(config.KoyebFra) case monitorv1.Region_REGION_KOYEB_PAR: return string(config.KoyebPar) case monitorv1.Region_REGION_KOYEB_SFO: return string(config.KoyebSfo) case monitorv1.Region_REGION_KOYEB_SIN: return string(config.KoyebSin) case monitorv1.Region_REGION_KOYEB_TYO: return string(config.KoyebTyo) case monitorv1.Region_REGION_KOYEB_WAS: return string(config.KoyebWas) // Railway regions case monitorv1.Region_REGION_RAILWAY_US_WEST2: return string(config.RailwayUsWest2) case monitorv1.Region_REGION_RAILWAY_US_EAST4: return string(config.RailwayUsEast4) case monitorv1.Region_REGION_RAILWAY_EUROPE_WEST4: return string(config.RailwayEuropeWest4) case monitorv1.Region_REGION_RAILWAY_ASIA_SOUTHEAST1: return string(config.RailwayAsiaSoutheast1) default: return r.String() }}
// regionsToStrings converts a slice of SDK Region enums to stringsfunc regionsToStrings(regions []monitorv1.Region) []string { result := make([]string, len(regions)) for i, r := range regions { result[i] = regionToString(r) } return result}
// groupRegionsByProvider categorizes regions into Fly.io, Koyeb, and Railway groupsfunc groupRegionsByProvider(regions []monitorv1.Region) map[string][]string { groups := map[string][]string{ "Fly.io": {}, "Koyeb": {}, "Railway": {}, } for _, r := range regions { code := regionToString(r) switch { case strings.HasPrefix(r.String(), "REGION_FLY_"): groups["Fly.io"] = append(groups["Fly.io"], code) case strings.HasPrefix(r.String(), "REGION_KOYEB_"): groups["Koyeb"] = append(groups["Koyeb"], code) case strings.HasPrefix(r.String(), "REGION_RAILWAY_"): groups["Railway"] = append(groups["Railway"], code) } } return groups}
// Inverse converter functions (config types → SDK types)
// stringToPeriodicity converts config.Frequency to SDK Periodicityfunc stringToPeriodicity(f config.Frequency) monitorv1.Periodicity { switch f { case config.The30S: return monitorv1.Periodicity_PERIODICITY_30S case config.The1M: return monitorv1.Periodicity_PERIODICITY_1M case config.The5M: return monitorv1.Periodicity_PERIODICITY_5M case config.The10M: return monitorv1.Periodicity_PERIODICITY_10M case config.The30M: return monitorv1.Periodicity_PERIODICITY_30M case config.The1H: return monitorv1.Periodicity_PERIODICITY_1H default: return monitorv1.Periodicity_PERIODICITY_10M }}
// stringToHTTPMethod converts config.Method to SDK HTTPMethodfunc stringToHTTPMethod(m config.Method) monitorv1.HTTPMethod { switch m { case config.Get: return monitorv1.HTTPMethod_HTTP_METHOD_GET case config.Post: return monitorv1.HTTPMethod_HTTP_METHOD_POST case config.Put: return monitorv1.HTTPMethod_HTTP_METHOD_PUT case config.Patch: return monitorv1.HTTPMethod_HTTP_METHOD_PATCH case config.Delete: return monitorv1.HTTPMethod_HTTP_METHOD_DELETE case config.Head: return monitorv1.HTTPMethod_HTTP_METHOD_HEAD case config.Options: return monitorv1.HTTPMethod_HTTP_METHOD_OPTIONS default: return monitorv1.HTTPMethod_HTTP_METHOD_GET }}
// stringToRegion converts config.Region to SDK Regionfunc stringToRegion(r config.Region) monitorv1.Region { switch r { case config.Ams: return monitorv1.Region_REGION_FLY_AMS case config.Arn: return monitorv1.Region_REGION_FLY_ARN case config.BOM: return monitorv1.Region_REGION_FLY_BOM case config.Cdg: return monitorv1.Region_REGION_FLY_CDG case config.Dfw: return monitorv1.Region_REGION_FLY_DFW case config.Ewr: return monitorv1.Region_REGION_FLY_EWR case config.Fra: return monitorv1.Region_REGION_FLY_FRA case config.Gru: return monitorv1.Region_REGION_FLY_GRU case config.Iad: return monitorv1.Region_REGION_FLY_IAD case config.Jnb: return monitorv1.Region_REGION_FLY_JNB case config.Lax: return monitorv1.Region_REGION_FLY_LAX case config.Lhr: return monitorv1.Region_REGION_FLY_LHR case config.Nrt: return monitorv1.Region_REGION_FLY_NRT case config.Ord: return monitorv1.Region_REGION_FLY_ORD case config.Sin: return monitorv1.Region_REGION_FLY_SIN case config.Sjc: return monitorv1.Region_REGION_FLY_SJC case config.Syd: return monitorv1.Region_REGION_FLY_SYD case config.Yyz: return monitorv1.Region_REGION_FLY_YYZ // Koyeb regions case config.KoyebFra: return monitorv1.Region_REGION_KOYEB_FRA case config.KoyebPar: return monitorv1.Region_REGION_KOYEB_PAR case config.KoyebSfo: return monitorv1.Region_REGION_KOYEB_SFO case config.KoyebSin: return monitorv1.Region_REGION_KOYEB_SIN case config.KoyebTyo: return monitorv1.Region_REGION_KOYEB_TYO case config.KoyebWas: return monitorv1.Region_REGION_KOYEB_WAS // Railway regions case config.RailwayUsWest2: return monitorv1.Region_REGION_RAILWAY_US_WEST2 case config.RailwayUsEast4: return monitorv1.Region_REGION_RAILWAY_US_EAST4 case config.RailwayEuropeWest4: return monitorv1.Region_REGION_RAILWAY_EUROPE_WEST4 case config.RailwayAsiaSoutheast1: return monitorv1.Region_REGION_RAILWAY_ASIA_SOUTHEAST1 default: return monitorv1.Region_REGION_UNSPECIFIED }}
// stringsToRegions converts []config.Region to []monitorv1.Regionfunc stringsToRegions(regions []config.Region) []monitorv1.Region { result := make([]monitorv1.Region, len(regions)) for i, r := range regions { result[i] = stringToRegion(r) } return result}
// configCompareToNumberComparator converts config.Compare to NumberComparatorfunc configCompareToNumberComparator(c config.Compare) monitorv1.NumberComparator { switch c { case config.Eq: return monitorv1.NumberComparator_NUMBER_COMPARATOR_EQUAL case config.NotEq: return monitorv1.NumberComparator_NUMBER_COMPARATOR_NOT_EQUAL case config.Gt: return monitorv1.NumberComparator_NUMBER_COMPARATOR_GREATER_THAN case config.Gte: return monitorv1.NumberComparator_NUMBER_COMPARATOR_GREATER_THAN_OR_EQUAL case config.Lt: return monitorv1.NumberComparator_NUMBER_COMPARATOR_LESS_THAN case config.LTE: return monitorv1.NumberComparator_NUMBER_COMPARATOR_LESS_THAN_OR_EQUAL default: return monitorv1.NumberComparator_NUMBER_COMPARATOR_EQUAL }}
// configCompareToStringComparator converts config.Compare to StringComparatorfunc configCompareToStringComparator(c config.Compare) monitorv1.StringComparator { switch c { case config.Eq: return monitorv1.StringComparator_STRING_COMPARATOR_EQUAL case config.NotEq: return monitorv1.StringComparator_STRING_COMPARATOR_NOT_EQUAL case config.Contains: return monitorv1.StringComparator_STRING_COMPARATOR_CONTAINS case config.NotContains: return monitorv1.StringComparator_STRING_COMPARATOR_NOT_CONTAINS case config.Empty: return monitorv1.StringComparator_STRING_COMPARATOR_EMPTY case config.NotEmpty: return monitorv1.StringComparator_STRING_COMPARATOR_NOT_EMPTY case config.Gt: return monitorv1.StringComparator_STRING_COMPARATOR_GREATER_THAN case config.Gte: return monitorv1.StringComparator_STRING_COMPARATOR_GREATER_THAN_OR_EQUAL case config.Lt: return monitorv1.StringComparator_STRING_COMPARATOR_LESS_THAN case config.LTE: return monitorv1.StringComparator_STRING_COMPARATOR_LESS_THAN_OR_EQUAL default: return monitorv1.StringComparator_STRING_COMPARATOR_EQUAL }}
// Builder functions (config.Monitor → SDK monitor types)
// configToHTTPMonitor converts config.Monitor to SDK HTTPMonitorfunc configToHTTPMonitor(m config.Monitor) *monitorv1.HTTPMonitor { // Convert headers headers := make([]*monitorv1.Headers, 0, len(m.Request.Headers)) for k, v := range m.Request.Headers { headers = append(headers, &monitorv1.Headers{Key: k, Value: v}) }
// Convert assertions to separate types var statusCodeAssertions []*monitorv1.StatusCodeAssertion var bodyAssertions []*monitorv1.BodyAssertion var headerAssertions []*monitorv1.HeaderAssertion
for _, a := range m.Assertions { switch a.Kind { case config.StatusCode: var target int64 switch v := a.Target.(type) { case int: target = int64(v) case int64: target = v case float64: target = int64(v) } statusCodeAssertions = append(statusCodeAssertions, &monitorv1.StatusCodeAssertion{ Target: target, Comparator: configCompareToNumberComparator(a.Compare), }) case config.TextBody: target, _ := a.Target.(string) bodyAssertions = append(bodyAssertions, &monitorv1.BodyAssertion{ Target: target, Comparator: configCompareToStringComparator(a.Compare), }) case config.Header: target, _ := a.Target.(string) headerAssertions = append(headerAssertions, &monitorv1.HeaderAssertion{ Key: a.Key, Target: target, Comparator: configCompareToStringComparator(a.Compare), }) } }
monitor := &monitorv1.HTTPMonitor{ Name: m.Name, Description: m.Description, Url: m.Request.URL, Method: stringToHTTPMethod(m.Request.Method), Body: m.Request.Body, Periodicity: stringToPeriodicity(m.Frequency), Active: m.Active, Public: m.Public, Regions: stringsToRegions(m.Regions), Timeout: m.Timeout, Retry: m.Retry, Headers: headers, StatusCodeAssertions: statusCodeAssertions, BodyAssertions: bodyAssertions, HeaderAssertions: headerAssertions, }
if m.DegradedAfter > 0 { monitor.DegradedAt = &m.DegradedAfter }
if m.Request.FollowRedirects != nil { monitor.FollowRedirects = m.Request.FollowRedirects }
return monitor}
// configToTCPMonitor converts config.Monitor to SDK TCPMonitorfunc configToTCPMonitor(m config.Monitor) (*monitorv1.TCPMonitor, error) { if m.Request.Host == "" { return nil, fmt.Errorf("TCP monitor %q: host is required", m.Name) } if m.Request.Port <= 0 || m.Request.Port > 65535 { return nil, fmt.Errorf("TCP monitor %q: port must be between 1 and 65535, got %d", m.Name, m.Request.Port) }
monitor := &monitorv1.TCPMonitor{ Name: m.Name, Description: m.Description, Uri: fmt.Sprintf("%s:%d", m.Request.Host, m.Request.Port), Periodicity: stringToPeriodicity(m.Frequency), Active: m.Active, Public: m.Public, Regions: stringsToRegions(m.Regions), Timeout: m.Timeout, Retry: m.Retry, }
if m.DegradedAfter > 0 { monitor.DegradedAt = &m.DegradedAfter }
return monitor, nil}
type Monitor struct { ID int `json:"id"` Name string `json:"name"` URL string `json:"url"` Periodicity string `json:"periodicity"` Description string `json:"description"` Method string `json:"method"` Regions []string `json:"regions"` Active bool `json:"active"` Public bool `json:"public"` Timeout int `json:"timeout"` DegradedAfter int `json:"degraded_after,omitempty"` Body string `json:"body"` Headers []Header `json:"headers,omitempty"` Assertions []Assertion `json:"assertions,omitempty"` Retry int `json:"retry"` JobType string `json:"jobType"`}
type Header struct { Key string `json:"key"` Value string `json:"value"`}
type Assertion struct { Type string `json:"type"` Compare string `json:"compare"` Key string `json:"key"` Target any `json:"target"`}
type Timing struct { DnsStart int64 `json:"dnsStart"` DnsDone int64 `json:"dnsDone"` ConnectStart int64 `json:"connectStart"` ConnectDone int64 `json:"connectDone"` TlsHandshakeStart int64 `json:"tlsHandshakeStart"` TlsHandshakeDone int64 `json:"tlsHandshakeDone"` FirstByteStart int64 `json:"firstByteStart"` FirstByteDone int64 `json:"firstByteDone"` TransferStart int64 `json:"transferStart"` TransferDone int64 `json:"transferDone"`}
type MonitorTriggerResponse struct { ResultId int `json:"resultId"`}
type TriggerResultResponse struct { MonitorId string `json:"monitorId"` Url string `json:"url"` Error bool `json:"error"` Region string `json:"region"` Timestamp int `json:"timestamp"` Latency int `json:"latency"` StatusCode int `json:"statusCode"` Timing Timing `json:"timing"`}
type RunResult struct { JobType string `json:"jobType"` Region string `json:"region"` Message json.RawMessage Timestamp int64 `json:"timestamp"` Latency int64 `json:"latency"`}
type HTTPRunResult struct { Headers map[string]string `json:"headers,omitempty"` Body string `json:"body,omitempty"` Error string `json:"error,omitempty"` Status int `json:"status,omitempty"` Timing Timing `json:"timing"`}
type TCPRunResult struct { ErrorMessage string `json:"errorMessage"` Timing struct { TCPStart int64 `json:"tcpStart"` TCPDone int64 `json:"tcpDone"` } `json:"timing"`}
func MonitorsCmd() *cli.Command { monitorsCmd := cli.Command{ Name: "monitors", Usage: "Manage your monitors", Aliases: []string{"m"}, Commands: []*cli.Command{ GetMonitorsApplyCmd(), GetMonitorCreateCmd(), GetMonitorDeleteCmd(), GetMonitorImportCmd(), GetMonitorInfoCmd(), GetMonitorsListCmd(), GetMonitorsTriggerCmd(), }, } return &monitorsCmd}