-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Overview
After many years of using ngrok and creating ad-hoc configs via web UI I've recently decided it's about time to move all of that into Terraform code. In order to put it all together I started with ngrok_reserved_domain
resource.
Unfortunately the migration process revealed quite a few hiccups, therefore I decided to sum it all up in a ticket (please let me know if there's a better way to report such issues). Long story short - it does work, although developer experience wasn't particularly great and there was more rough edges than anticipated.
For reference:
$ terraform -v
Terraform v1.9.6
on darwin_arm64
+ provider registry.terraform.io/hashicorp/aws v5.69.0
+ provider registry.terraform.io/ngrok/ngrok v0.3.0
It's going to be quite lengthy (sorry in advance!).
Issues
1. Required vs optional arguments
Documentation says that all the arguments are optional. That simply doesn't make any sense since user has to provide either a string (so it becomes a subdomain of <user-provided-string>.ngrok.app
) or an FQDN.
resource "ngrok_reserved_domain" "this" {
}
Luckily the API returns ERR_NGROK_422 with "The reserved domain name update failed because no values were provided. Specify at least one value." message in case of above.
Suggested actions:
- Documentation update (I guess it may require provider codebase changes as well)
- Input validation - if user didn't provide required arguments it doesn't make sense to even initiate HTTP communication with the API
2. domain
vs name
dilemma
According to the docs either name
or domain
can be specified. Here's what it says at the time of writing:
domain
- (String) hostname of the reserved domainname
- (String) the domain name to reserve. It may be a full domain name like app.example.com. If the name does not contain a '.' it will reserve that subdomain on ngrok.io.
I find above extremely confusing. Let's say I'd like to use dev.example.com
as my ngrok domain. Which of the following should I use?
resource "ngrok_reserved_domain" "foo" {
name = "dev.example.com"
}
# OR...
resource "ngrok_reserved_domain" "bar" {
domain = "dev.example.com"
}
# OR... perhaps both arguments should be provided?
What's interesting is that according to API reference /reserved_domains
endpoint does not support name
parameter at all, but... ReservedDomainCreate struct (which becomes POST /reserved_domains
payload) does include it.
Let's break it down how it behaves in real life.
Argument | API response | Notes |
---|---|---|
name = "01b747d1" |
gist | 01b747d1.ngrok.app created successfully; mind the name string must be unique |
name = "a2158993.ngrok.pro" |
gist | Here's how to create a built-in subdomain different than default <whatever>.ngrok.app |
name = "0f31e481.example.com" |
gist | Domain successfully created |
domain = "45c97ae6" |
gist | That's clearly an invalid domain. The API returns 400 with ERR_NGROK_448 |
domain = "334b8d11.example.com" |
gist | Domain successfully created |
name = "foo" + domain = "bar.example.com" |
gist | ERR_NGROK_449 response with "You may only specify one of 'name' or 'domain'." message |
Expected state/things to consider:
- It should be a no brainer which argument to use in order to register a custom domain - at the time of writing both
name = "foo.example.com"
anddomain = "foo.example.com"
result in the exact same state. To avoid unnecessary confusionngrok_reserved_domain
should expose only one argument that controls this aspect (probablyname
as it supports both scenarios - FQDN +<placeholder>.ngrok.(app|dev|pro|pizza|<etc>)
) - API reference should list all the params supported by
POST /reserved_domain
endpoint - Resource docs do not mention how to deal with
<foo>.ngrok.(dev|pro|pizza|io)
and<bar>.ngrok-free.(app|dev)
cases - Yet another doc glitch - by default
ngrok.app
gets used, notngrok.io
(I'm on the OG Pro plan, free account may behave differently)
Aside from above I stumbled upon a few other minor issues while experimenting with ngrok_reserved_domain
resource:
2a. certificate_management_status discrepancies in HTTP response (click to expand)
2a certificate_management_status
discrepancies in HTTP response
That's probably irrelevant and depends on some async background process, but it caught my attention anyway. Response body of successful POST /reserved_domains
request sometimes includes "certificate_management_status": null
element.
{
"id": "<redacted>",
"uri": "https://api.ngrok.com/reserved_domains/<redacted>",
"created_at": "2024-09-27T20:14:54Z",
"domain": "<redacted>",
"region": "",
"cname_target": "<redacted>",
"http_endpoint_configuration": null,
"https_endpoint_configuration": null,
"certificate": null,
"certificate_management_policy": {
"authority": "letsencrypt",
"private_key_type": "ecdsa"
},
"certificate_management_status": {
"renews_at": null,
"provisioning_job": {
"error_code": null,
"msg": "Managed certificate provisioning in progress.",
"started_at": "2024-09-27T20:14:54Z",
"retries_at": null
}
},
"acme_challenge_cname_target": null,
"error_redirect_url": null
}
vs
{
"id": "<redacted>",
"uri": "https://api.ngrok.com/reserved_domains/<redacted>",
"created_at": "2024-09-27T20:16:45Z",
"domain": "<redacted>",
"region": "",
"cname_target": "<redacted>",
"http_endpoint_configuration": null,
"https_endpoint_configuration": null,
"certificate": null,
"certificate_management_policy": {
"authority": "letsencrypt",
"private_key_type": "ecdsa"
},
"certificate_management_status": null,
"acme_challenge_cname_target": null,
"error_redirect_url": null
}
2b. Undocumented ERR_NGROK_448 and ERR_NGROK_449 codes (click to expand)
2b. Undocumented ERR_NGROK_448
and ERR_NGROK_449
codes
Neither ERR_NGROK_448
nor ERR_NGROK_449
is documented here.
2c. Broken 404s for https://ngrok.com/docs/* URLs (click to expand)
2c. Broken 404s for https://ngrok.com/docs/* URLs
I thought that perhaps ERR_NGROK_448
wasn't listed for some reason and visited https://ngrok.com/docs/errors/err_ngrok_448/. Here's how it looks for me (some .js
and .css
requests end with 404):
The problem affects any https://ngrok.com/docs/*
URL that does not exist, i.e. https://ngrok.com/docs/foo or https://ngrok.com/docs/bar
3. certificate_management_policy
defaults
There's a glitch in the docs - it says that the certificate_management_policy
block supports the following values:
certificate_management_policy {
authority = "letsencrypt" # default
private_key_type = "rsa" # default, can be changed to "ecdsa"
}
The thing is all my attempts (without explicitly defined certificate_management_policy
block) ended with the following response body:
{
"id": "...",
"...": "...",
"certificate_management_policy": {
"authority": "letsencrypt",
"private_key_type": "ecdsa"
}
}
Could it be that rsa
is no longer the default?