Skip to content

Commit cddeb9b

Browse files
feat: add terraform generate command to export workspace resources to HCL (#25)
Adds `openstatus terraform generate` (alias `tf gen`) that fetches all workspace resources and generates Terraform HCL files with import blocks. - New `internal/terraform/` package with fetch, HCL generation (hclwrite), naming/dedup, region mapping (28 regions), and enum converters - Generates provider.tf, monitors.tf, notifications.tf, status_pages.tf, and imports.tf in ./openstatus-terraform/ - Supports HTTP, TCP, DNS monitors, 12 notification providers, status pages with components and component groups - Cross-references single-value fields (page_id, monitor_id, group_id) as Terraform traversals with fallback to raw strings - Sensitive values emitted as "REPLACE_ME" with TODO comments - Updates CLI skill with terraform trigger phrases and workflow docs
1 parent c3c647d commit cddeb9b

File tree

16 files changed

+1522
-11
lines changed

16 files changed

+1522
-11
lines changed

docs/openstatus-docs.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,3 +558,32 @@ Usage:
558558
```bash
559559
$ openstatus [GLOBAL FLAGS] logout [ARGUMENTS...]
560560
```
561+
562+
### `terraform` command (aliases: `tf`)
563+
564+
Generate Terraform configuration.
565+
566+
Usage:
567+
568+
```bash
569+
$ openstatus [GLOBAL FLAGS] terraform [ARGUMENTS...]
570+
```
571+
572+
### `terraform generate` subcommand (aliases: `gen`)
573+
574+
Generate Terraform configuration from workspace resources.
575+
576+
> openstatus terraform generate [--output-dir ./openstatus-terraform/]
577+
578+
Usage:
579+
580+
```bash
581+
$ openstatus [GLOBAL FLAGS] terraform generate [COMMAND FLAGS] [ARGUMENTS...]
582+
```
583+
584+
The following flags are supported:
585+
586+
| Name | Description | Default value | Environment variables |
587+
|-----------------------------|-----------------------------------------|:-------------------------:|:----------------------:|
588+
| `--access-token="…"` (`-t`) | OpenStatus API Access Token | | `OPENSTATUS_API_TOKEN` |
589+
| `--output-dir="…"` (`-o`) | Directory to write Terraform files into | `./openstatus-terraform/` | *none* |

docs/openstatus.1

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1346,3 +1346,57 @@ Usage:
13461346
.EX
13471347
$ openstatus [GLOBAL FLAGS] logout [ARGUMENTS...]
13481348
.EE
1349+
.SS \f[CR]terraform\f[R] command (aliases: \f[CR]tf\f[R])
1350+
Generate Terraform configuration.
1351+
.PP
1352+
Usage:
1353+
.IP
1354+
.EX
1355+
$ openstatus [GLOBAL FLAGS] terraform [ARGUMENTS...]
1356+
.EE
1357+
.SS \f[CR]terraform generate\f[R] subcommand (aliases: \f[CR]gen\f[R])
1358+
Generate Terraform configuration from workspace resources.
1359+
.RS
1360+
.PP
1361+
openstatus terraform generate [\(enoutput\-dir ./openstatus\-terraform/]
1362+
.RE
1363+
.PP
1364+
Usage:
1365+
.IP
1366+
.EX
1367+
$ openstatus [GLOBAL FLAGS] terraform generate [COMMAND FLAGS] [ARGUMENTS...]
1368+
.EE
1369+
.PP
1370+
The following flags are supported:
1371+
.PP
1372+
.TS
1373+
tab(@);
1374+
lw(16.8n) lw(23.7n) cw(15.6n) cw(13.9n).
1375+
T{
1376+
Name
1377+
T}@T{
1378+
Description
1379+
T}@T{
1380+
Default value
1381+
T}@T{
1382+
Environment variables
1383+
T}
1384+
_
1385+
T{
1386+
\f[CR]\-\-access\-token=\(dq\&...\(dq\f[R] (\f[CR]\-t\f[R])
1387+
T}@T{
1388+
OpenStatus API Access Token
1389+
T}@T{
1390+
T}@T{
1391+
\f[CR]OPENSTATUS_API_TOKEN\f[R]
1392+
T}
1393+
T{
1394+
\f[CR]\-\-output\-dir=\(dq\&...\(dq\f[R] (\f[CR]\-o\f[R])
1395+
T}@T{
1396+
Directory to write Terraform files into
1397+
T}@T{
1398+
\f[CR]./openstatus\-terraform/\f[R]
1399+
T}@T{
1400+
\f[I]none\f[R]
1401+
T}
1402+
.TE

go.mod

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/openstatusHQ/cli
22

3-
go 1.24.0
3+
go 1.25
44

55
require github.com/urfave/cli/v3 v3.0.0-alpha9.2 // direct
66

@@ -12,6 +12,7 @@ require (
1212
github.com/charmbracelet/huh v1.0.0
1313
github.com/fatih/color v1.18.0
1414
github.com/google/go-cmp v0.7.0
15+
github.com/hashicorp/hcl/v2 v2.24.0
1516
github.com/joho/godotenv v1.5.1
1617
github.com/knadh/koanf/parsers/yaml v0.1.0
1718
github.com/knadh/koanf/providers/file v1.1.2
@@ -21,13 +22,17 @@ require (
2122
github.com/olekukonko/tablewriter v1.0.7
2223
github.com/rodaine/table v1.3.0
2324
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
24-
golang.org/x/term v0.1.0
25+
github.com/zclconf/go-cty v1.18.0
26+
golang.org/x/term v0.32.0
27+
golang.org/x/text v0.25.0
2528
sigs.k8s.io/yaml v1.4.0
2629
)
2730

2831
require (
2932
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20251209175733-2a1774d88802.1 // indirect
3033
buf.build/gen/go/gnostic/gnostic/protocolbuffers/go v1.36.11-20230414000709-087bc8072ce4.1 // indirect
34+
github.com/agext/levenshtein v1.2.1 // indirect
35+
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
3136
github.com/atotto/clipboard v0.1.4 // indirect
3237
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
3338
github.com/catppuccin/go v0.3.0 // indirect
@@ -50,6 +55,7 @@ require (
5055
github.com/mattn/go-localereader v0.0.1 // indirect
5156
github.com/mattn/go-runewidth v0.0.16 // indirect
5257
github.com/mitchellh/copystructure v1.2.0 // indirect
58+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
5359
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
5460
github.com/mitchellh/reflectwalk v1.0.2 // indirect
5561
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
@@ -60,9 +66,10 @@ require (
6066
github.com/rivo/uniseg v0.4.7 // indirect
6167
github.com/russross/blackfriday/v2 v2.1.0 // indirect
6268
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
69+
golang.org/x/mod v0.17.0 // indirect
6370
golang.org/x/sync v0.15.0 // indirect
6471
golang.org/x/sys v0.33.0 // indirect
65-
golang.org/x/text v0.23.0 // indirect
72+
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
6673
google.golang.org/protobuf v1.36.11 // indirect
6774
gopkg.in/yaml.v3 v3.0.1 // indirect
6875
)

go.sum

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ connectrpc.com/connect v1.19.1 h1:R5M57z05+90EfEvCY1b7hBxDVOUl45PrtXtAV2fOC14=
1010
connectrpc.com/connect v1.19.1/go.mod h1:tN20fjdGlewnSFeZxLKb0xwIZ6ozc3OQs2hTXy4du9w=
1111
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
1212
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
13+
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
14+
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
15+
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
16+
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
1317
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
1418
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
1519
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
@@ -63,12 +67,16 @@ github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
6367
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
6468
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
6569
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
70+
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
71+
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
6672
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
6773
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
6874
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
6975
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
7076
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
7177
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
78+
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
79+
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
7280
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
7381
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
7482
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
@@ -94,6 +102,8 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
94102
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
95103
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
96104
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
105+
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
106+
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
97107
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
98108
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
99109
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
@@ -134,19 +144,27 @@ github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgL
134144
github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
135145
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
136146
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
147+
github.com/zclconf/go-cty v1.18.0 h1:pJ8+HNI4gFoyRNqVE37wWbJWVw43BZczFo7KUoRczaA=
148+
github.com/zclconf/go-cty v1.18.0/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg=
149+
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
150+
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
137151
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
138152
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
153+
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
154+
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
139155
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
140156
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
141157
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
142158
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
143159
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
144160
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
145161
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
146-
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
147-
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
148-
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
149-
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
162+
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
163+
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
164+
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
165+
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
166+
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
167+
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
150168
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
151169
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
152170
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

internal/cmd/app.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/openstatusHQ/cli/internal/run"
1414
"github.com/openstatusHQ/cli/internal/statuspage"
1515
"github.com/openstatusHQ/cli/internal/statusreport"
16+
"github.com/openstatusHQ/cli/internal/terraform"
1617
"github.com/openstatusHQ/cli/internal/whoami"
1718
"github.com/urfave/cli/v3"
1819
)
@@ -74,6 +75,7 @@ https://docs.openstatus.dev | https://github.com/openstatusHQ/cli/issues/new`,
7475
whoami.WhoamiCmd(),
7576
login.LoginCmd(),
7677
login.LogoutCmd(),
78+
terraform.TerraformCmd(),
7779
},
7880
}
7981
return app

internal/cmd/app_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ func Test_NewApp(t *testing.T) {
3232
t.Run("Has expected commands", func(t *testing.T) {
3333
app := cmd.NewApp()
3434

35-
if len(app.Commands) != 8 {
36-
t.Errorf("Expected 8 commands, got %d", len(app.Commands))
35+
if len(app.Commands) != 9 {
36+
t.Errorf("Expected 9 commands, got %d", len(app.Commands))
3737
}
3838

3939
expectedCommands := map[string]bool{
@@ -45,6 +45,7 @@ func Test_NewApp(t *testing.T) {
4545
"whoami": false,
4646
"login": false,
4747
"logout": false,
48+
"terraform": false,
4849
}
4950

5051
for _, subcmd := range app.Commands {

0 commit comments

Comments
 (0)