package 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" ) 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 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), 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 _, 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 } func GenerateProviderFile() []byte { return []byte(`terraform { required_providers { openstatus = { source = "openstatusHQ/openstatus" version = "~> 0.3" } } } # Set OPENSTATUS_API_TOKEN environment variable or configure api_token below provider "openstatus" {} `) } 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() } 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())) 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 --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 _, 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.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 } 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")}, }) }