Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/cli. OpenStatus CLI www.openstatus.dev
cli cli-app synthetic-monitoring
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847package terraform
import ( "fmt" "os" "sort"
monitorv1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/monitor/v1" notificationv1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/notification/v1" status_pagev1 "buf.build/gen/go/openstatus/api/protocolbuffers/go/openstatus/status_page/v1" "github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclwrite" "github.com/zclconf/go-cty/cty"
"github.com/openstatusHQ/cli/internal/api")
type resourceRef struct { ResourceType string Name string}
type Generator struct { data *WorkspaceData registry *NameRegistry monitorRefs map[string]resourceRef pageRefs map[string]resourceRef groupRefs map[string]resourceRef
httpMonitorNames map[string]string tcpMonitorNames map[string]string dnsMonitorNames map[string]string icmpMonitorNames map[string]string notifNames map[string]string pageNames map[string]string componentNames map[string]string groupNames map[string]string privateLocationNames map[string]string skippedNotifications map[string]bool}
func NewGenerator(data *WorkspaceData) *Generator { g := &Generator{ data: data, registry: NewNameRegistry(), monitorRefs: make(map[string]resourceRef), pageRefs: make(map[string]resourceRef), groupRefs: make(map[string]resourceRef), httpMonitorNames: make(map[string]string), tcpMonitorNames: make(map[string]string), dnsMonitorNames: make(map[string]string), icmpMonitorNames: make(map[string]string), notifNames: make(map[string]string), pageNames: make(map[string]string), componentNames: make(map[string]string), groupNames: make(map[string]string), privateLocationNames: make(map[string]string), skippedNotifications: make(map[string]bool), }
for _, m := range data.HTTPMonitors { name := g.registry.Name("openstatus_http_monitor", m.GetName()) g.httpMonitorNames[m.GetId()] = name g.monitorRefs[m.GetId()] = resourceRef{"openstatus_http_monitor", name} } for _, m := range data.TCPMonitors { name := g.registry.Name("openstatus_tcp_monitor", m.GetName()) g.tcpMonitorNames[m.GetId()] = name g.monitorRefs[m.GetId()] = resourceRef{"openstatus_tcp_monitor", name} } for _, m := range data.DNSMonitors { name := g.registry.Name("openstatus_dns_monitor", m.GetName()) g.dnsMonitorNames[m.GetId()] = name g.monitorRefs[m.GetId()] = resourceRef{"openstatus_dns_monitor", name} } for _, m := range data.ICMPMonitors { name := g.registry.Name("openstatus_icmp_monitor", m.GetName()) g.icmpMonitorNames[m.GetId()] = name g.monitorRefs[m.GetId()] = resourceRef{"openstatus_icmp_monitor", name} } for _, n := range data.Notifications { if _, ok := renderableNotification(n); !ok { g.skippedNotifications[n.GetId()] = true fmt.Fprintf(os.Stderr, "warning: skipping notification %q — unknown provider type (CLI may be outdated)\n", n.GetName()) continue } name := g.registry.Name("openstatus_notification", n.GetName()) g.notifNames[n.GetId()] = name } for _, sp := range data.StatusPages { page := sp.Page name := g.registry.Name("openstatus_status_page", page.GetTitle()) g.pageNames[page.GetId()] = name g.pageRefs[page.GetId()] = resourceRef{"openstatus_status_page", name}
for _, grp := range sp.Groups { gName := g.registry.Name("openstatus_status_page_component_group", grp.GetName()) g.groupNames[grp.GetId()] = gName g.groupRefs[grp.GetId()] = resourceRef{"openstatus_status_page_component_group", gName} } for _, comp := range sp.Components { cName := g.registry.Name("openstatus_status_page_component", comp.GetName()) g.componentNames[comp.GetId()] = cName } } for _, l := range data.PrivateLocations { name := g.registry.Name("openstatus_private_location", l.GetName()) g.privateLocationNames[l.GetId()] = name }
return g}
// GenerateProviderFile pins base_url when baseURL is not openstatus Cloud,// since the provider does not read OPENSTATUS_API_URL.func GenerateProviderFile(baseURL string) []byte { provider := `provider "openstatus" {}` if baseURL != api.DefaultBaseURL { provider = fmt.Sprintf("provider \"openstatus\" {\n base_url = %q\n}", baseURL+"/rpc") } return []byte(`terraform { required_providers { openstatus = { source = "openstatusHQ/openstatus" version = "~> 0.4" } }}
# Set OPENSTATUS_API_TOKEN environment variable or configure api_token below` + provider + "\n")}
func (g *Generator) GenerateMonitorsFile() *hclwrite.File { f := hclwrite.NewEmptyFile() body := f.Body()
for _, m := range g.data.HTTPMonitors { name := g.httpMonitorNames[m.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_http_monitor", name}) b := block.Body()
b.SetAttributeValue("name", cty.StringVal(m.GetName())) b.SetAttributeValue("url", cty.StringVal(m.GetUrl())) b.SetAttributeValue("periodicity", cty.StringVal(periodicityToString(m.GetPeriodicity())))
if method := httpMethodToString(m.GetMethod()); method != "GET" { b.SetAttributeValue("method", cty.StringVal(method)) } if m.GetBody() != "" { b.SetAttributeValue("body", cty.StringVal(m.GetBody())) } if m.GetTimeout() != 45000 { b.SetAttributeValue("timeout", cty.NumberIntVal(m.GetTimeout())) } if m.GetDegradedAt() != 0 { b.SetAttributeValue("degraded_at", cty.NumberIntVal(m.GetDegradedAt())) } if m.GetRetry() != 3 { b.SetAttributeValue("retry", cty.NumberIntVal(m.GetRetry())) } if !m.GetFollowRedirects() { b.SetAttributeValue("follow_redirects", cty.BoolVal(false)) } b.SetAttributeValue("active", cty.BoolVal(m.GetActive())) b.SetAttributeValue("public", cty.BoolVal(m.GetPublic())) if m.GetDescription() != "" { b.SetAttributeValue("description", cty.StringVal(m.GetDescription())) }
writeRegions(b, m.GetRegions()) writeHeaders(b, m.GetHeaders()) writeStatusCodeAssertions(b, m.GetStatusCodeAssertions()) writeBodyAssertions(b, m.GetBodyAssertions()) writeHeaderAssertions(b, m.GetHeaderAssertions()) writeOpenTelemetry(b, m.GetOpenTelemetry())
body.AppendNewline() }
for _, m := range g.data.TCPMonitors { name := g.tcpMonitorNames[m.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_tcp_monitor", name}) b := block.Body()
b.SetAttributeValue("name", cty.StringVal(m.GetName())) b.SetAttributeValue("uri", cty.StringVal(m.GetUri())) b.SetAttributeValue("periodicity", cty.StringVal(periodicityToString(m.GetPeriodicity())))
if m.GetTimeout() != 45000 { b.SetAttributeValue("timeout", cty.NumberIntVal(m.GetTimeout())) } if m.GetDegradedAt() != 0 { b.SetAttributeValue("degraded_at", cty.NumberIntVal(m.GetDegradedAt())) } if m.GetRetry() != 3 { b.SetAttributeValue("retry", cty.NumberIntVal(m.GetRetry())) } b.SetAttributeValue("active", cty.BoolVal(m.GetActive())) b.SetAttributeValue("public", cty.BoolVal(m.GetPublic())) if m.GetDescription() != "" { b.SetAttributeValue("description", cty.StringVal(m.GetDescription())) }
writeRegions(b, m.GetRegions()) writeOpenTelemetry(b, m.GetOpenTelemetry())
body.AppendNewline() }
for _, m := range g.data.DNSMonitors { name := g.dnsMonitorNames[m.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_dns_monitor", name}) b := block.Body()
b.SetAttributeValue("name", cty.StringVal(m.GetName())) b.SetAttributeValue("uri", cty.StringVal(m.GetUri())) b.SetAttributeValue("periodicity", cty.StringVal(periodicityToString(m.GetPeriodicity())))
if m.GetTimeout() != 45000 { b.SetAttributeValue("timeout", cty.NumberIntVal(m.GetTimeout())) } if m.GetDegradedAt() != 0 { b.SetAttributeValue("degraded_at", cty.NumberIntVal(m.GetDegradedAt())) } if m.GetRetry() != 3 { b.SetAttributeValue("retry", cty.NumberIntVal(m.GetRetry())) } b.SetAttributeValue("active", cty.BoolVal(m.GetActive())) b.SetAttributeValue("public", cty.BoolVal(m.GetPublic())) if m.GetDescription() != "" { b.SetAttributeValue("description", cty.StringVal(m.GetDescription())) }
writeRegions(b, m.GetRegions())
for _, a := range m.GetRecordAssertions() { ab := b.AppendNewBlock("record_assertions", nil).Body() ab.SetAttributeValue("record", cty.StringVal(a.GetRecord())) ab.SetAttributeValue("target", cty.StringVal(a.GetTarget())) ab.SetAttributeValue("comparator", cty.StringVal(recordComparatorToString(a.GetComparator()))) } writeOpenTelemetry(b, m.GetOpenTelemetry())
body.AppendNewline() }
for _, m := range g.data.ICMPMonitors { name := g.icmpMonitorNames[m.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_icmp_monitor", name}) b := block.Body()
b.SetAttributeValue("name", cty.StringVal(m.GetName())) b.SetAttributeValue("uri", cty.StringVal(m.GetUri())) b.SetAttributeValue("periodicity", cty.StringVal(periodicityToString(m.GetPeriodicity())))
if m.GetTimeout() != 45000 { b.SetAttributeValue("timeout", cty.NumberIntVal(m.GetTimeout())) } if m.GetDegradedAt() != 0 { b.SetAttributeValue("degraded_at", cty.NumberIntVal(m.GetDegradedAt())) } if m.GetRetry() != 3 { b.SetAttributeValue("retry", cty.NumberIntVal(m.GetRetry())) } b.SetAttributeValue("active", cty.BoolVal(m.GetActive())) b.SetAttributeValue("public", cty.BoolVal(m.GetPublic())) if m.GetDescription() != "" { b.SetAttributeValue("description", cty.StringVal(m.GetDescription())) }
writeRegions(b, m.GetRegions()) writeOpenTelemetry(b, m.GetOpenTelemetry())
body.AppendNewline() }
return f}
func (g *Generator) GenerateNotificationsFile() *hclwrite.File { f := hclwrite.NewEmptyFile() body := f.Body()
for _, n := range g.data.Notifications { if g.skippedNotifications[n.GetId()] { continue } providerType, ok := renderableNotification(n) if !ok { continue } name := g.notifNames[n.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_notification", name}) b := block.Body()
b.SetAttributeValue("name", cty.StringVal(n.GetName())) b.SetAttributeValue("provider_type", cty.StringVal(providerType))
g.writeMonitorIds(b, n.GetMonitorIds()) g.writeNotificationProvider(b, n)
body.AppendNewline() }
return f}
func (g *Generator) writeMonitorIds(b *hclwrite.Body, ids []string) { if len(ids) == 0 { return } sorted := append([]string(nil), ids...) sort.Strings(sorted)
tokens := hclwrite.Tokens{ {Type: hclsyntax.TokenOBrack, Bytes: []byte("[")}, } for i, id := range sorted { if i > 0 { tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenComma, Bytes: []byte(",")}, &hclwrite.Token{Type: hclsyntax.TokenIdent, Bytes: []byte(" ")}, ) } if ref, found := g.monitorRefs[id]; found { tokens = append(tokens, traversalTokensInline(ref.ResourceType, ref.Name, "id")...) } else { tokens = append(tokens, stringLitTokens(id)...) } } tokens = append(tokens, &hclwrite.Token{Type: hclsyntax.TokenCBrack, Bytes: []byte("]")}, &hclwrite.Token{Type: hclsyntax.TokenNewline, Bytes: []byte("\n")}, ) b.SetAttributeRaw("monitor_ids", tokens)}
func stringLitTokens(s string) hclwrite.Tokens { return hclwrite.Tokens{ {Type: hclsyntax.TokenOQuote, Bytes: []byte(`"`)}, {Type: hclsyntax.TokenQuotedLit, Bytes: []byte(s)}, {Type: hclsyntax.TokenCQuote, Bytes: []byte(`"`)}, }}
func renderableNotification(n *notificationv1.Notification) (string, bool) { data := n.GetData() if data == nil { return "", false } switch data.Data.(type) { case *notificationv1.NotificationData_Discord: return "discord", true case *notificationv1.NotificationData_Email: return "email", true case *notificationv1.NotificationData_Slack: return "slack", true case *notificationv1.NotificationData_Pagerduty: return "pagerduty", true case *notificationv1.NotificationData_Opsgenie: return "opsgenie", true case *notificationv1.NotificationData_Webhook: return "webhook", true case *notificationv1.NotificationData_Telegram: return "telegram", true case *notificationv1.NotificationData_Sms: return "sms", true case *notificationv1.NotificationData_Whatsapp: return "whatsapp", true case *notificationv1.NotificationData_GoogleChat: return "google_chat", true case *notificationv1.NotificationData_GrafanaOncall: return "grafana_oncall", true case *notificationv1.NotificationData_Ntfy: return "ntfy", true case *notificationv1.NotificationData_MsTeams: return "ms_teams", true } return "", false}
func (g *Generator) writeNotificationProvider(b *hclwrite.Body, n *notificationv1.Notification) { data := n.GetData() if data == nil { return }
switch d := data.Data.(type) { case *notificationv1.NotificationData_Discord: pb := b.AppendNewBlock("discord", nil).Body() pb.SetAttributeValue("webhook_url", cty.StringVal(d.Discord.GetWebhookUrl())) case *notificationv1.NotificationData_Email: pb := b.AppendNewBlock("email", nil).Body() pb.SetAttributeValue("email", cty.StringVal(d.Email.GetEmail())) case *notificationv1.NotificationData_Slack: pb := b.AppendNewBlock("slack", nil).Body() pb.SetAttributeValue("webhook_url", cty.StringVal(d.Slack.GetWebhookUrl())) case *notificationv1.NotificationData_Pagerduty: pb := b.AppendNewBlock("pagerduty", nil).Body() appendTODOComment(pb) pb.SetAttributeValue("integration_key", cty.StringVal("REPLACE_ME")) case *notificationv1.NotificationData_Opsgenie: pb := b.AppendNewBlock("opsgenie", nil).Body() appendTODOComment(pb) pb.SetAttributeValue("api_key", cty.StringVal("REPLACE_ME")) pb.SetAttributeValue("region", cty.StringVal(opsgenieRegionToString(d.Opsgenie.GetRegion()))) case *notificationv1.NotificationData_Webhook: pb := b.AppendNewBlock("webhook", nil).Body() pb.SetAttributeValue("endpoint", cty.StringVal(d.Webhook.GetEndpoint())) vals := make([]cty.Value, 0, len(d.Webhook.GetHeaders())) for _, h := range d.Webhook.GetHeaders() { if h.GetKey() == "" { continue } vals = append(vals, cty.ObjectVal(map[string]cty.Value{ "key": cty.StringVal(h.GetKey()), "value": cty.StringVal(h.GetValue()), })) } if len(vals) > 0 { pb.SetAttributeValue("headers", cty.ListVal(vals)) } case *notificationv1.NotificationData_Telegram: pb := b.AppendNewBlock("telegram", nil).Body() pb.SetAttributeValue("chat_id", cty.StringVal(d.Telegram.GetChatId())) case *notificationv1.NotificationData_Sms: pb := b.AppendNewBlock("sms", nil).Body() pb.SetAttributeValue("phone_number", cty.StringVal(d.Sms.GetPhoneNumber())) //nolint:staticcheck // deprecated values still exist on older resources case *notificationv1.NotificationData_Whatsapp: pb := b.AppendNewBlock("whatsapp", nil).Body() pb.SetAttributeValue("phone_number", cty.StringVal(d.Whatsapp.GetPhoneNumber())) case *notificationv1.NotificationData_GoogleChat: pb := b.AppendNewBlock("google_chat", nil).Body() pb.SetAttributeValue("webhook_url", cty.StringVal(d.GoogleChat.GetWebhookUrl())) case *notificationv1.NotificationData_GrafanaOncall: pb := b.AppendNewBlock("grafana_oncall", nil).Body() pb.SetAttributeValue("webhook_url", cty.StringVal(d.GrafanaOncall.GetWebhookUrl())) case *notificationv1.NotificationData_Ntfy: pb := b.AppendNewBlock("ntfy", nil).Body() pb.SetAttributeValue("topic", cty.StringVal(d.Ntfy.GetTopic())) if d.Ntfy.GetServerUrl() != "" { pb.SetAttributeValue("server_url", cty.StringVal(d.Ntfy.GetServerUrl())) } if d.Ntfy.HasToken() { appendTODOComment(pb) pb.SetAttributeValue("token", cty.StringVal("REPLACE_ME")) } case *notificationv1.NotificationData_MsTeams: pb := b.AppendNewBlock("ms_teams", nil).Body() pb.SetAttributeValue("webhook_url", cty.StringVal(d.MsTeams.GetWebhookUrl())) }}
func (g *Generator) GenerateStatusPagesFile() *hclwrite.File { f := hclwrite.NewEmptyFile() body := f.Body()
for _, sp := range g.data.StatusPages { page := sp.Page pageName := g.pageNames[page.GetId()] block := body.AppendNewBlock("resource", []string{"openstatus_status_page", pageName}) b := block.Body()
b.SetAttributeValue("title", cty.StringVal(page.GetTitle())) b.SetAttributeValue("slug", cty.StringVal(page.GetSlug())) if page.GetDescription() != "" { b.SetAttributeValue("description", cty.StringVal(page.GetDescription())) } if page.GetHomepageUrl() != "" { b.SetAttributeValue("homepage_url", cty.StringVal(page.GetHomepageUrl())) } if page.GetContactUrl() != "" { b.SetAttributeValue("contact_url", cty.StringVal(page.GetContactUrl())) } if page.GetIcon() != "" { b.SetAttributeValue("icon", cty.StringVal(page.GetIcon())) } if page.GetCustomDomain() != "" { b.SetAttributeValue("custom_domain", cty.StringVal(page.GetCustomDomain())) }
switch pageAccessTypeToString(page.GetAccessType()) { case "password": b.SetAttributeValue("access_type", cty.StringVal("password")) appendTODOComment(b) b.SetAttributeValue("password", cty.StringVal("REPLACE_ME")) case "email-domain": b.SetAttributeValue("access_type", cty.StringVal("email-domain")) domains := dedupe(page.GetAuthEmailDomains()) if len(domains) > 0 { vals := make([]cty.Value, len(domains)) for i, d := range domains { vals[i] = cty.StringVal(d) } b.SetAttributeValue("auth_email_domains", cty.ListVal(vals)) } else { appendTODOComment(b) b.SetAttributeValue("auth_email_domains", cty.ListVal([]cty.Value{cty.StringVal("REPLACE_ME")})) } case "ip": b.SetAttributeValue("access_type", cty.StringVal("ip")) if r := page.GetAllowedIpRanges(); r != "" { b.SetAttributeValue("allowed_ip_ranges", cty.StringVal(r)) } else { appendTODOComment(b) b.SetAttributeValue("allowed_ip_ranges", cty.StringVal("REPLACE_ME")) } }
if theme := pageThemeToString(page.GetTheme()); theme != "" && theme != "system" { b.SetAttributeValue("theme", cty.StringVal(theme)) } if dl := localeToString(page.GetDefaultLocale()); dl != "" && dl != "en" { b.SetAttributeValue("default_locale", cty.StringVal(dl)) } if locs := page.GetLocales(); len(locs) > 0 { raw := make([]string, 0, len(locs)) for _, l := range locs { if s := localeToString(l); s != "" { raw = append(raw, s) } } if strs := dedupe(raw); len(strs) > 0 { vals := make([]cty.Value, len(strs)) for i, s := range strs { vals[i] = cty.StringVal(s) } b.SetAttributeValue("locales", cty.ListVal(vals)) } } if page.GetAllowIndex() { b.SetAttributeValue("allow_index", cty.BoolVal(true)) } writeCustomTheme(b, page.GetCustomTheme())
body.AppendNewline()
// Component groups first for _, grp := range sp.Groups { gName := g.groupNames[grp.GetId()] gb := body.AppendNewBlock("resource", []string{"openstatus_status_page_component_group", gName}).Body() setTraversalOrString(gb, "page_id", g.pageRefs, page.GetId()) gb.SetAttributeValue("name", cty.StringVal(grp.GetName())) if grp.GetDefaultOpen() { gb.SetAttributeValue("default_open", cty.BoolVal(true)) } body.AppendNewline() }
// Components for _, comp := range sp.Components { cName := g.componentNames[comp.GetId()] cb := body.AppendNewBlock("resource", []string{"openstatus_status_page_component", cName}).Body() setTraversalOrString(cb, "page_id", g.pageRefs, page.GetId()) cb.SetAttributeValue("type", cty.StringVal(pageComponentTypeToString(comp.GetType())))
if comp.GetType() == status_pagev1.PageComponentType_PAGE_COMPONENT_TYPE_MONITOR && comp.GetMonitorId() != "" { setTraversalOrString(cb, "monitor_id", g.monitorRefs, comp.GetMonitorId()) } if comp.GetName() != "" { cb.SetAttributeValue("name", cty.StringVal(comp.GetName())) } if comp.GetDescription() != "" { cb.SetAttributeValue("description", cty.StringVal(comp.GetDescription())) } cb.SetAttributeValue("order", cty.NumberIntVal(int64(comp.GetOrder())))
if comp.GetGroupId() != "" { setTraversalOrString(cb, "group_id", g.groupRefs, comp.GetGroupId()) cb.SetAttributeValue("group_order", cty.NumberIntVal(int64(comp.GetGroupOrder()))) }
body.AppendNewline() } }
return f}
func (g *Generator) GeneratePrivateLocationsFile() *hclwrite.File { f := hclwrite.NewEmptyFile() body := f.Body()
body.AppendUnstructuredTokens(hclwrite.Tokens{ {Type: hclsyntax.TokenComment, Bytes: []byte("# Agent tokens are generated by OpenStatus and exposed as the read-only\n")}, {Type: hclsyntax.TokenComment, Bytes: []byte("# `token` attribute. Reveal one with 'openstatus pl info <id> --show-token'.\n")}, }) body.AppendNewline()
for _, l := range g.data.PrivateLocations { name := g.privateLocationNames[l.GetId()] b := body.AppendNewBlock("resource", []string{"openstatus_private_location", name}).Body()
b.SetAttributeValue("name", cty.StringVal(l.GetName())) g.writeMonitorIds(b, l.GetMonitorIds()) setStringMap(b, "metadata", l.GetMetadata())
body.AppendNewline() }
return f}
func (g *Generator) GenerateImportsFile() *hclwrite.File { f := hclwrite.NewEmptyFile() body := f.Body()
for _, m := range g.data.HTTPMonitors { writeImportBlock(body, "openstatus_http_monitor", g.httpMonitorNames[m.GetId()], m.GetId()) } for _, m := range g.data.TCPMonitors { writeImportBlock(body, "openstatus_tcp_monitor", g.tcpMonitorNames[m.GetId()], m.GetId()) } for _, m := range g.data.DNSMonitors { writeImportBlock(body, "openstatus_dns_monitor", g.dnsMonitorNames[m.GetId()], m.GetId()) } for _, m := range g.data.ICMPMonitors { writeImportBlock(body, "openstatus_icmp_monitor", g.icmpMonitorNames[m.GetId()], m.GetId()) } for _, n := range g.data.Notifications { if g.skippedNotifications[n.GetId()] { continue } writeImportBlock(body, "openstatus_notification", g.notifNames[n.GetId()], n.GetId()) } for _, sp := range g.data.StatusPages { page := sp.Page writeImportBlock(body, "openstatus_status_page", g.pageNames[page.GetId()], page.GetId()) for _, grp := range sp.Groups { writeImportBlock(body, "openstatus_status_page_component_group", g.groupNames[grp.GetId()], fmt.Sprintf("%s/%s", page.GetId(), grp.GetId())) } for _, comp := range sp.Components { writeImportBlock(body, "openstatus_status_page_component", g.componentNames[comp.GetId()], fmt.Sprintf("%s/%s", page.GetId(), comp.GetId())) } } for _, l := range g.data.PrivateLocations { writeImportBlock(body, "openstatus_private_location", g.privateLocationNames[l.GetId()], l.GetId()) }
return f}
func (g *Generator) TotalResourceCount() int { count := len(g.data.HTTPMonitors) + len(g.data.TCPMonitors) + len(g.data.DNSMonitors) + len(g.data.ICMPMonitors) + len(g.data.Notifications) for _, sp := range g.data.StatusPages { count += 1 + len(sp.Components) + len(sp.Groups) } count += len(g.data.PrivateLocations) return count}
func (g *Generator) HasMonitors() bool { return len(g.data.HTTPMonitors) > 0 || len(g.data.TCPMonitors) > 0 || len(g.data.DNSMonitors) > 0 || len(g.data.ICMPMonitors) > 0}
func (g *Generator) HasNotifications() bool { return len(g.data.Notifications) > 0}
func (g *Generator) HasStatusPages() bool { return len(g.data.StatusPages) > 0}
func (g *Generator) HasPrivateLocations() bool { return len(g.data.PrivateLocations) > 0}
// helpers
func writeRegions(b *hclwrite.Body, regions []monitorv1.Region) { if len(regions) == 0 { return } strs := make([]string, len(regions)) for i, r := range regions { strs[i] = regionToTerraform(r) } sort.Strings(strs) vals := make([]cty.Value, len(strs)) for i, s := range strs { vals[i] = cty.StringVal(s) } b.SetAttributeValue("regions", cty.ListVal(vals))}
// dedupe drops repeats while keeping the API's order. Both callers feed provider// attributes typed as List: sorting or duplicating them makes the applied result// differ from the plan, which Terraform rejects as an inconsistent result.func dedupe(values []string) []string { out := make([]string, 0, len(values)) seen := make(map[string]bool, len(values)) for _, v := range values { if seen[v] { continue } seen[v] = true out = append(out, v) } return out}
// stringMapValue returns cty.NilVal for an empty map so callers can omit the// attribute entirely rather than emit an empty one.func stringMapValue(m map[string]string) cty.Value { if len(m) == 0 { return cty.NilVal } vals := make(map[string]cty.Value, len(m)) for k, v := range m { vals[k] = cty.StringVal(v) } return cty.MapVal(vals)}
func setStringMap(b *hclwrite.Body, attr string, m map[string]string) { if v := stringMapValue(m); v != cty.NilVal { b.SetAttributeValue(attr, v) }}
// writeCustomTheme emits only the modes that have entries: the provider rejects// an empty map with "omit the map instead of leaving it empty".func writeCustomTheme(b *hclwrite.Body, theme *status_pagev1.CustomTheme) { if theme == nil { return } modes := make(map[string]cty.Value, 2) if v := stringMapValue(theme.GetLight()); v != cty.NilVal { modes["light"] = v } if v := stringMapValue(theme.GetDark()); v != cty.NilVal { modes["dark"] = v } if len(modes) == 0 { return } b.SetAttributeValue("custom_theme", cty.ObjectVal(modes))}
func writeOpenTelemetry(b *hclwrite.Body, ot *monitorv1.OpenTelemetryConfig) { if ot == nil { return } endpoint := ot.GetEndpoint() headers := ot.GetHeaders() if endpoint == "" && len(headers) == 0 { return } otb := b.AppendNewBlock("open_telemetry", nil).Body() if endpoint != "" { otb.SetAttributeValue("endpoint", cty.StringVal(endpoint)) } for _, h := range headers { if h.GetKey() == "" { continue } hb := otb.AppendNewBlock("headers", nil).Body() hb.SetAttributeValue("key", cty.StringVal(h.GetKey())) hb.SetAttributeValue("value", cty.StringVal(h.GetValue())) }}
func writeHeaders(b *hclwrite.Body, headers []*monitorv1.Headers) { for _, h := range headers { if h.GetKey() == "" { continue } hb := b.AppendNewBlock("headers", nil).Body() hb.SetAttributeValue("key", cty.StringVal(h.GetKey())) hb.SetAttributeValue("value", cty.StringVal(h.GetValue())) }}
func writeStatusCodeAssertions(b *hclwrite.Body, assertions []*monitorv1.StatusCodeAssertion) { for _, a := range assertions { ab := b.AppendNewBlock("status_code_assertions", nil).Body() ab.SetAttributeValue("target", cty.NumberIntVal(int64(a.GetTarget()))) ab.SetAttributeValue("comparator", cty.StringVal(numberComparatorToString(a.GetComparator()))) }}
func writeBodyAssertions(b *hclwrite.Body, assertions []*monitorv1.BodyAssertion) { for _, a := range assertions { ab := b.AppendNewBlock("body_assertions", nil).Body() ab.SetAttributeValue("target", cty.StringVal(a.GetTarget())) ab.SetAttributeValue("comparator", cty.StringVal(stringComparatorToString(a.GetComparator()))) }}
func writeHeaderAssertions(b *hclwrite.Body, assertions []*monitorv1.HeaderAssertion) { for _, a := range assertions { ab := b.AppendNewBlock("header_assertions", nil).Body() ab.SetAttributeValue("key", cty.StringVal(a.GetKey())) ab.SetAttributeValue("target", cty.StringVal(a.GetTarget())) ab.SetAttributeValue("comparator", cty.StringVal(stringComparatorToString(a.GetComparator()))) }}
func writeImportBlock(body *hclwrite.Body, resourceType, name, id string) { block := body.AppendNewBlock("import", nil) b := block.Body() b.SetAttributeRaw("to", traversalTokens(resourceType, name)) b.SetAttributeValue("id", cty.StringVal(id)) body.AppendNewline()}
func setTraversalOrString(b *hclwrite.Body, attr string, refs map[string]resourceRef, id string) { if ref, ok := refs[id]; ok { b.SetAttributeRaw(attr, traversalTokens(ref.ResourceType, ref.Name, "id")) } else { b.SetAttributeValue(attr, cty.StringVal(id)) }}
func traversalTokens(parts ...string) hclwrite.Tokens { tokens := traversalTokensInline(parts...) tokens = append(tokens, &hclwrite.Token{ Type: hclsyntax.TokenNewline, Bytes: []byte("\n"), }) return tokens}
func traversalTokensInline(parts ...string) hclwrite.Tokens { tokens := hclwrite.Tokens{} for i, part := range parts { if i > 0 { tokens = append(tokens, &hclwrite.Token{ Type: hclsyntax.TokenDot, Bytes: []byte("."), }) } tokens = append(tokens, &hclwrite.Token{ Type: hclsyntax.TokenIdent, Bytes: []byte(part), }) } return tokens}
func appendTODOComment(b *hclwrite.Body) { b.AppendUnstructuredTokens(hclwrite.Tokens{ {Type: hclsyntax.TokenComment, Bytes: []byte("# TODO: set the actual value — not available from the API\n")}, })}