Compare commits

...

40 Commits

Author SHA1 Message Date
Oscar Hermoso e6c50a470d 🔧 rebuild server when ssh_keys, image or user_data changes (#44) 2024-11-17 15:28:41 +08:00
Oscar Hermoso 2bc281d177 🐙 fix server import for public_ipv4_count (#43) 2024-11-13 22:22:10 +08:00
Oscar Hermoso 2a6ba27340 🎛 Replace wait_for_create with timeouts, support resize & change ips (#41) 2024-11-10 13:38:29 +08:00
dependabot[bot] fecd42d4a9 🐹 Bump github.com/hashicorp/terraform-plugin-docs (#39)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs).


Updates `github.com/hashicorp/terraform-plugin-docs` from 0.19.4 to 0.20.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-docs/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-docs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-docs/compare/v0.19.4...v0.20.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-docs
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 07:18:23 +08:00
dependabot[bot] c5a44b7694 🐈 Bump goreleaser/goreleaser-action in the minor-patch group (#40)
Bumps the minor-patch group with 1 update: [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action).


Updates `goreleaser/goreleaser-action` from 6.0.0 to 6.1.0
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/286f3b13b1b49da4ac219696163fb8c1c93e1200...9ed2f89a662bf1735a48bc8557fd212fa902bebf)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-10 07:16:58 +08:00
Oscar Hermoso 2c639837ef 💁 support updates for server resource - currently rename, or replace (#38) 2024-11-03 11:02:15 +08:00
Oscar Hermoso 200b2ee3a1 📠 Make public_ipv4_count a mandatory attribute on the server resource (#36) 2024-11-03 06:42:41 +08:00
Oscar Hermoso a135bceee8 🧽 remove retries from create operations, BL has fixed underlying bug (#35) 2024-11-03 06:06:32 +08:00
dependabot[bot] 103f4cc2b0 🐈 Bump crazy-max/ghaction-import-gpg in the minor-patch group (#33)
Bumps the minor-patch group with 1 update: [crazy-max/ghaction-import-gpg](https://github.com/crazy-max/ghaction-import-gpg).


Updates `crazy-max/ghaction-import-gpg` from 6.1.0 to 6.2.0
- [Release notes](https://github.com/crazy-max/ghaction-import-gpg/releases)
- [Commits](https://github.com/crazy-max/ghaction-import-gpg/compare/01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4...cb9bde2e2525e640591a934b1fd28eef1dcaf5e5)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-import-gpg
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-03 05:39:38 +08:00
dependabot[bot] db948a103c 🐹 Bump the minor-patch group with 3 updates (#34)
Bumps the minor-patch group with 3 updates: [github.com/hashicorp/terraform-plugin-framework](https://github.com/hashicorp/terraform-plugin-framework), [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators) and [github.com/hashicorp/terraform-plugin-go](https://github.com/hashicorp/terraform-plugin-go).


Updates `github.com/hashicorp/terraform-plugin-framework` from 1.12.0 to 1.13.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework/compare/v1.12.0...v1.13.0)

Updates `github.com/hashicorp/terraform-plugin-framework-validators` from 0.14.0 to 0.15.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework-validators/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework-validators/compare/v0.14.0...v0.15.0)

Updates `github.com/hashicorp/terraform-plugin-go` from 0.24.0 to 0.25.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-go/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-go/compare/v0.24.0...v0.25.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-framework
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-framework-validators
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
- dependency-name: github.com/hashicorp/terraform-plugin-go
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-03 05:39:19 +08:00
Oscar Hermoso e18d2424d0 📼 ensure that load balancer always sets model state for health checks (#32) 2024-10-25 10:39:11 +08:00
dependabot[bot] e1aac7ec1f 🐹 Bump github.com/hashicorp/terraform-plugin-framework-validators (#31)
Bumps the minor-patch group with 1 update: [github.com/hashicorp/terraform-plugin-framework-validators](https://github.com/hashicorp/terraform-plugin-framework-validators).


Updates `github.com/hashicorp/terraform-plugin-framework-validators` from 0.13.0 to 0.14.0
- [Release notes](https://github.com/hashicorp/terraform-plugin-framework-validators/releases)
- [Changelog](https://github.com/hashicorp/terraform-plugin-framework-validators/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/terraform-plugin-framework-validators/compare/v0.13.0...v0.14.0)

---
updated-dependencies:
- dependency-name: github.com/hashicorp/terraform-plugin-framework-validators
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-18 09:50:08 +08:00
Oscar Hermoso 6c3520bbda ⭕ Retry if Servers, SSH Keys, or Load Balancer creation returns 500 response (#29)
* ⭕ retry on failed create of server, load balancer, and SSH key

* 🧹 sweepers for test resources

* 🧹 sweepers, but now they're working
2024-10-13 08:48:12 +08:00
dependabot[bot] 22ea042e33 Bump hashicorp/setup-terraform from 2 to 3 (#23)
Bumps [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) from 2 to 3.
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](https://github.com/hashicorp/setup-terraform/compare/v2...v3)

---
updated-dependencies:
- dependency-name: hashicorp/setup-terraform
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-13 08:47:41 +08:00
dependabot[bot] b63f052a9f Bump actions/checkout from 3 to 4 (#22)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-10 16:37:09 +08:00
Oscar Hermoso a5731e0a12 🧲 Fix README link to examples/README.md 2024-10-05 08:34:22 +08:00
Oscar Hermoso e5450d9f27 🐒 Fix SSH key logging, types (#28)
* 🐒 Fix SSH key logging, import type

* 💱 Ignore comments in go generate git diff
2024-10-04 13:10:14 +08:00
dependabot[bot] 53b1643f30 🐹 Bump github.com/oapi-codegen/oapi-codegen/v2 in the minor-patch group (#27)
Bumps the minor-patch group with 1 update: [github.com/oapi-codegen/oapi-codegen/v2](https://github.com/oapi-codegen/oapi-codegen).


Updates `github.com/oapi-codegen/oapi-codegen/v2` from 2.3.0 to 2.4.1
- [Release notes](https://github.com/oapi-codegen/oapi-codegen/releases)
- [Commits](https://github.com/oapi-codegen/oapi-codegen/compare/v2.3.0...v2.4.1)

---
updated-dependencies:
- dependency-name: github.com/oapi-codegen/oapi-codegen/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-04 12:15:54 +08:00
Oscar Hermoso 1084c27ab6 🥢 import ssh key by fingerprint, make tests faster (#26)
* 🥢 import ssh key by fingerprint, make tests faster

* ⌚ Wait for load balancer creation
2024-10-04 10:14:23 +08:00
dependabot[bot] ffcadb74b3 Bump actions/setup-go from 4 to 5 (#21)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-27 11:41:55 +08:00
Oscar Hermoso d1adea205b 🐛 Move cancel-in-progress to correct location 2024-09-27 08:53:27 +08:00
Oscar Hermoso 57ed8ff440 🐈 pass secrets to test.yml, add prefix to dependabot commit messages (#25)
* 🐈 Add commit message prefix to dependabot.yml

* 😶 Pass GitHub actions secrets from release.yml to test.yml
2024-09-27 08:50:17 +08:00
Oscar Hermoso 702aae5b2f 🐛 Fix release.yml depending on test.yml 2024-09-27 08:27:01 +08:00
Oscar Hermoso 9747587128 🧪 Bump terraform framework to v0.4.1 & test skipping go generate (#24)
* 🧪 Bump terraform framework to v0.4.1 & test skipping go generate

* ⏰ run go generate, update dependabot.yml
2024-09-27 08:22:01 +08:00
Oscar Hermoso 44772aa55d 🚛 load balancer resource (#17)
* 🚛 load balancer resource

* Fix whitespace in server resource
2024-09-16 15:55:01 +08:00
Oscar Hermoso 600e3f56e7 👆 update docs & data sources (#16) 2024-09-14 14:15:26 +08:00
Oscar Hermoso 7a978731bb 🥉 k3s example working (#14)
* WIP commit - helm example

* 🥉 k3s working!
2024-09-07 17:00:24 +08:00
Oscar Hermoso 54dd00fa72 📰 Create LICENSE 2024-09-01 08:17:56 +08:00
Anurag e93659373d 🕸 Add server import by hostname & fix missing port_blocking (#12)
* Add server import by hostname

* Fix missing port blocking in server create

* hostname -> name
2024-08-31 15:27:03 +08:00
Oscar Hermoso dd3aaa2d75 ⚡ allow importing of binarylane_server resources (#10) 2024-08-31 14:39:31 +08:00
Oscar Hermoso 6cafba5330 🚰 add SSH Keys attribute to servers (#9) 2024-08-31 11:48:43 +08:00
Oscar Hermoso 63632d3abb 🔨 Fix some resource attributes to be read only or required (#8) 2024-08-27 20:27:24 +08:00
Oscar Hermoso d5f45788b2 🚒 Create server_firewall_rules resource (#7) 2024-08-26 17:45:48 +08:00
Oscar Hermoso d0716f58af Add data source for VPC Route Entries, imports for VPC, fix update/tests (#6) 2024-08-25 12:47:14 +08:00
Oscar Hermoso 1f24f45af7 ☁️ Add VPC resource (#4)
* WIP commit - Adding VPC resource

* Tests passing for VPC resource

* Eject vpc_route_entries into its own resource so it can be applied later
2024-08-24 15:03:55 +08:00
Oscar Hermoso 924e785de2 Remove cached .terraform.lock.hcl file from git 2024-08-19 12:55:42 +08:00
Oscar Hermoso f4c91d2f94 🐳 Remove docker and terraform from dependabot.yml 2024-08-19 12:50:03 +08:00
Oscar Hermoso f73528497f ⏳ Wait for server creation with wait_for_create attribute (#2)
* Wait for cloud-init to complete

* Define `wait_for_create` attribute to make waiting for server opt-in
2024-08-19 11:46:18 +08:00
Oscar Hermoso f1b7b9853e Initialize VMs with user_data script, create cloud-init examples 2024-08-17 17:38:32 +08:00
Oscar Hermoso c8cbcd4f19 Fix data sources for servers, add tests (tests still WIP) 2024-08-17 14:55:19 +08:00
81 changed files with 8386 additions and 968 deletions
+8
View File
@@ -0,0 +1,8 @@
TF_ACC=1
BINARYLANE_API_TOKEN=
# Uncomment to enable debug logging (will dump request bodies)
# TF_LOG=DEBUG
# Uncomment to disable the Go cache when testing, useful for flaky tests
# GOCACHE=off
+30
View File
@@ -0,0 +1,30 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
commit-message:
prefix: '🐈 '
directory: '/'
schedule:
interval: 'weekly'
day: 'friday'
time: '06:00'
timezone: 'Australia/Perth'
groups:
minor-patch:
update-types:
- 'minor'
- 'patch'
- package-ecosystem: 'gomod'
commit-message:
prefix: '🐹 '
directory: '/'
schedule:
interval: 'weekly'
day: 'friday'
time: '06:00'
timezone: 'Australia/Perth'
groups:
minor-patch:
update-types:
- 'minor'
- 'patch'
+10 -4
View File
@@ -6,7 +6,7 @@ name: Release
on:
push:
tags:
- "v*"
- 'v*'
# Releases need permissions to read and write the repository contents.
# GitHub considers creating releases and uploading assets as writing contents.
@@ -14,7 +14,13 @@ permissions:
contents: write
jobs:
test:
uses: ./.github/workflows/test.yml
secrets:
BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }}
goreleaser:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
@@ -23,16 +29,16 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2
with:
go-version-file: "go.mod"
go-version-file: 'go.mod'
cache: true
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@01dd5d3ca463c7f10f7f4f7b4f177225ac661ee4 # v6.1.0
uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0
id: import_gpg
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0
uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0
with:
args: release --clean
env:
+61
View File
@@ -0,0 +1,61 @@
name: Terraform Provider Tests
on:
pull_request:
paths:
- '.github/workflows/test.yml'
- '**.go'
workflow_call:
secrets:
BINARYLANE_API_TOKEN:
required: true
permissions:
# Permission for checking out code
contents: read
jobs:
acceptance:
name: Acceptance Tests
runs-on: ubuntu-latest
environment: test
concurrency:
group: test
cancel-in-progress: true # would be nice if GH offered cancel-pending instead, https://github.com/orgs/community/discussions/63136
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: '1.5.*'
terraform_wrapper: false
- name: Check if generated files are up to date
run: |
go generate
git diff --color --exit-code --ignore-matching-lines='//'
if [ $? -ne 0 ]; then
echo "generated files are not up to date, please run 'go generate' and commit the changes" >&2
exit 1
fi
- name: Run tests
run: go test -v -cover ./internal/provider/...
env:
TF_ACC: '1'
BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }}
- name: Run sweepers
if: failure()
run: go test -v ./internal/provider/... -sweep=all
env:
TF_ACC: '1'
BINARYLANE_API_TOKEN: ${{ secrets.BINARYLANE_API_TOKEN }}
# unit:
# name: Unit Tests
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: actions/setup-go@v5
# with:
# go-version-file: 'go.mod'
# - run: go test -v -cover ./...
+3 -4
View File
@@ -27,7 +27,6 @@ go.work.sum
# Misc
tmp
# Normally, you wouldn't want to exclude lock files, but in this case, we're
# using Terraform's lock file to store the provider version, so we need to
# ignore it.
terraform.lock.hcl
# Normally, you wouldn't want to exclude lock files, but our examples are tested
# with a locally built provider and won't be useful to others.
.terraform.lock.hcl
+3
View File
@@ -0,0 +1,3 @@
{
"go.testEnvFile": "${workspaceFolder}/.env",
}
+138 -25
View File
@@ -12,7 +12,6 @@ Based on [this example from the terraform docs](https://developer.hashicorp.com/
```hcl
provider_installation {
dev_overrides {
# Example GOBIN path, will need to be replaced with your own GOBIN path. Default is $GOPATH/bin
"oscarhermoso/binarylane" = "/home/oscarhermoso/Git/terraform-provider-binarylane/bin"
@@ -32,6 +31,28 @@ terraform plan
terraform apply
```
### Testing
```sh
cp .env.example .env
# Add your API token to .env
eval export $(cat .env)
go test -v ./internal/provider/...
```
To run Sweepers:
```sh
go test -v ./internal/provider/... -sweep=all
```
### Update modules
```sh
go get -u && go mod tidy && go generate
```
### Adding resources/data sources to the provider
1. Make any changes to `provider_gen_config.yml` (see https://developer.hashicorp.com/terraform/plugin/code-generation/openapi-generator#generator-config)
@@ -57,31 +78,42 @@ tfplugingen-framework scaffold data-source \
4. Populate the template scaffolding:
Define the interfaces that the resource/data source should implement:
#### Resource
Define the interfaces that the resource should implement:
```diff
- var _ resource.Resource = (*replaceMeResource)(nil)
+ // Ensure the implementation satisfies the expected interfaces.
- var _ resource.Resource = (*exampleResource)(nil)
+ var (
+ _ resource.Resource = &replaceMeResource{}
+ _ resource.ResourceWithConfigure = &replaceMeResource{}
+ // _ resource.ResourceWithImportState = &replaceMeResource{}
+ _ resource.Resource = &exampleResource{}
+ _ resource.ResourceWithConfigure = &exampleResource{}
+ _ resource.ResourceWithImportState = &exampleResource{}
+ )
```
Pass the API client to the resource:
Add the API client to the resource:
```diff
- type replaceMeResource struct{}
+ type replaceMeResource struct {
- type exampleResource struct{}
+ type exampleResource struct {
+ bc *BinarylaneClient
+ }
```
Extend the resource model from the generated `resources.*Model`.
```diff
type exampleResourceModel struct {
- Id types.String `tfsdk:"id"`
+ resources.ExampleModel
+ // Add any additional fields here
}
```
Add a `Configure` method to the resource:
```diff
+ func (d *replaceMeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ func (d *exampleResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
+ if req.ProviderData == nil {
+ return
+ }
@@ -89,7 +121,7 @@ Add a `Configure` method to the resource:
+ bc, ok := req.ProviderData.(BinarylaneClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ "Unexpected Resource Configure Type",
+ fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
+ )
@@ -100,25 +132,106 @@ Add a `Configure` method to the resource:
+ }
```
Delete the local `*ResourceModel` struct, and import the generated `resources.*Model` instead.
```diff
- type replaceMeResourceModel struct {
- Id types.String `tfsdk:"id"`
- }
+ func (r *exampleResource) ImportState(
+ ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
+ ) {
+ resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
+ }
```
```diff
func (r *replaceMeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
- var data replaceMeResourceModel
+ var data resources.ReplaceMeModel
```
Add the new data source/resource to `provider.go`:
Add the new resource to `provider.go`:
```diff
return []func() resource.Resource{
# ...
+ NewReplaceMeResource,
+ NewExampleResource,
}
```
#### Data Source
Define the interfaces that the data source should implement:
```diff
- var _ resource.Resource = (*exampleDataSource)(nil)
+ var (
+ _ datasource.DataSource = &exampleDataSource{}
+ _ datasource.DataSourceWithConfigure = &exampleDataSource{}
+ )
Add the API client to the data source:
```diff
- type exampleResource struct{}
+ type exampleResource struct {
+ bc *BinarylaneClient
+ }
```
Derive the data source model from the generated `resources.*Model`.
```diff
type exampleDataSourceModel struct {
- Id types.String `tfsdk:"id"`
+ resources.ExampleModel
}
```
Add a `Configure` method to the data source.
```diff
+ func (d *exampleDataSource) Configure(
+ _ context.Context,
+ req datasource.ConfigureRequest,
+ resp *datasource.ConfigureResponse,
+ ) {
+ if req.ProviderData == nil {
+ return
+ }
+
+ bc, ok := req.ProviderData.(BinarylaneClient)
+ if !ok {
+ resp.Diagnostics.AddError(
+ "Unexpected Data Source Configure Type",
+ fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
+ return
+ }
+
+ d.bc = &bc
+ }
```
Use the generated schema to define the data source schema.
```diff
func (d *exampleDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
- resp.Schema = schema.Schema{
- Attributes: map[string]schema.Attribute{
- "id": schema.StringAttribute{
- Computed: true,
- },
- },
- }
+ ds, err := convertResourceSchemaToDataSourceSchema(
+ resources.ExampleResourceSchema(ctx)
+ AttributeConfig{
+ RequiredAttributes: &[]string{"id"},
+ },
+ )
+ if err != nil {
+ resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
+ return
+ }
+ resp.Schema = *ds
+ // resp.Schema.Description = "TODO"
}
```
Add the data source to `provider.go`:
```diff
return []func() datasource.DataSource{
# ...
+ NewExampleDataSource,
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Oscar Hermoso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+38 -15
View File
@@ -1,24 +1,43 @@
# terraform-provider-binarylane
See the examples in the [examples directory](./examples/basic/main.tf).
See the [documentation on the Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest), or see [examples in the `examples` directory](./examples/README.md).
```terraform
resource "binarylane_server" "example" {
name = "example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
public_ipv4_count = 1
}
```
## WIP
## Progress
If somehow you use this in production I would be pretty impressed.
Planned features:
- [x] Create/delete a server when runing locally
- [x] [Publish to Terraform Registry](https://registry.terraform.io/providers/oscarhermoso/binarylane/latest/docs/resources/server)
- [ ] (and maybe OpenTofu?)
- [ ] Deploy the rest of the owl
- [x] Servers
- [x] Firewall Rules
- [x] Cloud Init
- [ ] Updating server properties
- [ ] Backups
- [ ] Software
- [ ] IPV6
- [ ] Alerts
- [x] SSH Keys
- [x] Virtual Private Cloud
- [x] Load Balancers
- [ ] Images
- [ ] DNS
- [x] Docs
- [x] Generated docs
- [ ] Review & fill out remaining descriptions
- [ ] Include examples in docs
- [x] Examples
- [x] Basic
- [x] Cloud Init
- [ ] NixOS
- [ ] Virtual Private Cloud
- [ ] Kubernetes
## Parameters
@@ -26,7 +45,7 @@ Images:
```sh
curl -X GET "https://api.binarylane.com.au/v2/images?type=distribution&&page=1&per_page=200" \
-H "Authorization: Bearer **********" > tmp/images.json
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/images.json
jq '[ .images[] | .slug ] | sort' tmp/images.json
```
@@ -63,7 +82,7 @@ Regions:
```sh
curl -X GET "https://api.binarylane.com.au/v2/regions" \
-H "Authorization: Bearer **********"" > tmp/regions.json
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/regions.json
jq '[ .regions[] | .slug ] | sort' tmp/regions.json
```
@@ -78,14 +97,18 @@ jq '[ .regions[] | .slug ] | sort' tmp/regions.json
]
```
Sizes (not all sizes are available in all regions):
### Sizes
<details>
<summary>VM size</summary>
```sh
curl -X GET "https://api.binarylane.com.au/v2/sizes" \
-H "Authorization: Bearer **********"" > tmp/sizes.json
-H "Authorization: Bearer $BINARYLANE_API_TOKEN" > tmp/sizes.json
jq '[ .sizes[] | .slug ] | sort' tmp/sizes.json
```
</details>
```json
[
+44
View File
@@ -0,0 +1,44 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_load_balancer Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_load_balancer (Data Source)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `id` (Number) The ID of the load balancer to fetch.
### Read-Only
- `forwarding_rules` (List of Object) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
- `health_check` (Object) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
- `name` (String) The hostname of the load balancer.
- `region` (String) Leave null to create an anycast load balancer.
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
<a id="nestedatt--forwarding_rules"></a>
### Nested Schema for `forwarding_rules`
Read-Only:
- `entry_protocol` (String)
<a id="nestedatt--health_check"></a>
### Nested Schema for `health_check`
Read-Only:
- `path` (String)
- `protocol` (String)
+18 -3
View File
@@ -3,18 +3,33 @@
page_title: "binarylane_server Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
Provides a Binary Lane Server data source. This can be used to read existing server details.
---
# binarylane_server (Data Source)
Provides a Binary Lane Server data source. This can be used to read existing server details.
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `id` (Number) The ID of the server to fetch.
### Read-Only
- `id` (String) The ID of this resource.
- `backups` (Boolean) If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.
- `image` (String) The slug of the selected operating system.
- `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.
- `permalink` (String) A randomly generated two-word identifier assigned to servers in regions that support this feature
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.
- `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server.
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
- `region` (String) The slug of the selected region.
- `size` (String) The slug of the selected size.
- `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.
- `user_data` (String) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_server_firewall_rules Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_server_firewall_rules (Data Source)
TODO
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `server_id` (Number) The ID of the server for which firewall rules should be listed.
### Read-Only
- `firewall_rules` (List of Object) A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules. (see [below for nested schema](#nestedatt--firewall_rules))
<a id="nestedatt--firewall_rules"></a>
### Nested Schema for `firewall_rules`
Read-Only:
- `action` (String)
- `description` (String)
- `destination_addresses` (List of String)
- `destination_ports` (List of String)
- `protocol` (String)
- `source_addresses` (List of String)
+8 -1
View File
@@ -15,6 +15,13 @@ description: |-
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `id` (Number) The ID or fingerprint of the SSH Key to fetch.
### Read-Only
- `id` (String) The ID of this resource.
- `default` (Boolean) Optional: If true this will be added to all new server installations (if we support SSH Key injection for the server's operating system).
- `fingerprint` (String) The fingerprint of the SSH key.
- `name` (String) A name to help you identify the key.
- `public_key` (String) The public key in OpenSSH "authorized_keys" format.
+25
View File
@@ -0,0 +1,25 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_vpc (Data Source)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `id` (Number) The target vpc id.
### Read-Only
- `ip_range` (String) A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.
- `name` (String) A name to help identify this VPC.
+33
View File
@@ -0,0 +1,33 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc_route_entries Data Source - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_vpc_route_entries (Data Source)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `vpc_id` (Number) The target vpc id.
### Read-Only
- `route_entries` (List of Object) Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save. (see [below for nested schema](#nestedatt--route_entries))
<a id="nestedatt--route_entries"></a>
### Nested Schema for `route_entries`
Read-Only:
- `description` (String)
- `destination` (String)
- `router` (String)
+56
View File
@@ -0,0 +1,56 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_load_balancer Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_load_balancer (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) The hostname of the load balancer.
### Optional
- `forwarding_rules` (Attributes List) The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule. (see [below for nested schema](#nestedatt--forwarding_rules))
- `health_check` (Attributes) The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules. (see [below for nested schema](#nestedatt--health_check))
- `region` (String) Leave null to create an anycast load balancer.
- `server_ids` (List of Number) A list of server IDs to assign to this load balancer.
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
### Read-Only
- `id` (Number) The ID of the load balancer to fetch.
<a id="nestedatt--forwarding_rules"></a>
### Nested Schema for `forwarding_rules`
Required:
- `entry_protocol` (String) The protocol that traffic must match for this load balancer to forward traffic according to this rule.
<a id="nestedatt--health_check"></a>
### Nested Schema for `health_check`
Optional:
- `path` (String) Leave null to accept the default '/' path.
- `protocol` (String) Leave null to accept the default HTTP protocol.
<a id="nestedatt--timeouts"></a>
### Nested Schema for `timeouts`
Optional:
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
+24 -3
View File
@@ -18,12 +18,33 @@ Provides a Binary Lane Server resource. This can be used to create and delete se
### Required
- `image` (String) The slug of the selected operating system.
- `public_ipv4_count` (Number) The number of public IPv4 addresses to assign to the server. If this is not provided, the server will be created with the default number of public IPv4 addresses.
- `region` (String) The slug of the selected region.
- `size` (String) The slug of the selected size.
### Optional
- `backups` (Boolean) If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.
- `id` (Number) The ID of the server to fetch.
- `backups` (Boolean) If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.
- `name` (String) The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.
- `password` (String, Sensitive) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.
- `password` (String, Sensitive) If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions. If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.
- `port_blocking` (Boolean) Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.
- `ssh_keys` (List of Number) This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
- `user_data` (String) If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
- `vpc_id` (Number) Leave null to use default (public) network for the selected region.
### Read-Only
- `id` (Number) The ID of the server to fetch.
- `password_change_supported` (Boolean) If this is true then the `password` attribute can be changed with Terraform. If this is false then the `password` attribute can only be replaced with a null/empty value, which will clear the root/administrator password allowing the password to be changed via the web console.
- `permalink` (String) A randomly generated two-word identifier assigned to servers in regions that support this feature
- `private_ipv4_addresses` (List of String) The private IPv4 addresses assigned to the server.
- `public_ipv4_addresses` (List of String) The public IPv4 addresses assigned to the server.
<a id="nestedatt--timeouts"></a>
### Nested Schema for `timeouts`
Optional:
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).
+36
View File
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_server_firewall_rules Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_server_firewall_rules (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `firewall_rules` (Attributes List) A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules. (see [below for nested schema](#nestedatt--firewall_rules))
- `server_id` (Number) The ID of the server for which firewall rules should be listed.
<a id="nestedatt--firewall_rules"></a>
### Nested Schema for `firewall_rules`
Required:
- `action` (String) The action to take when there is a match on this rule.
- `destination_addresses` (List of String) The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
- `protocol` (String) The protocol to match for this rule.
- `source_addresses` (List of String) The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
Optional:
- `description` (String) A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".
- `destination_ports` (List of String) The destination ports to match for this rule. Leave null or empty to match on all ports.
+6 -2
View File
@@ -3,12 +3,12 @@
page_title: "binarylane_ssh_key Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
TODO
---
# binarylane_ssh_key (Resource)
TODO
@@ -24,3 +24,7 @@ TODO
- `default` (Boolean) Optional: If true this will be added to all new server installations (if we support SSH Key injection for the server's operating system).
- `id` (Number) The ID or fingerprint of the SSH Key to fetch.
### Read-Only
- `fingerprint` (String) The fingerprint of the SSH key.
+28
View File
@@ -0,0 +1,28 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_vpc (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `name` (String) A name to help identify this VPC.
### Optional
- `ip_range` (String) A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.
### Read-Only
- `id` (Number) The target vpc id.
+36
View File
@@ -0,0 +1,36 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "binarylane_vpc_route_entries Resource - terraform-provider-binarylane"
subcategory: ""
description: |-
---
# binarylane_vpc_route_entries (Resource)
<!-- schema generated by tfplugindocs -->
## Schema
### Required
- `vpc_id` (Number) The target vpc id.
### Optional
- `route_entries` (Attributes List) Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save. (see [below for nested schema](#nestedatt--route_entries))
<a id="nestedatt--route_entries"></a>
### Nested Schema for `route_entries`
Required:
- `destination` (String) The destination address for this route entry. This may be in CIDR format.
- `router` (String) The server that will receive traffic sent to the destination property in this VPC.
Optional:
- `description` (String) An optional description for the route.
+10
View File
@@ -0,0 +1,10 @@
# examples
In order of increasing complexity, these examples show how to use the provider.
- [./basic](./basic/main.tf) - A basic example that creates a server.
- [./ssh](./ssh/main.tf) - Create a server and registers an SSH key as a global default.
- [./cloud-init](./cloud-init/main.tf) - Create a server with a cloud-init configuration.
- [./nix](./nix/main.tf) - WIP - Create a server with a NixOS configuration.
- [./vpc](./vpc/main.tf) - WIP - Create a server with a Virtual Private Cloud.
- [./k3s](./k3s/main.tf) - WIP - Create Kubernetes cluster using k3s.
-24
View File
@@ -1,24 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/oscarhermoso/binarylane" {
version = "0.1.0"
hashes = [
"h1:M/GVWGdH+W26pw8o6O2zvzorVZvi0hZbaKCT8scoqT8=",
"zh:07c855b20ad789e78602918ce1565b5e1ac411df92b1f9f4a654ef3da77bcd1e",
"zh:09530d6532dcff2ccdf48de853d7144f0c66d7be203883461b256f52e719c06d",
"zh:2c118c8c7572f60739b4f0ac5cecbadd56636fa63f7f4e7f0c6cdec427fbd609",
"zh:3a51045b18ec6f7bd0ff642370f45b7ba936afa9d0e79ff52481a447adb99b66",
"zh:64608398b9da27f5b1efa2d164fc20e4cb924ebef6cdf80da7b4329f344dbf13",
"zh:a9808dacc075dd53774fe559862f22b9ab02a22b789dde59cff0ea3b42f37c73",
"zh:b22dc91a867a470da75f8887f432938cf5db314943c1f90203eff39ce8e24e85",
"zh:c110e831eafd8a138f9af9efc1730b8c3295cec54561918676744b0f6968498c",
"zh:cd4a98509c740beff50979c1e50c00e5b3915bf35185c89b9f306a2432b54a2c",
"zh:cf3b046ec96b5140fb6dacceead8d9ed723debcf580462ab93a5ed1578eaeab8",
"zh:d395d9a1d681980f0068e2146a4961e0a39c572ffdf131d5d5c1f3a91296a5e8",
"zh:eaf6c069dc35c9a6c97502b1b283d4a815730f70b0cfb637d26e29f2a70c2a51",
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
"zh:f31a253fc4c7f0d37ec2bfe2ca2e40c7c51a0db355039d56123160c97bdf7c90",
"zh:fdbadc51fc6c9ac6f2a68d5846fdb743be7b0344307c9dbf6d90252753894633",
]
}
+6 -7
View File
@@ -7,15 +7,14 @@ terraform {
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
resource "binarylane_server" "example" {
name = "tf-example"
region = "per" # or "syd", "mel", "bne", "sin"
image = "ubuntu-24.04"
size = "std-min" # 1 VPCU, 1 GB Memory, 20 GB NVME Storage, 1000 GB Data Transfer
# password will be generated automatically and mailed to the account holder
name = "tf-example-basic"
region = "per" # or "syd", "mel", "bne", "sin"
image = "ubuntu-24.04"
size = "std-min" # 1 VPCU, 1 GB Memory, 20 GB NVME Storage, 1000 GB Data Transfer
public_ipv4_count = 1
# Password will be generated automatically and mailed to the account holder
}
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+47
View File
@@ -0,0 +1,47 @@
# examples/cloud-init
Binary lane servers initialize cloud-init configurations to the server upon creation.
If you want to deploy multiple parts of a cloud-init configuration, you can use the [cloudinit_config](https://registry.terraform.io/providers/hashicorp/cloudinit/latest/docs/data-sources/config) data source.
```terraform
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com.au/v2"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
data "cloudinit_config" "example" {
gzip = false
base64_encode = false
part {
filename = "hello-script.sh"
content_type = "text/x-shellscript"
content = file("./hello-script.sh")
}
part {
filename = "cloud-config.yaml"
content_type = "text/cloud-config"
content = file("./cloud-config.yml")
}
}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = data.cloudinit_config.example.rendered
}
```
+17
View File
@@ -0,0 +1,17 @@
#cloud-config
bootcmd:
# If you have additional installation steps that need to be awaited, define
# these in the `bootcmd` section and they will also be awaited.
#
- [cloud-init-per, once, 'example-installing', sh, -c, 'sleep 60'] # pretend this is a slow installation step
- [
cloud-init-per,
once,
'example-done',
sh,
-c,
'echo Finished installing, Terraform will now proceed:`date` >> /var/tmp/user_data.log',
]
runcmd:
- [sh, -c, 'sleep 99999'] # The provider won't wait for `runcmd` to complete
+18
View File
@@ -0,0 +1,18 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {}
resource "binarylane_server" "example" {
name = "tf-cloud-init-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
user_data = file("./cloud-config.yml")
public_ipv4_count = 1
}
+40
View File
@@ -0,0 +1,40 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# .kube/config contains credentials
.kube
+15
View File
@@ -0,0 +1,15 @@
# WIP - examples/k8s
This WIP example shows how to create a Kubernetes cluster on Binary Lane.
## Prior art
- [MartinHodges/create_k8s](https://github.com/MartinHodges/create_k8s) repo and the [medium article](https://medium.com/@martin.hodges/creating-a-kubernetes-cluster-from-scratch-in-1-hour-using-automation-a25e387be547) that goes with it.
- https://github.com/inscapist/terraform-k3s-private-cloud
- https://github.com/schnerring/schnerring.github.io/blob/07d06fb40e3d01f2483a739f516ac4d711a5742c/content/blog/use-terraform-to-deploy-an-azure-kubernetes-service-aks-cluster-traefik-2-cert-manager-and-lets-encrypt-certificates/index.md
## Remaining TODOs
- [ ] Consider rewriting module to use `microk8s` instead of `k3s`
- [ ] Use `kustomize` instead of `helm`
- [ ] Use the `binarylane_load_balancer` resource instead of `kubectl port-forward` (or use the load balancer in another example)
+16
View File
@@ -0,0 +1,16 @@
#!/bin/bash -x
exec >/tmp/k3s-agent-join-debug.log 2>&1
export K3S_TOKEN="${cluster_token}"
export K3S_URL="https://${cluster_server}:6443"
curl -sfL https://get.k3s.io | sh -s - agent \
--node-name="$(hostname -f)"
unset K3S_TOKEN
unset K3S_URL
# Disable firewall recommended
ufw disable
echo "K3s Node Join Completed"
+25
View File
@@ -0,0 +1,25 @@
#!/bin/bash -x
exec >/tmp/k3s-server-install-debug.log 2>&1
export INSTALL_K3S_NAME="${cluster_id}"
export K3S_TOKEN="${cluster_token}"
export K3S_KUBECONFIG_MODE="644"
curl -sfL https://get.k3s.io | sh -s - server \
--node-name="$(hostname -f)"
# --disable-cloud-controller \
# --disable servicelb \
# --disable local-storage \
# --disable traefik
unset INSTALL_K3S_NAME
unset K3S_TOKEN
unset K3S_KUBECONFIG_MODE
echo "Installing Helm"
curl -sfL https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
# Disable firewall recommended
ufw disable
echo "K3s Setup Completed"
+176
View File
@@ -0,0 +1,176 @@
locals {
cluster_id = "tf-example-k8s"
custom_domain = "tf-example-k8s.internal"
}
resource "random_password" "binarylane" {
length = 18
}
resource "random_password" "cluster_token" {
length = 64
special = false
}
# SSH Key
# -------
resource "binarylane_ssh_key" "example" {
name = "tf-example-k8s"
public_key = file("~/.ssh/id_ed25519.pub")
}
# Virtual Private Cloud
# ---------------------
resource "binarylane_vpc" "example" {
name = local.cluster_id
ip_range = "10.240.0.0/16"
}
# k3s Servers
# -----------
data "cloudinit_config" "server" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-server-install.sh", {
cluster_id = local.cluster_id,
cluster_token = random_password.cluster_token.result,
})
}
}
resource "binarylane_server" "server" {
count = 1
name = "${local.cluster_id}-server-${count.index + 1}"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
password = random_password.binarylane.result
ssh_keys = [binarylane_ssh_key.example.id]
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.server.rendered)
}
data "external" "kubeconfig" {
program = [
"/usr/bin/ssh",
"-o UserKnownHostsFile=/dev/null",
"-o StrictHostKeyChecking=no",
"root@${binarylane_server.server.0.permalink}",
"echo '{\"kubeconfig\":\"'$(cat /etc/rancher/k3s/k3s.yaml | base64)'\"}'",
]
}
resource "local_sensitive_file" "kubeconfig" {
depends_on = [binarylane_server.server]
content = replace(
base64decode(
replace(data.external.kubeconfig.result.kubeconfig, " ", "")
),
"server: https://127.0.0.1:6443",
"server: https://${binarylane_server.server.0.permalink}:6443",
)
filename = "${path.module}/.kube/config"
file_permission = "0600"
}
# k3s Agents
# ----------
data "cloudinit_config" "agent" {
gzip = false
base64_encode = false
part {
content_type = "text/x-shellscript"
content = templatefile("${path.module}/k3s-agent-join.sh", {
cluster_token = random_password.cluster_token.result,
cluster_server = binarylane_server.server.0.private_ipv4_addresses.0
})
}
}
resource "binarylane_server" "agent" {
count = 2
name = "${local.cluster_id}-agent-${count.index + 1}"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
password = random_password.binarylane.result
ssh_keys = [binarylane_ssh_key.example.id]
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
user_data = sensitive(data.cloudinit_config.agent.rendered)
}
# Virtual Private Cloud Routing
# -----------------------------
resource "binarylane_vpc_route_entries" "example" {
vpc_id = binarylane_vpc.example.id
route_entries = [
# {
# description = "NAT"
# destination = "0.0.0.0/0"
# router = binarylane_server.gateway.private_ipv4_addresses.0
# }
]
}
locals {
agent_ips = flatten([for _, a in binarylane_server.agent : a.private_ipv4_addresses])
server_ips = flatten([for _, s in binarylane_server.server : s.private_ipv4_addresses])
}
resource "binarylane_server_firewall_rules" "example" {
for_each = { for server in concat(binarylane_server.server, binarylane_server.agent) : server.name => server }
server_id = each.value.id
firewall_rules = [
{
description = "K3s supervisor and Kubernetes API Server"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = local.server_ips
destination_ports = ["6443"]
action = "accept"
},
{
description = "Flannel VXLAN"
protocol = "udp"
source_addresses = [binarylane_vpc.example.ip_range]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["8472"]
action = "accept"
},
{
description = "Kubelet metrics"
protocol = "tcp"
source_addresses = [binarylane_vpc.example.ip_range]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["10250"]
action = "accept"
},
{
description = "SSH"
protocol = "udp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["22"]
action = "accept"
},
{
description = "HTTP"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_vpc.example.ip_range]
destination_ports = ["80"]
action = "accept"
},
]
}
+64
View File
@@ -0,0 +1,64 @@
resource "kubectl_manifest" "hello" {
yaml_body = <<YAML
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
selector:
matchLabels:
app: nginx-hello
template:
metadata:
labels:
app: nginx-hello
spec:
containers:
- image: nginxdemos/hello
name: nginx-hello
ports:
- name: http
containerPort: 8080
protocol: TCP
YAML
}
resource "kubectl_manifest" "hello_service" {
yaml_body = <<YAML
apiVersion: v1
kind: Service
metadata:
name: nginx-hello
labels:
app: nginx-hello
spec:
type: ClusterIP
selector:
app: nginx-hello
ports:
- port: 80
YAML
}
resource "kubectl_manifest" "hello_ingress" {
yaml_body = <<YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-hello
spec:
rules:
- host: "*.bnr.la"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-hello
port:
number: 80
YAML
}
+27
View File
@@ -0,0 +1,27 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
kubectl = {
source = "alekc/kubectl"
version = ">= 2.0.0"
}
}
}
provider "binarylane" {}
provider "helm" {
kubernetes {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
}
provider "kubectl" {
host = "${binarylane_server.server.0.permalink}:6443"
config_path = local_sensitive_file.kubeconfig.filename
insecure = true # TODO
}
+42
View File
@@ -0,0 +1,42 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Misc
# Don't commit private keys
id_ed25519
+3
View File
@@ -0,0 +1,3 @@
# examples/nix
WIP
+14
View File
@@ -0,0 +1,14 @@
#cloud-config
# yaml-language-server: $schema=https://raw.githubusercontent.com/canonical/cloud-init/main/cloudinit/config/schemas/schema-cloud-config-v1.json
#cloud-config
write_files:
- path: /etc/nixos/host.nix
permissions: '0644'
content: |
{pkgs, ...}:
{
environment.systemPackages = with pkgs; [ vim ];
}
bootcmd:
- cloud-init-per once "infect" curl https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | NIXOS_IMPORT=./host.nix NIX_CHANNEL=nixos-23.05 bash 2>&1 | tee /var/tmp/infect.log
+18
View File
@@ -0,0 +1,18 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {}
resource "binarylane_server" "example" {
name = "tf-nix-example"
region = "per"
image = "debian-12"
size = "std-min"
user_data = file("./cloud-config.yml")
public_ipv4_count = 1
}
-24
View File
@@ -1,24 +0,0 @@
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.
provider "registry.terraform.io/oscarhermoso/binarylane" {
version = "0.1.1"
hashes = [
"h1:GvTza7TvwgMCegUJ/q+kmteckZ8Sc/lqsIUK2UCxxAU=",
"zh:025de19988848b2e6b8dacd61b7e99274641f32ea9a9e1a67f7dad006630916f",
"zh:0b5ce83392db3e7693a8c9a71b9c1eb60983c43f758f8a2623a6b64778e683b9",
"zh:0bf621429caa7c4468e2996f6c517178761b0492f8611603efd912797ef4da7c",
"zh:1c4db19c124efecde6910a7e9ecbdb5f3f9042685457a3828ca0c5a43da9f43d",
"zh:30fe222bc7cdfadfe3872d443a8f6db957711fa3961f62601a9b974bda995637",
"zh:3b21a79e2d69d76364a043319bd54c86080eee3016f11b73f821f22629e8b0ef",
"zh:3bbc5ea741c85267ff25bd6db1c0863548f2da2c2277bfbf30955080282fa4f0",
"zh:414887df5094afe07ce13d521c57e161301437f8891876d9d480409ffc51e5e2",
"zh:594827a9be3de7ab4fe634be3d933c5c5a0de665293feab34037da6af5355bf6",
"zh:7d73d2ca49b8a6d6f8fe23cf0adfc1fae17fcbb2b1f0a6fb7eef0b3cca5995e1",
"zh:ad0b99751f3ee17630edeed6bec065defcbb08abd377b09169a7826d0f5896ef",
"zh:c4c1ed1f41a238b50a802efc2e8fd025d5e3a1ebd0a6a0b3d84cde1b0a7eda4a",
"zh:cfb3b91d255258583d41382f3adea82af76afff7ba607daff739b951a4a27012",
"zh:d865a4a7886c8354c0636d005904ef090dfad2ea2f5ef27b1ecad2e2dcd9964b",
"zh:f26e0763dbe6a6b2195c94b44696f2110f7f55433dc142839be16b9697fa5597",
]
}
+26 -15
View File
@@ -6,25 +6,36 @@ terraform {
}
}
provider "binarylane" {
# api_url = "" # Defaults to "https://api.binarylane.com"
# api_token = "" # Recommend setting with environment variable BINARYLANE_API_TOKEN
}
provider "binarylane" {}
/**
* This example uses the public key in the git repository, but your SSH key will
* be different. You can use the following command to generate a new key pair:
*
* ssh-keygen -t ed25519 -C "your_email@example.com"
*
* Then, update the "public_key" attribute to point to the location of your
* generated public key.
*/
resource "binarylane_ssh_key" "example" {
name = "test-user"
public_key = file("./id_ed25519.pub") # Likely "~/.ssh/id_ed25519.pub"
default = true # Currently, the server will only register SSH keys that are added as global defaults
}
name = "tf-example-ssh"
public_key = file("./id_ed25519.pub") # Change to "~/.ssh/id_ed25519.pub"
# You also have the option to register an SSH key as a global default, for any
# new VM but it is not the recommended approach.
# default = false
}
resource "binarylane_server" "example" {
name = "tf-example"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
name = "tf-example-ssh"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
ssh_keys = [binarylane_ssh_key.example.id]
public_ipv4_count = 1
depends_on = [binarylane_ssh_key.example] # Wait for SSH key to be created as a global default
# TODO: Extend provider with "ssh_key_id" option for explicit registraton of keys
# If you are using a global default SSH key, you need to explicitly wait for
# it to be created before creating the server.
# depends_on = [binarylane_ssh_key.example]
}
+37
View File
@@ -0,0 +1,37 @@
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json
# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json
# Ignore transient lock info files created by terraform apply
.terraform.tfstate.lock.info
# Include override files you do wish to add to version control using negated pattern
# !example_override.tf
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*
# Ignore CLI configuration files
.terraformrc
terraform.rc
+60
View File
@@ -0,0 +1,60 @@
terraform {
required_providers {
binarylane = {
source = "oscarhermoso/binarylane"
}
}
}
provider "binarylane" {}
resource "binarylane_vpc" "example" {
name = "tf-example-vpc"
ip_range = "10.240.0.0/16"
}
# Web & NAT server
resource "binarylane_server" "web" {
name = "tf-example-vpc-web"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
}
# Database server
resource "binarylane_server" "db" {
name = "tf-example-vpc-db"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 0
}
# VPN server
resource "binarylane_server" "vpn" {
name = "tf-example-vpc-vpn"
region = "per"
image = "ubuntu-24.04"
size = "std-min"
vpc_id = binarylane_vpc.example.id
public_ipv4_count = 1
}
resource "binarylane_vpc_route_entries" "example" {
vpc_id = binarylane_vpc.example.id
route_entries = [
{
description = "NAT"
destination = "0.0.0.0/0"
router = binarylane_server.web.private_ipv4_addresses.0
},
{
description = "VPN"
destination = "192.168.1.0/24"
router = binarylane_server.vpn.private_ipv4_addresses.0
}
]
}
+1 -1
View File
@@ -8,4 +8,4 @@ package main
//go:generate mkdir -p internal/resources internal/data_sources
//go:generate go run github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework generate resources --input=./provider_code_spec.json --output=./internal/resources --package=resources
//go:generate go run github.com/hashicorp/terraform-plugin-codegen-framework/cmd/tfplugingen-framework generate data-sources --input=./provider_code_spec.json --output=./internal/data_sources --package=data_sources
//go:generate tfplugindocs generate
//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs generate
+42 -29
View File
@@ -1,16 +1,19 @@
module terraform-provider-binarylane
go 1.22.3
go 1.22.7
require (
github.com/deepmap/oapi-codegen v1.16.3
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-framework v1.11.0
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0
github.com/hashicorp/terraform-plugin-docs v0.20.0
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1
github.com/hashicorp/terraform-plugin-framework-validators v0.15.0
github.com/hashicorp/terraform-plugin-go v0.25.0
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0
github.com/hashicorp/terraform-plugin-testing v1.10.0
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1
github.com/oapi-codegen/runtime v1.1.1
)
@@ -21,44 +24,49 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect
github.com/agext/levenshtein v1.2.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bgentry/speakeasy v0.1.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/frankban/quicktest v1.14.6 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/getkin/kin-openapi v0.124.0 // indirect
github.com/go-openapi/jsonpointer v0.20.2 // indirect
github.com/go-openapi/swag v0.22.8 // indirect
github.com/getkin/kin-openapi v0.127.0 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/cli v1.1.6 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-plugin v1.6.0 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hc-install v0.8.0 // indirect
github.com/hashicorp/hc-install v0.9.0 // indirect
github.com/hashicorp/hcl/v2 v2.21.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.21.0 // indirect
github.com/hashicorp/terraform-json v0.22.1 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1 // indirect
github.com/hashicorp/terraform-plugin-go v0.23.0 // indirect
github.com/hashicorp/terraform-json v0.23.0 // indirect
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 // indirect
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect
github.com/hashicorp/terraform-registry-address v0.2.3 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/huandu/xstrings v1.4.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/invopop/yaml v0.2.0 // indirect
github.com/invopop/yaml v0.3.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
@@ -66,16 +74,20 @@ require (
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/onsi/gomega v1.33.1 // indirect
github.com/pb33f/libopenapi v0.15.0 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
@@ -83,21 +95,22 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yuin/goldmark v1.7.1 // indirect
github.com/yuin/goldmark v1.7.7 // indirect
github.com/yuin/goldmark-meta v1.1.0 // indirect
github.com/zclconf/go-cty v1.15.0 // indirect
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 // indirect
golang.org/x/mod v0.21.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/tools v0.22.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/grpc v1.67.1 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+91 -59
View File
@@ -16,8 +16,11 @@ github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
@@ -27,8 +30,8 @@ github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xW
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q=
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
@@ -52,31 +55,32 @@ github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5ql
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY=
github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
github.com/go-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -109,13 +113,15 @@ github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuD
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI=
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A=
github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI=
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@@ -123,34 +129,44 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.8.0 h1:LdpZeXkZYMQhoKPCecJHlKvUkQFixN/nvyR1CdfOLjI=
github.com/hashicorp/hc-install v0.8.0/go.mod h1:+MwJYjDfCruSD/udvBmRB22Nlkwwkwf5sAB6uTIhSaU=
github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE=
github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg=
github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14=
github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg=
github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec=
github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0 h1:xB96Rakulnn7ZxikDj8glCyojKpTbW+SGm6QxipoNxU=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.0/go.mod h1:nXlVjqHFBDr6EXnKTT9RCLmwnuTYj/3LE4ffkIdOtog=
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1 h1:eaI/3dsu2T5QAXbA+7N+B+UBj20GdtYnsRuYypKh3S4=
github.com/hashicorp/terraform-plugin-codegen-framework v0.4.1/go.mod h1:kpYM23L7NtcfaQdWAN0QFkV/lU0w16qJ2ddAPCI4zAg=
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0 h1:IKpc337XKk50QyQPSxLrHwdqSo1E2XqCMxFkWsZcTvc=
github.com/hashicorp/terraform-plugin-codegen-openapi v0.3.0/go.mod h1:tT6wl80h7nsMBw+1yZRgJXi+Ys85PUai11weDqysvp4=
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1 h1:7djpLeqtz0x6w01W4hckWwINJUnfXn2zGr/EhSC8rek=
github.com/hashicorp/terraform-plugin-codegen-spec v0.1.1/go.mod h1:PQn6bDD8UWoAVJoHXqFk2i/RmLbeQBjbiP38i+E+YIw=
github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c=
github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA=
github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE=
github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM=
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E=
github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo=
github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co=
github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ=
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0 h1:91dQG1A/DxP6vRz9GiytDTrZTXDbhHPvmpYnAyWA/Vw=
github.com/hashicorp/terraform-plugin-codegen-spec v0.2.0/go.mod h1:fywrEKpordQypmAjz/HIfm2LuNVmyJ6KDe8XT9GdJxQ=
github.com/hashicorp/terraform-plugin-docs v0.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o=
github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y=
github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw=
github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 h1:gm5b1kHgFFhaKFhm4h2TgvMUlNzFAtUqlcOWnWPm+9E=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1/go.mod h1:MsjL1sQ9L7wGwzJ5RjcI6FzEMdyoBnw+XK8ZnOvQOLY=
github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y=
github.com/hashicorp/terraform-plugin-framework-validators v0.15.0/go.mod h1:Bh89/hNmqsEWug4/XWKYBwtnw3tbz5BAy1L1OgvbIaY=
github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974rdTxjqEhXJjbAyks=
github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg=
github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw=
github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc=
github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI=
github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
@@ -159,8 +175,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:
github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/invopop/yaml v0.2.0 h1:7zky/qH+O0DwAyoobXUqvVBwgBFRxKoQ/3FjcVpjTMY=
github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q=
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
@@ -195,6 +211,10 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
@@ -203,12 +223,12 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwd
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 h1:rICjNsHbPP1LttefanBPnwsSwl09SqhCO7Ee623qR84=
github.com/oapi-codegen/oapi-codegen/v2 v2.3.0/go.mod h1:4k+cJeSq5ntkwlcpQSxLxICCxQzCL772o30PxdibRt4=
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q=
github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8=
github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro=
github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@@ -236,8 +256,8 @@ github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSg
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
@@ -246,6 +266,8 @@ github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5g
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg=
github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
@@ -260,6 +282,9 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
@@ -279,12 +304,14 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U=
github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU=
github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw=
go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -292,14 +319,14 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8 h1:ESSUROHIBHg7USnszlcdmjBEwdMj9VUvU+OPk4yl2mc=
golang.org/x/exp v0.0.0-20240409090435-93d18d7e34b8/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -310,8 +337,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -343,18 +370,20 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -365,10 +394,13 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38 h1:zciRKQ4kBpFgpfC5QQCVtnnNAcLIqweL7plyZRQHVpI=
google.golang.org/genproto/googleapis/rpc v0.0.0-20241021214115-324edc3d5d38/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI=
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -377,9 +409,10 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
@@ -396,6 +429,5 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+64
View File
@@ -0,0 +1,64 @@
package binarylane
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httputil"
"os"
"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
func NewClientWithDefaultConfig() (*ClientWithResponses, error) {
return NewClientWithConfig("", "")
}
func NewClientWithConfig(endpoint string, token string) (*ClientWithResponses, error) {
if endpoint == "" {
endpoint = os.Getenv("BINARYLANE_API_ENDPOINT")
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
}
if token == "" {
token = os.Getenv("BINARYLANE_API_TOKEN")
if token == "" {
return nil, errors.New("missing or empty value for the Binary Lane API " +
"token. Set the `api_token` value in the configuration or use the " +
"BINARYLANE_API_TOKEN environment variable. If either is already set, " +
"ensure the value is not empty")
}
}
auth, err := securityprovider.NewSecurityProviderBearerToken(token)
if err != nil {
return nil, fmt.Errorf("failed to create API client with supplied API token: %w", err)
}
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
client, err := NewClientWithResponses(
endpoint,
WithRequestEditorFn(func(ctx context.Context, req *http.Request) error {
dump, err := httputil.DumpRequestOut(req, true)
if err != nil {
return err
}
tflog.Debug(ctx, fmt.Sprintf("%q\n", dump))
return nil
}),
WithRequestEditorFn(auth.Intercept), // include auth AFTER the request logger
)
if err != nil {
return nil, fmt.Errorf("failed to create Binary Lane API client: %w", err)
}
return client, nil
}
+136 -46
View File
@@ -1,6 +1,6 @@
// Package binarylane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT.
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
package binarylane
import (
@@ -155,29 +155,47 @@ type ClientInterface interface {
PostDomainsRefreshNameserverCache(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
// DeleteDomainsDomainName request
DeleteDomainsDomainName(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*http.Response, error)
DeleteDomainsDomainName(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetDomainsDomainName request
GetDomainsDomainName(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*http.Response, error)
GetDomainsDomainName(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetDomainsDomainNameRecords request
GetDomainsDomainNameRecords(ctx context.Context, domainName string, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
GetDomainsDomainNameRecords(ctx context.Context, domainName struct {
union json.RawMessage
}, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*http.Response, error)
// PostDomainsDomainNameRecordsWithBody request with any body
PostDomainsDomainNameRecordsWithBody(ctx context.Context, domainName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
PostDomainsDomainNameRecordsWithBody(ctx context.Context, domainName struct {
union json.RawMessage
}, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
PostDomainsDomainNameRecords(ctx context.Context, domainName string, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
PostDomainsDomainNameRecords(ctx context.Context, domainName struct {
union json.RawMessage
}, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// DeleteDomainsDomainNameRecordsRecordId request
DeleteDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error)
DeleteDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetDomainsDomainNameRecordsRecordId request
GetDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error)
GetDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error)
// PutDomainsDomainNameRecordsRecordIdWithBody request with any body
PutDomainsDomainNameRecordsRecordIdWithBody(ctx context.Context, domainName string, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
PutDomainsDomainNameRecordsRecordIdWithBody(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error)
PutDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
PutDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
// GetFailoverIpsServerId request
GetFailoverIpsServerId(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -843,7 +861,9 @@ func (c *Client) PostDomainsRefreshNameserverCache(ctx context.Context, reqEdito
return c.Client.Do(req)
}
func (c *Client) DeleteDomainsDomainName(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) DeleteDomainsDomainName(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewDeleteDomainsDomainNameRequest(c.Server, domainName)
if err != nil {
return nil, err
@@ -855,7 +875,9 @@ func (c *Client) DeleteDomainsDomainName(ctx context.Context, domainName string,
return c.Client.Do(req)
}
func (c *Client) GetDomainsDomainName(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) GetDomainsDomainName(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetDomainsDomainNameRequest(c.Server, domainName)
if err != nil {
return nil, err
@@ -867,7 +889,9 @@ func (c *Client) GetDomainsDomainName(ctx context.Context, domainName string, re
return c.Client.Do(req)
}
func (c *Client) GetDomainsDomainNameRecords(ctx context.Context, domainName string, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) GetDomainsDomainNameRecords(ctx context.Context, domainName struct {
union json.RawMessage
}, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetDomainsDomainNameRecordsRequest(c.Server, domainName, params)
if err != nil {
return nil, err
@@ -879,7 +903,9 @@ func (c *Client) GetDomainsDomainNameRecords(ctx context.Context, domainName str
return c.Client.Do(req)
}
func (c *Client) PostDomainsDomainNameRecordsWithBody(ctx context.Context, domainName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) PostDomainsDomainNameRecordsWithBody(ctx context.Context, domainName struct {
union json.RawMessage
}, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPostDomainsDomainNameRecordsRequestWithBody(c.Server, domainName, contentType, body)
if err != nil {
return nil, err
@@ -891,7 +917,9 @@ func (c *Client) PostDomainsDomainNameRecordsWithBody(ctx context.Context, domai
return c.Client.Do(req)
}
func (c *Client) PostDomainsDomainNameRecords(ctx context.Context, domainName string, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) PostDomainsDomainNameRecords(ctx context.Context, domainName struct {
union json.RawMessage
}, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPostDomainsDomainNameRecordsRequest(c.Server, domainName, body)
if err != nil {
return nil, err
@@ -903,7 +931,9 @@ func (c *Client) PostDomainsDomainNameRecords(ctx context.Context, domainName st
return c.Client.Do(req)
}
func (c *Client) DeleteDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) DeleteDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewDeleteDomainsDomainNameRecordsRecordIdRequest(c.Server, domainName, recordId)
if err != nil {
return nil, err
@@ -915,7 +945,9 @@ func (c *Client) DeleteDomainsDomainNameRecordsRecordId(ctx context.Context, dom
return c.Client.Do(req)
}
func (c *Client) GetDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) GetDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetDomainsDomainNameRecordsRecordIdRequest(c.Server, domainName, recordId)
if err != nil {
return nil, err
@@ -927,7 +959,9 @@ func (c *Client) GetDomainsDomainNameRecordsRecordId(ctx context.Context, domain
return c.Client.Do(req)
}
func (c *Client) PutDomainsDomainNameRecordsRecordIdWithBody(ctx context.Context, domainName string, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) PutDomainsDomainNameRecordsRecordIdWithBody(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPutDomainsDomainNameRecordsRecordIdRequestWithBody(c.Server, domainName, recordId, contentType, body)
if err != nil {
return nil, err
@@ -939,7 +973,9 @@ func (c *Client) PutDomainsDomainNameRecordsRecordIdWithBody(ctx context.Context
return c.Client.Do(req)
}
func (c *Client) PutDomainsDomainNameRecordsRecordId(ctx context.Context, domainName string, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
func (c *Client) PutDomainsDomainNameRecordsRecordId(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewPutDomainsDomainNameRecordsRecordIdRequest(c.Server, domainName, recordId, body)
if err != nil {
return nil, err
@@ -3512,7 +3548,9 @@ func NewPostDomainsRefreshNameserverCacheRequest(server string) (*http.Request,
}
// NewDeleteDomainsDomainNameRequest generates requests for DeleteDomainsDomainName
func NewDeleteDomainsDomainNameRequest(server string, domainName string) (*http.Request, error) {
func NewDeleteDomainsDomainNameRequest(server string, domainName struct {
union json.RawMessage
}) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3546,7 +3584,9 @@ func NewDeleteDomainsDomainNameRequest(server string, domainName string) (*http.
}
// NewGetDomainsDomainNameRequest generates requests for GetDomainsDomainName
func NewGetDomainsDomainNameRequest(server string, domainName string) (*http.Request, error) {
func NewGetDomainsDomainNameRequest(server string, domainName struct {
union json.RawMessage
}) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3580,7 +3620,9 @@ func NewGetDomainsDomainNameRequest(server string, domainName string) (*http.Req
}
// NewGetDomainsDomainNameRecordsRequest generates requests for GetDomainsDomainNameRecords
func NewGetDomainsDomainNameRecordsRequest(server string, domainName string, params *GetDomainsDomainNameRecordsParams) (*http.Request, error) {
func NewGetDomainsDomainNameRecordsRequest(server string, domainName struct {
union json.RawMessage
}, params *GetDomainsDomainNameRecordsParams) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3684,7 +3726,9 @@ func NewGetDomainsDomainNameRecordsRequest(server string, domainName string, par
}
// NewPostDomainsDomainNameRecordsRequest calls the generic PostDomainsDomainNameRecords builder with application/json body
func NewPostDomainsDomainNameRecordsRequest(server string, domainName string, body PostDomainsDomainNameRecordsJSONRequestBody) (*http.Request, error) {
func NewPostDomainsDomainNameRecordsRequest(server string, domainName struct {
union json.RawMessage
}, body PostDomainsDomainNameRecordsJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
@@ -3695,7 +3739,9 @@ func NewPostDomainsDomainNameRecordsRequest(server string, domainName string, bo
}
// NewPostDomainsDomainNameRecordsRequestWithBody generates requests for PostDomainsDomainNameRecords with any type of body
func NewPostDomainsDomainNameRecordsRequestWithBody(server string, domainName string, contentType string, body io.Reader) (*http.Request, error) {
func NewPostDomainsDomainNameRecordsRequestWithBody(server string, domainName struct {
union json.RawMessage
}, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3731,7 +3777,9 @@ func NewPostDomainsDomainNameRecordsRequestWithBody(server string, domainName st
}
// NewDeleteDomainsDomainNameRecordsRecordIdRequest generates requests for DeleteDomainsDomainNameRecordsRecordId
func NewDeleteDomainsDomainNameRecordsRecordIdRequest(server string, domainName string, recordId int64) (*http.Request, error) {
func NewDeleteDomainsDomainNameRecordsRecordIdRequest(server string, domainName struct {
union json.RawMessage
}, recordId int64) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3772,7 +3820,9 @@ func NewDeleteDomainsDomainNameRecordsRecordIdRequest(server string, domainName
}
// NewGetDomainsDomainNameRecordsRecordIdRequest generates requests for GetDomainsDomainNameRecordsRecordId
func NewGetDomainsDomainNameRecordsRecordIdRequest(server string, domainName string, recordId int64) (*http.Request, error) {
func NewGetDomainsDomainNameRecordsRecordIdRequest(server string, domainName struct {
union json.RawMessage
}, recordId int64) (*http.Request, error) {
var err error
var pathParam0 string
@@ -3813,7 +3863,9 @@ func NewGetDomainsDomainNameRecordsRecordIdRequest(server string, domainName str
}
// NewPutDomainsDomainNameRecordsRecordIdRequest calls the generic PutDomainsDomainNameRecordsRecordId builder with application/json body
func NewPutDomainsDomainNameRecordsRecordIdRequest(server string, domainName string, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody) (*http.Request, error) {
func NewPutDomainsDomainNameRecordsRecordIdRequest(server string, domainName struct {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody) (*http.Request, error) {
var bodyReader io.Reader
buf, err := json.Marshal(body)
if err != nil {
@@ -3824,7 +3876,9 @@ func NewPutDomainsDomainNameRecordsRecordIdRequest(server string, domainName str
}
// NewPutDomainsDomainNameRecordsRecordIdRequestWithBody generates requests for PutDomainsDomainNameRecordsRecordId with any type of body
func NewPutDomainsDomainNameRecordsRecordIdRequestWithBody(server string, domainName string, recordId int64, contentType string, body io.Reader) (*http.Request, error) {
func NewPutDomainsDomainNameRecordsRecordIdRequestWithBody(server string, domainName struct {
union json.RawMessage
}, recordId int64, contentType string, body io.Reader) (*http.Request, error) {
var err error
var pathParam0 string
@@ -8569,29 +8623,47 @@ type ClientWithResponsesInterface interface {
PostDomainsRefreshNameserverCacheWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*PostDomainsRefreshNameserverCacheResponse, error)
// DeleteDomainsDomainNameWithResponse request
DeleteDomainsDomainNameWithResponse(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameResponse, error)
DeleteDomainsDomainNameWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameResponse, error)
// GetDomainsDomainNameWithResponse request
GetDomainsDomainNameWithResponse(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameResponse, error)
GetDomainsDomainNameWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameResponse, error)
// GetDomainsDomainNameRecordsWithResponse request
GetDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName string, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsResponse, error)
GetDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsResponse, error)
// PostDomainsDomainNameRecordsWithBodyWithResponse request with any body
PostDomainsDomainNameRecordsWithBodyWithResponse(ctx context.Context, domainName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error)
PostDomainsDomainNameRecordsWithBodyWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error)
PostDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName string, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error)
PostDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error)
// DeleteDomainsDomainNameRecordsRecordIdWithResponse request
DeleteDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameRecordsRecordIdResponse, error)
DeleteDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameRecordsRecordIdResponse, error)
// GetDomainsDomainNameRecordsRecordIdWithResponse request
GetDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsRecordIdResponse, error)
GetDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsRecordIdResponse, error)
// PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse request with any body
PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse(ctx context.Context, domainName string, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error)
PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error)
PutDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error)
PutDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error)
// GetFailoverIpsServerIdWithResponse request
GetFailoverIpsServerIdWithResponse(ctx context.Context, serverId int64, params *GetFailoverIpsServerIdParams, reqEditors ...RequestEditorFn) (*GetFailoverIpsServerIdResponse, error)
@@ -11955,7 +12027,9 @@ func (c *ClientWithResponses) PostDomainsRefreshNameserverCacheWithResponse(ctx
}
// DeleteDomainsDomainNameWithResponse request returning *DeleteDomainsDomainNameResponse
func (c *ClientWithResponses) DeleteDomainsDomainNameWithResponse(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameResponse, error) {
func (c *ClientWithResponses) DeleteDomainsDomainNameWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameResponse, error) {
rsp, err := c.DeleteDomainsDomainName(ctx, domainName, reqEditors...)
if err != nil {
return nil, err
@@ -11964,7 +12038,9 @@ func (c *ClientWithResponses) DeleteDomainsDomainNameWithResponse(ctx context.Co
}
// GetDomainsDomainNameWithResponse request returning *GetDomainsDomainNameResponse
func (c *ClientWithResponses) GetDomainsDomainNameWithResponse(ctx context.Context, domainName string, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameResponse, error) {
func (c *ClientWithResponses) GetDomainsDomainNameWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameResponse, error) {
rsp, err := c.GetDomainsDomainName(ctx, domainName, reqEditors...)
if err != nil {
return nil, err
@@ -11973,7 +12049,9 @@ func (c *ClientWithResponses) GetDomainsDomainNameWithResponse(ctx context.Conte
}
// GetDomainsDomainNameRecordsWithResponse request returning *GetDomainsDomainNameRecordsResponse
func (c *ClientWithResponses) GetDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName string, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsResponse, error) {
func (c *ClientWithResponses) GetDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, params *GetDomainsDomainNameRecordsParams, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsResponse, error) {
rsp, err := c.GetDomainsDomainNameRecords(ctx, domainName, params, reqEditors...)
if err != nil {
return nil, err
@@ -11982,7 +12060,9 @@ func (c *ClientWithResponses) GetDomainsDomainNameRecordsWithResponse(ctx contex
}
// PostDomainsDomainNameRecordsWithBodyWithResponse request with arbitrary body returning *PostDomainsDomainNameRecordsResponse
func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithBodyWithResponse(ctx context.Context, domainName string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error) {
func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithBodyWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error) {
rsp, err := c.PostDomainsDomainNameRecordsWithBody(ctx, domainName, contentType, body, reqEditors...)
if err != nil {
return nil, err
@@ -11990,7 +12070,9 @@ func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithBodyWithResponse(c
return ParsePostDomainsDomainNameRecordsResponse(rsp)
}
func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName string, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error) {
func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, body PostDomainsDomainNameRecordsJSONRequestBody, reqEditors ...RequestEditorFn) (*PostDomainsDomainNameRecordsResponse, error) {
rsp, err := c.PostDomainsDomainNameRecords(ctx, domainName, body, reqEditors...)
if err != nil {
return nil, err
@@ -11999,7 +12081,9 @@ func (c *ClientWithResponses) PostDomainsDomainNameRecordsWithResponse(ctx conte
}
// DeleteDomainsDomainNameRecordsRecordIdWithResponse request returning *DeleteDomainsDomainNameRecordsRecordIdResponse
func (c *ClientWithResponses) DeleteDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameRecordsRecordIdResponse, error) {
func (c *ClientWithResponses) DeleteDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*DeleteDomainsDomainNameRecordsRecordIdResponse, error) {
rsp, err := c.DeleteDomainsDomainNameRecordsRecordId(ctx, domainName, recordId, reqEditors...)
if err != nil {
return nil, err
@@ -12008,7 +12092,9 @@ func (c *ClientWithResponses) DeleteDomainsDomainNameRecordsRecordIdWithResponse
}
// GetDomainsDomainNameRecordsRecordIdWithResponse request returning *GetDomainsDomainNameRecordsRecordIdResponse
func (c *ClientWithResponses) GetDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsRecordIdResponse, error) {
func (c *ClientWithResponses) GetDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, reqEditors ...RequestEditorFn) (*GetDomainsDomainNameRecordsRecordIdResponse, error) {
rsp, err := c.GetDomainsDomainNameRecordsRecordId(ctx, domainName, recordId, reqEditors...)
if err != nil {
return nil, err
@@ -12017,7 +12103,9 @@ func (c *ClientWithResponses) GetDomainsDomainNameRecordsRecordIdWithResponse(ct
}
// PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse request with arbitrary body returning *PutDomainsDomainNameRecordsRecordIdResponse
func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse(ctx context.Context, domainName string, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error) {
func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithBodyWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error) {
rsp, err := c.PutDomainsDomainNameRecordsRecordIdWithBody(ctx, domainName, recordId, contentType, body, reqEditors...)
if err != nil {
return nil, err
@@ -12025,7 +12113,9 @@ func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithBodyWithRes
return ParsePutDomainsDomainNameRecordsRecordIdResponse(rsp)
}
func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName string, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error) {
func (c *ClientWithResponses) PutDomainsDomainNameRecordsRecordIdWithResponse(ctx context.Context, domainName struct {
union json.RawMessage
}, recordId int64, body PutDomainsDomainNameRecordsRecordIdJSONRequestBody, reqEditors ...RequestEditorFn) (*PutDomainsDomainNameRecordsRecordIdResponse, error) {
rsp, err := c.PutDomainsDomainNameRecordsRecordId(ctx, domainName, recordId, body, reqEditors...)
if err != nil {
return nil, err
+30 -3
View File
@@ -4,16 +4,43 @@ OPENAPI_FILE=$(dirname "$0")/openapi.json
# Fetch the latest OpenAPI spec
curl https://api.binarylane.com.au/reference/openapi.json --output $OPENAPI_FILE
# Remove the /v2 prefix from the paths
cat <<<$(jq '.paths |= with_entries(.key |= sub("/v2/"; "/"))' $OPENAPI_FILE) >$OPENAPI_FILE
# Set the base URL with a /v2 suffix
# Move the /v2 prefix to the base URL and remove it from the paths
cat <<<$(jq '.servers[0].url = "https://api.binarylane.com.au/v2"' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths |= with_entries(.key |= sub("/v2/"; "/"))' $OPENAPI_FILE) >$OPENAPI_FILE
# Terraform can't handle oneOf types, so we need to replace them with basic types
cat <<<$(jq '.components.schemas.CreateServerRequest.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.CreateServerRequest.properties.ssh_keys.items |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.Rebuild.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ImageOptions.properties.ssh_keys.items |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ChangeImage.properties.image |= del(.oneOf) + {type:"string"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].put.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].get.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.paths["/account/keys/{key_id}"].delete.parameters[0].schema |= del(.oneOf) + {type:"integer"}' $OPENAPI_FILE) >$OPENAPI_FILE
# Remove the "/paths/{image_id}" path because its duplicated by "/images/{image_id_or_slug}"
cat <<<$(jq 'del(.paths."/images/{image_id}")' $OPENAPI_FILE) >$OPENAPI_FILE
# Add x-oapi-codegen-extra-tags so structs can be reflected
## RouteEntryRequest
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.destination += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.description += {"x-oapi-codegen-extra-tags": {"tfsdk": "description"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.RouteEntryRequest.properties.router += {"x-oapi-codegen-extra-tags": {"tfsdk": "router"}}' $OPENAPI_FILE) >$OPENAPI_FILE
## AdvancedFirewallRule
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.description += {"x-oapi-codegen-extra-tags": {"tfsdk": "description"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.action += {"x-oapi-codegen-extra-tags": {"tfsdk": "action"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.destination_ports += {"x-oapi-codegen-extra-tags": {"tfsdk": "destination_ports"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.AdvancedFirewallRule.properties.source_addresses += {"x-oapi-codegen-extra-tags": {"tfsdk": "source_addresses"}}' $OPENAPI_FILE) >$OPENAPI_FILE
## Load Balancer
cat <<<$(jq '.components.schemas.CreateLoadBalancerRequest.properties.forwarding_rules += {"x-oapi-codegen-extra-tags": {"tfsdk": "forwarding_rules"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.ForwardingRule.properties.entry_protocol += {"x-oapi-codegen-extra-tags": {"tfsdk": "entry_protocol"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.LoadBalancer.properties.health_check += {"x-oapi-codegen-extra-tags": {"tfsdk": "health_check"}}' $OPENAPI_FILE) >$OPENAPI_FILE
cat <<<$(jq '.components.schemas.HealthCheckProtocol |= del(.enum)' $OPENAPI_FILE) >$OPENAPI_FILE
+134 -68
View File
@@ -8,7 +8,7 @@
"url": "https://support.binarylane.com.au",
"email": "support@binarylane.com.au"
},
"version": "0.24.4",
"version": "0.25.0",
"x-logo": {
"url": "https://www.binarylane.com.au/res/images/binarylane/logo.png",
"backgroundColor": "#000000",
@@ -695,10 +695,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The name of the domain to fetch.",
"description": "The name or domain ID of the domain to fetch.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
}
],
@@ -744,10 +752,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The name of the domain to delete.",
"description": "The name or domain ID of the domain to delete.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
}
],
@@ -788,10 +804,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The domain name for which records should be listed.",
"description": "The domain name or domain ID for for which records should be listed.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
},
{
@@ -883,10 +907,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The domain name for which the record should be created.",
"description": "The domain name or domain ID for for which the record should be created.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
}
],
@@ -958,10 +990,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The domain name for which the record should be fetched.",
"description": "The domain name or domain ID for for which the record should be fetched.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
},
{
@@ -1017,10 +1057,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The domain name for which the record should be updated.",
"description": "The domain name or domain ID for for which the record should be updated.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
},
{
@@ -1100,10 +1148,18 @@
{
"name": "domain_name",
"in": "path",
"description": "The domain name for which the record should be deleted.",
"description": "The domain name or domain ID for which the record should be deleted.",
"required": true,
"schema": {
"type": "string"
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
}
},
{
@@ -8811,7 +8867,10 @@
"type": "string"
},
"description": "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"x-cli-format": 1
"x-cli-format": 1,
"x-oapi-codegen-extra-tags": {
"tfsdk": "source_addresses"
}
},
"destination_addresses": {
"minItems": 1,
@@ -8820,7 +8879,10 @@
"type": "string"
},
"description": "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"x-cli-format": 2
"x-cli-format": 2,
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination_addresses"
}
},
"destination_ports": {
"type": "array",
@@ -8829,7 +8891,10 @@
},
"description": "The destination ports to match for this rule. Leave null or empty to match on all ports.",
"nullable": true,
"x-cli-format": 4
"x-cli-format": 4,
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination_ports"
}
},
"protocol": {
"allOf": [
@@ -8838,7 +8903,10 @@
}
],
"description": "The protocol to match for this rule.",
"x-cli-format": 3
"x-cli-format": 3,
"x-oapi-codegen-extra-tags": {
"tfsdk": "protocol"
}
},
"action": {
"allOf": [
@@ -8847,7 +8915,10 @@
}
],
"description": "The action to take when there is a match on this rule.",
"x-cli-format": 5
"x-cli-format": 5,
"x-oapi-codegen-extra-tags": {
"tfsdk": "action"
}
},
"description": {
"maxLength": 250,
@@ -8855,7 +8926,10 @@
"type": "string",
"description": "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
"nullable": true,
"x-cli-format": 6
"x-cli-format": 6,
"x-oapi-codegen-extra-tags": {
"tfsdk": "description"
}
}
}
},
@@ -9440,17 +9514,10 @@
"type": "object",
"properties": {
"image": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "The slug or ID of the selected image. What type of image is permitted here varies based on the server action.",
"nullable": true,
"example": 5
"example": 5,
"type": "string"
},
"options": {
"allOf": [
@@ -9983,7 +10050,10 @@
"$ref": "#/components/schemas/ForwardingRule"
},
"description": "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
"nullable": true
"nullable": true,
"x-oapi-codegen-extra-tags": {
"tfsdk": "forwarding_rules"
}
},
"health_check": {
"allOf": [
@@ -10082,15 +10152,8 @@
"ssh_keys": {
"type": "array",
"items": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
"example": 5,
"type": "integer"
},
"description": "This may be either the SSH keys Ids or fingerprints. If this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH keys). Submit an empty array to disable deployment of default keys.",
"nullable": true
@@ -10506,11 +10569,18 @@
"Domain": {
"required": [
"current_nameservers",
"id",
"name",
"zone_file"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "The ID of this domain.",
"format": "int64",
"readOnly": true
},
"name": {
"minLength": 1,
"type": "string",
@@ -10924,7 +10994,10 @@
"$ref": "#/components/schemas/LoadBalancerRuleProtocol"
}
],
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule."
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
"x-oapi-codegen-extra-tags": {
"tfsdk": "entry_protocol"
}
}
}
},
@@ -10964,11 +11037,6 @@
}
},
"HealthCheckProtocol": {
"enum": [
"http",
"https",
"both"
],
"type": "string",
"description": "\n| Value | Description |\n| ----- | ----------- |\n| http | The health check will be performed via HTTP. |\n| https | The health check will be performed via HTTPS. |\n| both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |\n\n",
"x-enum-descriptions": [
@@ -11231,15 +11299,8 @@
"ssh_keys": {
"type": "array",
"items": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"example": 5
"example": 5,
"type": "integer"
},
"description": "This may be either the existing SSH Keys IDs or fingerprints.\nIf this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH Keys).\nSubmit an empty array to disable deployment of default keys.",
"nullable": true
@@ -11770,7 +11831,10 @@
}
],
"description": "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer.",
"readOnly": true
"readOnly": true,
"x-oapi-codegen-extra-tags": {
"tfsdk": "health_check"
}
},
"region": {
"allOf": [
@@ -12413,17 +12477,10 @@
"type": "string"
},
"image": {
"oneOf": [
{
"type": "integer"
},
{
"type": "string"
}
],
"description": "The Operating System ID or slug or Backup image ID to use as a base for the rebuild.",
"nullable": true,
"example": 5
"example": 5,
"type": "string"
},
"options": {
"allOf": [
@@ -12765,18 +12822,27 @@
"router": {
"minLength": 1,
"type": "string",
"description": "The server that will receive traffic sent to the destination property in this VPC."
"description": "The server that will receive traffic sent to the destination property in this VPC.",
"x-oapi-codegen-extra-tags": {
"tfsdk": "router"
}
},
"destination": {
"minLength": 1,
"type": "string",
"description": "The destination address for this route entry. This may be in CIDR format."
"description": "The destination address for this route entry. This may be in CIDR format.",
"x-oapi-codegen-extra-tags": {
"tfsdk": "destination"
}
},
"description": {
"maxLength": 250,
"type": "string",
"description": "An optional description for the route.",
"nullable": true
"nullable": true,
"x-oapi-codegen-extra-tags": {
"tfsdk": "description"
}
}
}
},
+65 -319
View File
@@ -1,6 +1,6 @@
// Package binarylane provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.3.0 DO NOT EDIT.
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
package binarylane
import (
@@ -230,13 +230,6 @@ const (
EnableIpv6TypeEnableIpv6 EnableIpv6Type = "enable_ipv6"
)
// Defines values for HealthCheckProtocol.
const (
HealthCheckProtocolBoth HealthCheckProtocol = "both"
HealthCheckProtocolHttp HealthCheckProtocol = "http"
HealthCheckProtocolHttps HealthCheckProtocol = "https"
)
// Defines values for ImageQueryType.
const (
ImageQueryTypeBackup ImageQueryType = "backup"
@@ -265,8 +258,8 @@ const (
// Defines values for LoadBalancerRuleProtocol.
const (
LoadBalancerRuleProtocolHttp LoadBalancerRuleProtocol = "http"
LoadBalancerRuleProtocolHttps LoadBalancerRuleProtocol = "https"
Http LoadBalancerRuleProtocol = "http"
Https LoadBalancerRuleProtocol = "https"
)
// Defines values for LoadBalancerStatus.
@@ -593,22 +586,22 @@ type AdvancedFeature string
// AdvancedFirewallRule defines model for AdvancedFirewallRule.
type AdvancedFirewallRule struct {
// Action The action to take when there is a match on this rule.
Action AdvancedFirewallRuleAction `json:"action"`
Action AdvancedFirewallRuleAction `json:"action" tfsdk:"action"`
// Description A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. "Block access to RDP" or "Allow access from HQ".
Description *string `json:"description"`
Description *string `json:"description" tfsdk:"description"`
// DestinationAddresses The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
DestinationAddresses []string `json:"destination_addresses"`
DestinationAddresses []string `json:"destination_addresses" tfsdk:"destination_addresses"`
// DestinationPorts The destination ports to match for this rule. Leave null or empty to match on all ports.
DestinationPorts *[]string `json:"destination_ports"`
DestinationPorts *[]string `json:"destination_ports" tfsdk:"destination_ports"`
// Protocol The protocol to match for this rule.
Protocol AdvancedFirewallRuleProtocol `json:"protocol"`
Protocol AdvancedFirewallRuleProtocol `json:"protocol" tfsdk:"protocol"`
// SourceAddresses The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.
SourceAddresses []string `json:"source_addresses"`
SourceAddresses []string `json:"source_addresses" tfsdk:"source_addresses"`
}
// AdvancedFirewallRuleAction
@@ -861,23 +854,12 @@ type ChangeBackupScheduleType string
// ChangeImage defines model for ChangeImage.
type ChangeImage struct {
// Image The slug or ID of the selected image. What type of image is permitted here varies based on the server action.
Image *ChangeImage_Image `json:"image"`
Image *string `json:"image"`
// Options Additional options for the server configuration after the image has been changed.
Options *ImageOptions `json:"options"`
}
// ChangeImageImage0 defines model for .
type ChangeImageImage0 = int
// ChangeImageImage1 defines model for .
type ChangeImageImage1 = string
// ChangeImage_Image The slug or ID of the selected image. What type of image is permitted here varies based on the server action.
type ChangeImage_Image struct {
union json.RawMessage
}
// ChangeIpv6 Enable or Disable IPv6 for a Server
type ChangeIpv6 struct {
// Enabled The desired enabled status of IPv6.
@@ -1131,7 +1113,7 @@ type ConsoleResponse struct {
// CreateLoadBalancerRequest defines model for CreateLoadBalancerRequest.
type CreateLoadBalancerRequest struct {
// ForwardingRules The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default "HTTP" only forwarding rule.
ForwardingRules *[]ForwardingRule `json:"forwarding_rules"`
ForwardingRules *[]ForwardingRule `json:"forwarding_rules" tfsdk:"forwarding_rules"`
// HealthCheck The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.
HealthCheck *HealthCheck `json:"health_check"`
@@ -1185,7 +1167,7 @@ type CreateServerRequest struct {
Size string `json:"size"`
// SshKeys This may be either the SSH keys Ids or fingerprints. If this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH keys). Submit an empty array to disable deployment of default keys.
SshKeys *[]CreateServerRequest_SshKeys_Item `json:"ssh_keys"`
SshKeys *[]int `json:"ssh_keys"`
// UserData If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
UserData *string `json:"user_data"`
@@ -1194,17 +1176,6 @@ type CreateServerRequest struct {
VpcId *int64 `json:"vpc_id"`
}
// CreateServerRequestSshKeys0 defines model for .
type CreateServerRequestSshKeys0 = int
// CreateServerRequestSshKeys1 defines model for .
type CreateServerRequestSshKeys1 = string
// CreateServerRequest_SshKeys_Item defines model for CreateServerRequest.ssh_keys.Item.
type CreateServerRequest_SshKeys_Item struct {
union json.RawMessage
}
// CreateServerResponse defines model for CreateServerResponse.
type CreateServerResponse struct {
Links *ActionsLinks `json:"links,omitempty"`
@@ -1375,6 +1346,9 @@ type Domain struct {
// CurrentNameservers The current authoritative name servers for this domain.
CurrentNameservers *[]string `json:"current_nameservers,omitempty"`
// Id The ID of this domain.
Id *int64 `json:"id,omitempty"`
// Name The name of the domain.
Name *string `json:"name,omitempty"`
@@ -1546,7 +1520,7 @@ type FailoverIpsResponse struct {
// ForwardingRule defines model for ForwardingRule.
type ForwardingRule struct {
// EntryProtocol The protocol that traffic must match for this load balancer to forward traffic according to this rule.
EntryProtocol LoadBalancerRuleProtocol `json:"entry_protocol"`
EntryProtocol LoadBalancerRuleProtocol `json:"entry_protocol" tfsdk:"entry_protocol"`
}
// ForwardingRulesRequest defines model for ForwardingRulesRequest.
@@ -1570,7 +1544,7 @@ type HealthCheck struct {
// | http | The health check will be performed via HTTP. |
// | https | The health check will be performed via HTTPS. |
// | both | The health check will be performed via both HTTP and HTTPS. Failing a health check on one protocol will remove the server from the pool of servers only for that protocol. |
type HealthCheckProtocol string
type HealthCheckProtocol = string
// Host defines model for Host.
type Host struct {
@@ -1677,23 +1651,12 @@ type ImageOptions struct {
// SshKeys This may be either the existing SSH Keys IDs or fingerprints.
// If this is null or not provided any SSH keys that have been marked as default will be deployed (if the operating system supports SSH Keys).
// Submit an empty array to disable deployment of default keys.
SshKeys *[]ImageOptions_SshKeys_Item `json:"ssh_keys"`
SshKeys *[]int `json:"ssh_keys"`
// UserData If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.
UserData *string `json:"user_data"`
}
// ImageOptionsSshKeys0 defines model for .
type ImageOptionsSshKeys0 = int
// ImageOptionsSshKeys1 defines model for .
type ImageOptionsSshKeys1 = string
// ImageOptions_SshKeys_Item defines model for ImageOptions.ssh_keys.Item.
type ImageOptions_SshKeys_Item struct {
union json.RawMessage
}
// ImageQueryType
// | Value | Description |
// | ----- | ----------- |
@@ -1875,7 +1838,7 @@ type LoadBalancer struct {
ForwardingRules *[]ForwardingRule `json:"forwarding_rules,omitempty"`
// HealthCheck The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer.
HealthCheck *HealthCheck `json:"health_check,omitempty"`
HealthCheck *HealthCheck `json:"health_check,omitempty" tfsdk:"health_check"`
// Id The ID of the load balancer.
Id *int64 `json:"id,omitempty"`
@@ -2191,24 +2154,13 @@ type RebootType string
// Rebuild Rebuild an Existing Server
type Rebuild struct {
// Image The Operating System ID or slug or Backup image ID to use as a base for the rebuild.
Image *Rebuild_Image `json:"image"`
Image *string `json:"image"`
// Options Additional options. Leaving this entirely null or any of the properties included null will use the defaults from the existing server.
Options *ImageOptions `json:"options"`
Type RebuildType `json:"type"`
}
// RebuildImage0 defines model for .
type RebuildImage0 = int
// RebuildImage1 defines model for .
type RebuildImage1 = string
// Rebuild_Image The Operating System ID or slug or Backup image ID to use as a base for the rebuild.
type Rebuild_Image struct {
union json.RawMessage
}
// RebuildType defines model for Rebuild.Type.
type RebuildType string
@@ -2349,13 +2301,13 @@ type RouteEntry struct {
// RouteEntryRequest defines model for RouteEntryRequest.
type RouteEntryRequest struct {
// Description An optional description for the route.
Description *string `json:"description"`
Description *string `json:"description" tfsdk:"description"`
// Destination The destination address for this route entry. This may be in CIDR format.
Destination string `json:"destination"`
Destination string `json:"destination" tfsdk:"destination"`
// Router The server that will receive traffic sent to the destination property in this VPC.
Router string `json:"router"`
Router string `json:"router" tfsdk:"router"`
}
// Sample defines model for Sample.
@@ -3187,6 +3139,18 @@ type GetDomainsParams struct {
// PostDomainsJSONBody defines parameters for PostDomains.
type PostDomainsJSONBody = DomainRequest
// DeleteDomainsDomainNameParamsDomainName0 defines parameters for DeleteDomainsDomainName.
type DeleteDomainsDomainNameParamsDomainName0 = int
// DeleteDomainsDomainNameParamsDomainName1 defines parameters for DeleteDomainsDomainName.
type DeleteDomainsDomainNameParamsDomainName1 = string
// GetDomainsDomainNameParamsDomainName0 defines parameters for GetDomainsDomainName.
type GetDomainsDomainNameParamsDomainName0 = int
// GetDomainsDomainNameParamsDomainName1 defines parameters for GetDomainsDomainName.
type GetDomainsDomainNameParamsDomainName1 = string
// GetDomainsDomainNameRecordsParams defines parameters for GetDomainsDomainNameRecords.
type GetDomainsDomainNameRecordsParams struct {
// Type
@@ -3214,12 +3178,42 @@ type GetDomainsDomainNameRecordsParams struct {
PerPage *int32 `form:"per_page,omitempty" json:"per_page,omitempty"`
}
// GetDomainsDomainNameRecordsParamsDomainName0 defines parameters for GetDomainsDomainNameRecords.
type GetDomainsDomainNameRecordsParamsDomainName0 = int
// GetDomainsDomainNameRecordsParamsDomainName1 defines parameters for GetDomainsDomainNameRecords.
type GetDomainsDomainNameRecordsParamsDomainName1 = string
// PostDomainsDomainNameRecordsJSONBody defines parameters for PostDomainsDomainNameRecords.
type PostDomainsDomainNameRecordsJSONBody = DomainRecordRequest
// PostDomainsDomainNameRecordsParamsDomainName0 defines parameters for PostDomainsDomainNameRecords.
type PostDomainsDomainNameRecordsParamsDomainName0 = int
// PostDomainsDomainNameRecordsParamsDomainName1 defines parameters for PostDomainsDomainNameRecords.
type PostDomainsDomainNameRecordsParamsDomainName1 = string
// DeleteDomainsDomainNameRecordsRecordIdParamsDomainName0 defines parameters for DeleteDomainsDomainNameRecordsRecordId.
type DeleteDomainsDomainNameRecordsRecordIdParamsDomainName0 = int
// DeleteDomainsDomainNameRecordsRecordIdParamsDomainName1 defines parameters for DeleteDomainsDomainNameRecordsRecordId.
type DeleteDomainsDomainNameRecordsRecordIdParamsDomainName1 = string
// GetDomainsDomainNameRecordsRecordIdParamsDomainName0 defines parameters for GetDomainsDomainNameRecordsRecordId.
type GetDomainsDomainNameRecordsRecordIdParamsDomainName0 = int
// GetDomainsDomainNameRecordsRecordIdParamsDomainName1 defines parameters for GetDomainsDomainNameRecordsRecordId.
type GetDomainsDomainNameRecordsRecordIdParamsDomainName1 = string
// PutDomainsDomainNameRecordsRecordIdJSONBody defines parameters for PutDomainsDomainNameRecordsRecordId.
type PutDomainsDomainNameRecordsRecordIdJSONBody = DomainRecordRequest
// PutDomainsDomainNameRecordsRecordIdParamsDomainName0 defines parameters for PutDomainsDomainNameRecordsRecordId.
type PutDomainsDomainNameRecordsRecordIdParamsDomainName0 = int
// PutDomainsDomainNameRecordsRecordIdParamsDomainName1 defines parameters for PutDomainsDomainNameRecordsRecordId.
type PutDomainsDomainNameRecordsRecordIdParamsDomainName1 = string
// GetFailoverIpsServerIdParams defines parameters for GetFailoverIpsServerId.
type GetFailoverIpsServerIdParams struct {
// Page The selected page. Page numbering starts at 1
@@ -3958,192 +3952,6 @@ func (a ValidationProblemDetails) MarshalJSON() ([]byte, error) {
return json.Marshal(object)
}
// AsChangeImageImage0 returns the union data inside the ChangeImage_Image as a ChangeImageImage0
func (t ChangeImage_Image) AsChangeImageImage0() (ChangeImageImage0, error) {
var body ChangeImageImage0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromChangeImageImage0 overwrites any union data inside the ChangeImage_Image as the provided ChangeImageImage0
func (t *ChangeImage_Image) FromChangeImageImage0(v ChangeImageImage0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeChangeImageImage0 performs a merge with any union data inside the ChangeImage_Image, using the provided ChangeImageImage0
func (t *ChangeImage_Image) MergeChangeImageImage0(v ChangeImageImage0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsChangeImageImage1 returns the union data inside the ChangeImage_Image as a ChangeImageImage1
func (t ChangeImage_Image) AsChangeImageImage1() (ChangeImageImage1, error) {
var body ChangeImageImage1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromChangeImageImage1 overwrites any union data inside the ChangeImage_Image as the provided ChangeImageImage1
func (t *ChangeImage_Image) FromChangeImageImage1(v ChangeImageImage1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeChangeImageImage1 performs a merge with any union data inside the ChangeImage_Image, using the provided ChangeImageImage1
func (t *ChangeImage_Image) MergeChangeImageImage1(v ChangeImageImage1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ChangeImage_Image) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ChangeImage_Image) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsCreateServerRequestSshKeys0 returns the union data inside the CreateServerRequest_SshKeys_Item as a CreateServerRequestSshKeys0
func (t CreateServerRequest_SshKeys_Item) AsCreateServerRequestSshKeys0() (CreateServerRequestSshKeys0, error) {
var body CreateServerRequestSshKeys0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromCreateServerRequestSshKeys0 overwrites any union data inside the CreateServerRequest_SshKeys_Item as the provided CreateServerRequestSshKeys0
func (t *CreateServerRequest_SshKeys_Item) FromCreateServerRequestSshKeys0(v CreateServerRequestSshKeys0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeCreateServerRequestSshKeys0 performs a merge with any union data inside the CreateServerRequest_SshKeys_Item, using the provided CreateServerRequestSshKeys0
func (t *CreateServerRequest_SshKeys_Item) MergeCreateServerRequestSshKeys0(v CreateServerRequestSshKeys0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsCreateServerRequestSshKeys1 returns the union data inside the CreateServerRequest_SshKeys_Item as a CreateServerRequestSshKeys1
func (t CreateServerRequest_SshKeys_Item) AsCreateServerRequestSshKeys1() (CreateServerRequestSshKeys1, error) {
var body CreateServerRequestSshKeys1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromCreateServerRequestSshKeys1 overwrites any union data inside the CreateServerRequest_SshKeys_Item as the provided CreateServerRequestSshKeys1
func (t *CreateServerRequest_SshKeys_Item) FromCreateServerRequestSshKeys1(v CreateServerRequestSshKeys1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeCreateServerRequestSshKeys1 performs a merge with any union data inside the CreateServerRequest_SshKeys_Item, using the provided CreateServerRequestSshKeys1
func (t *CreateServerRequest_SshKeys_Item) MergeCreateServerRequestSshKeys1(v CreateServerRequestSshKeys1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t CreateServerRequest_SshKeys_Item) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *CreateServerRequest_SshKeys_Item) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsImageOptionsSshKeys0 returns the union data inside the ImageOptions_SshKeys_Item as a ImageOptionsSshKeys0
func (t ImageOptions_SshKeys_Item) AsImageOptionsSshKeys0() (ImageOptionsSshKeys0, error) {
var body ImageOptionsSshKeys0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromImageOptionsSshKeys0 overwrites any union data inside the ImageOptions_SshKeys_Item as the provided ImageOptionsSshKeys0
func (t *ImageOptions_SshKeys_Item) FromImageOptionsSshKeys0(v ImageOptionsSshKeys0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeImageOptionsSshKeys0 performs a merge with any union data inside the ImageOptions_SshKeys_Item, using the provided ImageOptionsSshKeys0
func (t *ImageOptions_SshKeys_Item) MergeImageOptionsSshKeys0(v ImageOptionsSshKeys0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsImageOptionsSshKeys1 returns the union data inside the ImageOptions_SshKeys_Item as a ImageOptionsSshKeys1
func (t ImageOptions_SshKeys_Item) AsImageOptionsSshKeys1() (ImageOptionsSshKeys1, error) {
var body ImageOptionsSshKeys1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromImageOptionsSshKeys1 overwrites any union data inside the ImageOptions_SshKeys_Item as the provided ImageOptionsSshKeys1
func (t *ImageOptions_SshKeys_Item) FromImageOptionsSshKeys1(v ImageOptionsSshKeys1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeImageOptionsSshKeys1 performs a merge with any union data inside the ImageOptions_SshKeys_Item, using the provided ImageOptionsSshKeys1
func (t *ImageOptions_SshKeys_Item) MergeImageOptionsSshKeys1(v ImageOptionsSshKeys1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t ImageOptions_SshKeys_Item) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *ImageOptions_SshKeys_Item) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsNetworkNetmask0 returns the union data inside the Network_Netmask as a NetworkNetmask0
func (t Network_Netmask) AsNetworkNetmask0() (NetworkNetmask0, error) {
var body NetworkNetmask0
@@ -4206,68 +4014,6 @@ func (t *Network_Netmask) UnmarshalJSON(b []byte) error {
return err
}
// AsRebuildImage0 returns the union data inside the Rebuild_Image as a RebuildImage0
func (t Rebuild_Image) AsRebuildImage0() (RebuildImage0, error) {
var body RebuildImage0
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromRebuildImage0 overwrites any union data inside the Rebuild_Image as the provided RebuildImage0
func (t *Rebuild_Image) FromRebuildImage0(v RebuildImage0) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeRebuildImage0 performs a merge with any union data inside the Rebuild_Image, using the provided RebuildImage0
func (t *Rebuild_Image) MergeRebuildImage0(v RebuildImage0) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
// AsRebuildImage1 returns the union data inside the Rebuild_Image as a RebuildImage1
func (t Rebuild_Image) AsRebuildImage1() (RebuildImage1, error) {
var body RebuildImage1
err := json.Unmarshal(t.union, &body)
return body, err
}
// FromRebuildImage1 overwrites any union data inside the Rebuild_Image as the provided RebuildImage1
func (t *Rebuild_Image) FromRebuildImage1(v RebuildImage1) error {
b, err := json.Marshal(v)
t.union = b
return err
}
// MergeRebuildImage1 performs a merge with any union data inside the Rebuild_Image, using the provided RebuildImage1
func (t *Rebuild_Image) MergeRebuildImage1(v RebuildImage1) error {
b, err := json.Marshal(v)
if err != nil {
return err
}
merged, err := runtime.JSONMerge(t.union, b)
t.union = merged
return err
}
func (t Rebuild_Image) MarshalJSON() ([]byte, error) {
b, err := t.union.MarshalJSON()
return b, err
}
func (t *Rebuild_Image) UnmarshalJSON(b []byte) error {
err := t.union.UnmarshalJSON(b)
return err
}
// AsRestoreImage0 returns the union data inside the Restore_Image as a RestoreImage0
func (t Restore_Image) AsRestoreImage0() (RestoreImage0, error) {
var body RestoreImage0
@@ -1,26 +0,0 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package data_sources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func ServerDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Required: true,
Description: "The ID of the server to fetch.",
MarkdownDescription: "The ID of the server to fetch.",
},
},
}
}
type ServerModel struct {
Id types.Int64 `tfsdk:"id"`
}
@@ -1,26 +0,0 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package data_sources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)
func SshKeyDataSourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Required: true,
Description: "The ID or fingerprint of the SSH Key to fetch.",
MarkdownDescription: "The ID or fingerprint of the SSH Key to fetch.",
},
},
}
}
type SshKeyModel struct {
Id types.Int64 `tfsdk:"id"`
}
@@ -0,0 +1,104 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
)
var (
_ datasource.DataSource = &loadBalancerDataSource{}
_ datasource.DataSourceWithConfigure = &loadBalancerDataSource{}
)
func NewLoadBalancerDataSource() datasource.DataSource {
return &loadBalancerDataSource{}
}
type loadBalancerDataSource struct {
bc *BinarylaneClient
}
type loadBalancerDataSourceModel struct {
resources.LoadBalancerModel
}
func (d *loadBalancerDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_load_balancer"
}
func (d *loadBalancerDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
func (d *loadBalancerDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(
resources.LoadBalancerResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"id"},
},
)
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
// resp.Schema.Description = "TODO"
}
func (d *loadBalancerDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data loadBalancerDataSourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
lbResp, err := d.bc.client.GetLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if lbResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading load balancer",
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
)
return
}
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
+451
View File
@@ -0,0 +1,451 @@
package provider
import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"time"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &loadBalancerResource{}
_ resource.ResourceWithConfigure = &loadBalancerResource{}
_ resource.ResourceWithImportState = &loadBalancerResource{}
)
func NewLoadBalancerResource() resource.Resource {
return &loadBalancerResource{}
}
type loadBalancerResource struct {
bc *BinarylaneClient
}
type loadBalancerResourceModel struct {
resources.LoadBalancerModel
Timeouts timeouts.Value `tfsdk:"timeouts"`
}
func (r *loadBalancerResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_load_balancer"
}
func (d *loadBalancerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (r *loadBalancerResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.LoadBalancerResourceSchema(ctx)
// resp.Schema.Description = "TODO"
// Overrides
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
}
region := resp.Schema.Attributes["region"]
resp.Schema.Attributes["region"] = &schema.StringAttribute{
Description: region.GetDescription(),
MarkdownDescription: region.GetMarkdownDescription(),
Optional: true,
Computed: false, // region is not computed, defined at creation
PlanModifiers: []planmodifier.String{ // region is not allowed to be changed
stringplanmodifier.RequiresReplace(),
},
}
resp.Schema.Attributes["timeouts"] =
timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
})
}
func (r *loadBalancerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data loadBalancerResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
forwardingRules := []binarylane.ForwardingRule{}
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, &forwardingRules, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
serverIds := []int64{}
diags = data.ServerIds.ElementsAs(ctx, &serverIds, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
body := binarylane.CreateLoadBalancerRequest{
Name: data.Name.ValueString(),
ForwardingRules: &forwardingRules,
ServerIds: &serverIds,
}
if data.HealthCheck.Path.ValueStringPointer() != nil || data.HealthCheck.Protocol.ValueStringPointer() != nil {
body.HealthCheck = &binarylane.HealthCheck{
Path: data.HealthCheck.Path.ValueStringPointer(),
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
}
}
tflog.Info(ctx, fmt.Sprintf("Creating Load Balancer: name=%s", data.Name.ValueString()))
lbResp, err := r.bc.client.PostLoadBalancersWithResponse(ctx, body)
if err != nil {
tflog.Info(ctx, fmt.Sprintf("Attempted to create new load balancer: request=%+v", body))
resp.Diagnostics.AddError(
fmt.Sprintf("Error sending request to create load balancer: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if lbResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected status code creating load balancer",
fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
)
return
}
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
// Wait for server to be ready
var createActionId int64
for _, action := range *lbResp.JSON200.Links.Actions {
if *action.Rel == "create_load_balancer" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for load balancer to be created, links.actions with rel=create_load_balancer missing from response",
fmt.Sprintf("Received %s creating new load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body))
return
}
createTimeout, diags := data.Timeouts.Create(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, createTimeout)
defer cancel()
var lastReadyResp *binarylane.GetActionsActionIdResponse
retryLoop:
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return
}
resp.Diagnostics.AddError(
"Timed out waiting for load balancer to be created",
fmt.Sprintf(
"Timed out waiting for load balancer %s to be created, last response was status=%s, body: %s",
data.Name.ValueString(),
lastReadyResp.Status(),
lastReadyResp.Body,
),
)
return
default:
readyResp, err := r.bc.client.GetActionsActionIdWithResponse(ctx, createActionId)
if err != nil {
resp.Diagnostics.AddError("Error waiting for load balancer to be created", err.Error())
return
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
break retryLoop
}
lastReadyResp = readyResp
tflog.Debug(ctx, fmt.Sprintf("Waiting for load balancer to be created: name=%s, status=%s, details: %s", data.Name.ValueString(), readyResp.Status(), readyResp.Body))
}
time.Sleep(time.Second * 5)
}
}
func (r *loadBalancerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data loadBalancerResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
lbResp, err := r.bc.client.GetLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if lbResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading load balancer",
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
)
return
}
diags := SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *loadBalancerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data loadBalancerResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
forwardingRules := &[]binarylane.ForwardingRule{}
diags := data.LoadBalancerModel.ForwardingRules.ElementsAs(ctx, forwardingRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
serverIds := &[]int64{}
diags = data.ServerIds.ElementsAs(ctx, serverIds, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating Load Balancer: name=%s", data.Name.ValueString()))
body := binarylane.UpdateLoadBalancerRequest{
Name: data.Name.ValueString(),
ForwardingRules: forwardingRules,
HealthCheck: &binarylane.HealthCheck{
Path: data.HealthCheck.Path.ValueStringPointer(),
Protocol: data.HealthCheck.Protocol.ValueStringPointer(),
},
ServerIds: serverIds,
}
lbResp, err := r.bc.client.PutLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64(), body)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading load balancer: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if lbResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading load balancer",
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body),
)
return
}
diags = SetLoadBalancerModelState(ctx, &data.LoadBalancerModel, lbResp.JSON200.LoadBalancer)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *loadBalancerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data loadBalancerResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
tflog.Debug(ctx, fmt.Sprintf("Deleting Load Balancer: name=%s", data.Name.ValueString()))
lbResp, err := r.bc.client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting load balancer: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if lbResp.StatusCode() != http.StatusNoContent {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting load balancer",
fmt.Sprintf("Received %s deleting load balancer: name=%s. Details: %s", lbResp.Status(), data.Name.ValueString(), lbResp.Body))
return
}
}
func (r *loadBalancerResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
// Import by ID
id, err := strconv.ParseInt(req.ID, 10, 32)
if err == nil {
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
resp.Diagnostics.Append(diags...)
return
}
// Import by name
var page int32 = 1
perPage := int32(200)
var loadBalancer binarylane.LoadBalancer
var nextPage bool = true
for nextPage { // Need to paginate because the API does not support filtering by name
params := binarylane.GetLoadBalancersParams{
Page: &page,
PerPage: &perPage,
}
lbResp, err := r.bc.client.GetLoadBalancersWithResponse(ctx, &params)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error getting load balancer for import: name=%s", req.ID), err.Error())
return
}
if lbResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code getting load balancer for import",
fmt.Sprintf("Received %s reading load balancer: name=%s. Details: %s", lbResp.Status(), req.ID,
lbResp.Body))
return
}
loadBalancers := *lbResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if *lb.Name == req.ID {
loadBalancer = lb
nextPage = false
break
}
}
if lbResp.JSON200.Links == nil || lbResp.JSON200.Links.Pages == nil || lbResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
if loadBalancer.Id == nil {
resp.Diagnostics.AddError(
"Could not find load balancer by name",
fmt.Sprintf("Error finding load balancer: name=%s", req.ID),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), *loadBalancer.Id)
resp.Diagnostics.Append(diags...)
}
func SetLoadBalancerModelState(ctx context.Context, data *resources.LoadBalancerModel, lb *binarylane.LoadBalancer) diag.Diagnostics {
var diags, diag diag.Diagnostics
data.Id = types.Int64Value(*lb.Id)
data.Name = types.StringValue(*lb.Name)
if lb.Region == nil {
data.Region = types.StringNull()
} else {
data.Region = types.StringValue(*lb.Region.Slug)
}
data.ServerIds, diags = types.ListValueFrom(ctx, types.Int64Type, lb.ServerIds)
if lb.HealthCheck == nil {
data.HealthCheck = resources.NewHealthCheckValueNull()
} else {
data.HealthCheck, diag = resources.NewHealthCheckValue(
resources.HealthCheckValue{}.AttributeTypes(ctx),
map[string]attr.Value{
"path": types.StringValue(*lb.HealthCheck.Path),
"protocol": types.StringValue(*lb.HealthCheck.Protocol),
},
)
diags.Append(diag...)
}
data.ForwardingRules, diag = types.ListValueFrom(ctx,
types.ObjectType{AttrTypes: resources.ForwardingRulesValue{}.AttributeTypes(ctx)},
lb.ForwardingRules,
)
diags.Append(diag...)
return diags
}
@@ -0,0 +1,149 @@
package provider
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestLoadBalancerResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
_, err := rand.Read(pw_bytes)
if err != nil {
t.Errorf("Failed to generate password: %s", err)
return
}
password := base64.URLEncoding.EncodeToString(pw_bytes)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_server" "test" {
count = 2
name = "tf-test-lb-server-${count.index}"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
public_ipv4_count = 1
}
resource "binarylane_load_balancer" "test" {
name = "tf-test-lb"
server_ids = [binarylane_server.test.0.id, binarylane_server.test.1.id]
forwarding_rules = [{ entry_protocol = "http" }]
}
data "binarylane_load_balancer" "test" {
depends_on = [binarylane_load_balancer.test]
id = binarylane_load_balancer.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_load_balancer.test", "id"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "server_ids.#", "2"),
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
resource.TestCheckResourceAttrPair("binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "id", "binarylane_load_balancer.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "name", "tf-test-lb"),
resource.TestCheckNoResourceAttr("data.binarylane_load_balancer.test", "region"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "server_ids.#", "2"),
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.0", "binarylane_server.test.0", "id"),
resource.TestCheckResourceAttrPair("data.binarylane_load_balancer.test", "server_ids.1", "binarylane_server.test.1", "id"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_load_balancer.test", "forwarding_rules.0.entry_protocol", "http"),
),
},
// Test import by ID
{
ResourceName: "binarylane_load_balancer.test",
ImportState: true,
ImportStateVerify: true,
},
// Test import by name
{
ResourceName: "binarylane_load_balancer.test",
ImportState: true,
ImportStateId: "tf-test-lb",
ImportStateVerify: true,
},
},
})
}
func init() {
resource.AddTestSweepers("load_balancer", &resource.Sweeper{
Name: "load_balancer",
Dependencies: []string{"server"},
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
ctx := context.Background()
var page int32 = 1
perPage := int32(200)
var nextPage bool = true
for nextPage {
params := binarylane.GetLoadBalancersParams{
Page: &page,
PerPage: &perPage,
}
listResp, err := client.GetLoadBalancersWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting load balancers for test sweep: %w", err)
}
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting load balancers for test sweep: %s", listResp.Body)
}
loadBalancers := *listResp.JSON200.LoadBalancers
for _, lb := range loadBalancers {
if strings.HasPrefix(*lb.Name, "tf-test-") {
deleteResp, err := client.DeleteLoadBalancersLoadBalancerIdWithResponse(ctx, *lb.Id)
if err != nil {
return fmt.Errorf("Error deleting load balancer %d for test sweep: %w", *lb.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting load balancer %d in test sweep: %s", deleteResp.StatusCode(), *lb.Id, deleteResp.Body)
}
log.Println("Deleted load balancer during test sweep:", *lb.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
return nil
},
})
}
+14 -61
View File
@@ -2,12 +2,9 @@ package provider
import (
"context"
"os"
"terraform-provider-binarylane/internal/binarylane"
"github.com/deepmap/oapi-codegen/pkg/securityprovider"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -25,8 +22,7 @@ func New(version string) func() provider.Provider {
}
type BinarylaneClient struct {
endpoint string
client *binarylane.ClientWithResponses
client *binarylane.ClientWithResponses
}
type binarylaneProvider struct {
@@ -67,68 +63,17 @@ func (p *binarylaneProvider) Configure(ctx context.Context, req provider.Configu
return
}
if config.Endpoint.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("api_endpoint"),
"Unknown Binary Lane API endpoint",
"The provider cannot create the Binary Lane API client as there is an unknown configuration value for the "+
"Binary Lane API endpoint. Either target apply the source of the value first, set the value statically in "+
"the configuration, or use the BINARYLANE_API_ENDPOINT environment variable.",
)
}
if config.Token.IsUnknown() {
resp.Diagnostics.AddAttributeError(
path.Root("api_token"),
"Unknown Binary Lane token",
"The provider cannot create the Binary Lane API client as there is an unknown configuration value for the "+
"Binary Lane API token. Either target apply the source of the value first, set the value statically in the "+
"configuration, or use the BINARYLANE_API_TOKEN environment variable.",
)
}
if resp.Diagnostics.HasError() {
return
}
// Default values to environment variables, but override
// with Terraform configuration value if set.
endpoint := os.Getenv("BINARYLANE_API_ENDPOINT")
token := os.Getenv("BINARYLANE_API_TOKEN")
if !config.Endpoint.IsNull() {
endpoint = config.Endpoint.ValueString()
}
if !config.Token.IsNull() {
token = config.Token.ValueString()
}
if endpoint == "" {
endpoint = "https://api.binarylane.com.au/v2"
}
if token == "" {
resp.Diagnostics.AddAttributeError(
path.Root("api_token"),
"Missing Binary Lane API token",
"The provider cannot create the Binary Lane API client as there is a missing or empty value for the Binary "+
"Lane API token. Set the token value in the configuration or use the BINARYLANE_API_TOKEN environment "+
"variable. If either is already set, ensure the value is not empty.",
)
}
if resp.Diagnostics.HasError() {
return
}
auth, err := securityprovider.NewSecurityProviderBearerToken(token)
if err != nil {
resp.Diagnostics.AddError("Failed to create security provider with supplied token", err.Error())
return
}
client, err := binarylane.NewClientWithResponses(endpoint, binarylane.WithRequestEditorFn(auth.Intercept))
client, err := binarylane.NewClientWithConfig(
config.Endpoint.ValueString(),
config.Token.ValueString(),
)
if err != nil {
resp.Diagnostics.AddError("Failed to create Binary Lane API client", err.Error())
return
}
binarylaneClient := BinarylaneClient{
endpoint: endpoint,
client: client,
client: client,
}
resp.DataSourceData = binarylaneClient
@@ -143,13 +88,21 @@ func (p *binarylaneProvider) Metadata(_ context.Context, _ provider.MetadataRequ
func (p *binarylaneProvider) DataSources(_ context.Context) []func() datasource.DataSource {
return []func() datasource.DataSource{
NewServerDataSource,
NewServerFirewallRulesDataSource,
NewSshKeyDataSource,
NewVpcDataSource,
NewVpcRouteEntriesDataSource,
NewLoadBalancerDataSource,
}
}
func (p *binarylaneProvider) Resources(_ context.Context) []func() resource.Resource {
return []func() resource.Resource{
NewServerResource,
NewServerFirewallRulesResource,
NewSshKeyResource,
NewVpcResource,
NewVpcRouteEntriesResource,
NewLoadBalancerResource,
}
}
+28
View File
@@ -0,0 +1,28 @@
package provider
import (
"github.com/hashicorp/terraform-plugin-framework/providerserver"
"github.com/hashicorp/terraform-plugin-go/tfprotov6"
)
const (
providerConfig = `
provider "binarylane" {
# api_token = "test123"
# api_endpoint = "https://test.binarylane.internal.au/v2"
}
# End of provider config
`
)
var (
// testAccProtoV6ProviderFactories are used to instantiate a provider during
// acceptance testing. The factory function will be invoked for every Terraform
// CLI command executed to create a provider server to which the CLI can
// reattach.
testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){
"binarylane": providerserver.NewProtocol6WithError(New("test")()),
}
)
+138 -14
View File
@@ -2,48 +2,172 @@ package provider
import (
"context"
"terraform-provider-binarylane/internal/data_sources"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var _ datasource.DataSource = (*serverDataSource)(nil)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &serverDataSource{}
_ datasource.DataSourceWithConfigure = &serverDataSource{}
)
func NewServerDataSource() datasource.DataSource {
return &serverDataSource{}
}
type serverDataSource struct{}
type serverDataSource struct {
bc *BinarylaneClient
}
type serverDataModel struct {
resources.ServerModel
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
Permalink types.String `tfsdk:"permalink"`
}
func (d *serverDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (d *serverDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server"
}
func (d *serverDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
},
ds, err := convertResourceSchemaToDataSourceSchema(
resources.ServerResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"id"},
ExcludedAttributes: &[]string{"password"},
})
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "Provides a Binary Lane Server data source. This can be used to read existing server details."
// Overrides
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
Required: true, // ID is required to find the server
}
// Additional attributes
resp.Schema.Attributes["permalink"] = &schema.StringAttribute{
Description: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
MarkdownDescription: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
Computed: true,
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
resp.Schema.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
ElementType: types.StringType,
Computed: true,
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
resp.Schema.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
Description: privateIpv4AddressesDescription,
MarkdownDescription: privateIpv4AddressesDescription,
ElementType: types.StringType,
Computed: true,
}
}
func (d *serverDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data data_sources.ServerModel
var data serverDataModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
// Read server
serverResp, err := d.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server: name=%s, id=%s", data.Name.ValueString(), data.Id.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status code reading server: name=%s, id=%s", data.Name.ValueString(), data.Id.String()),
string(serverResp.Body),
)
return
}
// Example data value setting
// data.Id = types.StringValue("example-id")
// Set data values
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
var publicIpv4Addresses []string
var privateIpv4Addresses []string
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
// Get user data script
userDataResp, err := d.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server user data: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
}
if userDataResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status %d reading server user data: name=%s, id=%s", userDataResp.StatusCode(), data.Name.ValueString(), data.Id.String()),
string(userDataResp.Body),
)
return
}
data.UserData = types.StringPointerValue(userDataResp.JSON200.UserData)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
@@ -0,0 +1,105 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ datasource.DataSource = &serverFirewallRulesDataSource{}
_ datasource.DataSourceWithConfigure = &serverFirewallRulesDataSource{}
)
func NewServerFirewallRulesDataSource() datasource.DataSource {
return &serverFirewallRulesDataSource{}
}
type serverFirewallRulesDataSource struct {
bc *BinarylaneClient
}
type serverFirewallRulesDataSourceModel struct {
resources.ServerFirewallRulesModel
}
func (d *serverFirewallRulesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server_firewall_rules"
}
func (d *serverFirewallRulesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(
resources.ServerFirewallRulesResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"server_id"},
},
)
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
resp.Schema.Description = "TODO"
}
func (d *serverFirewallRulesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data serverFirewallRulesDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()))
fwResp, err := d.bc.client.GetServersServerIdAdvancedFirewallRulesWithResponse(ctx, data.ServerId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()),
err.Error(),
)
return
}
if fwResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server firewall rules",
fmt.Sprintf("Received %s reading server firewall rules: server_id=%d. Details: %s", fwResp.Status(), data.ServerId.ValueInt64(), fwResp.Body))
return
}
firewallRulesValue, diags := GetFirewallRulesState(ctx, &fwResp.JSON200.FirewallRules)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.FirewallRules = firewallRulesValue
// Save into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (d *serverFirewallRulesDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
@@ -0,0 +1,305 @@
package provider
import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &serverFirewallRulesResource{}
_ resource.ResourceWithConfigure = &serverFirewallRulesResource{}
_ resource.ResourceWithImportState = &serverFirewallRulesResource{}
)
func NewServerFirewallRulesResource() resource.Resource {
return &serverFirewallRulesResource{}
}
type serverFirewallRulesResource struct {
bc *BinarylaneClient
}
type serverFirewallRulesResourceModel struct {
resources.ServerFirewallRulesModel
}
func (d *serverFirewallRulesResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (r *serverFirewallRulesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_server_firewall_rules"
}
func (r *serverFirewallRulesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.ServerFirewallRulesResourceSchema(ctx)
// resp.Schema.Description = "TODO"
// Overrides
serverId := resp.Schema.Attributes["server_id"]
resp.Schema.Attributes["server_id"] = &schema.Int64Attribute{
Description: serverId.GetDescription(),
MarkdownDescription: serverId.GetMarkdownDescription(),
Required: true, // Server ID is required to define the firewall rules
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
}
}
func (r *serverFirewallRulesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
firewallRules := []binarylane.AdvancedFirewallRule{}
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if len(firewallRules) == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Creating server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: firewallRules,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating server firewall rules",
fmt.Sprintf("Received %s creating server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()))
fwResp, err := r.bc.client.GetServersServerIdAdvancedFirewallRulesWithResponse(ctx, data.ServerId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server firewall rules: server_id=%d", data.ServerId.ValueInt64()),
err.Error(),
)
return
}
if fwResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server firewall rules",
fmt.Sprintf("Received %s reading server firewall rules: server_id=%d. Details: %s", fwResp.Status(), data.ServerId.ValueInt64(), fwResp.Body))
return
}
firewallRulesValue, diags := GetFirewallRulesState(ctx, &fwResp.JSON200.FirewallRules)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.FirewallRules = firewallRulesValue
// Save into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
firewallRules := []binarylane.AdvancedFirewallRule{}
diags := data.FirewallRules.ElementsAs(ctx, &firewallRules, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if len(firewallRules) == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: firewallRules,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating server firewall rules",
fmt.Sprintf("Received %s updating server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverFirewallRulesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data serverFirewallRulesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
tflog.Debug(ctx, fmt.Sprintf("Deleting server firewall rules: server_id=%s", data.ServerId.String()))
serverResp, err := r.bc.client.PostServersServerIdActionsChangeAdvancedFirewallRulesWithResponse(
ctx,
data.ServerId.ValueInt64(),
binarylane.PostServersServerIdActionsChangeAdvancedFirewallRulesJSONRequestBody{
Type: "change_advanced_firewall_rules",
FirewallRules: []binarylane.AdvancedFirewallRule{},
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting server firewall rules: server_id=%s", data.ServerId.String()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting server firewall rules",
fmt.Sprintf("Received %s deleting server firewall rules: server_id=%s. Details: %s", serverResp.Status(), data.ServerId.String(), serverResp.Body))
return
}
}
func GetFirewallRulesState(ctx context.Context, firewallRules *[]binarylane.AdvancedFirewallRule) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics
firewallRulesValue := resources.FirewallRulesValue{}
var firewallRulesValues []resources.FirewallRulesValue
if *firewallRules == nil || len(*firewallRules) == 0 {
firewallRulesValues = []resources.FirewallRulesValue{}
} else {
for _, rule := range *firewallRules {
destinationAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.DestinationAddresses)
diags.Append(diag...)
var destinationPorts basetypes.ListValue
if rule.DestinationPorts != nil {
destinationPorts, diag = types.ListValueFrom(ctx, types.StringType, rule.DestinationPorts)
diags.Append(diag...)
}
sourceAddresses, diag := types.ListValueFrom(ctx, types.StringType, rule.SourceAddresses)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(firewallRulesValue.Type(ctx)), diags
}
ruleValue := resources.NewFirewallRulesValueMust(firewallRulesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringPointerValue(rule.Description),
"action": types.StringValue(string(rule.Action)),
"protocol": types.StringValue(string(rule.Protocol)),
"destination_addresses": destinationAddresses,
"destination_ports": destinationPorts,
"source_addresses": sourceAddresses,
})
firewallRulesValues = append(firewallRulesValues, ruleValue)
}
}
firewallRulesListValue, diag := types.ListValueFrom(ctx, firewallRulesValue.Type(ctx), firewallRulesValues)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(firewallRulesValue.Type(ctx)), diags
}
return firewallRulesListValue, diags
}
func (r *serverFirewallRulesResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
id, err := strconv.ParseInt(req.ID, 10, 32)
if err != nil {
resp.Diagnostics.AddError(
"Error importing server firewall rules",
"Could not import server firewall rules, unexpected error (ID should be an integer): "+err.Error(),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("server_id"), id)
resp.Diagnostics.Append(diags...)
}
@@ -0,0 +1,141 @@
package provider
import (
"crypto/rand"
"encoding/base64"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestServerFirewallRulesResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
rand.Read(pw_bytes)
password := base64.URLEncoding.EncodeToString(pw_bytes)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_server" "test" {
name = "tf-test-server-fw-rules"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
public_ipv4_count = 0
}
resource "binarylane_server_firewall_rules" "test" {
server_id = binarylane_server.test.id
firewall_rules = [
{
description = "Allow SSH"
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.test.private_ipv4_addresses.0]
destination_ports = ["22"]
action = "accept"
}
]
}
data "binarylane_server_firewall_rules" "test" {
server_id = binarylane_server_firewall_rules.test.server_id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "server_id"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow SSH"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "22"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
// Verify data source values
resource.TestCheckResourceAttrSet("data.binarylane_server_firewall_rules.test", "server_id"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow SSH"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "22"),
resource.TestCheckResourceAttr("data.binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
),
},
// ImportState testing
// {
// ResourceName: "binarylane_server.test",
// ImportState: true,
// ImportStateVerify: true,
// },
{
ResourceName: "binarylane_server_firewall_rules.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "server_id",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
resourceState := s.RootModule().Resources["binarylane_server_firewall_rules.test"]
return resourceState.Primary.Attributes["server_id"], nil
},
},
// Update and Read testing
{
Config: providerConfig + `
resource "binarylane_server" "test" {
name = "tf-test-server-fw-rules"
region = "per"
image = "debian-12"
size = "std-min"
password = "` + password + `"
public_ipv4_count = 0
}
resource "binarylane_server_firewall_rules" "test" {
server_id = binarylane_server.test.id
firewall_rules = [
{
description = "Allow HTTP" # Updated description
protocol = "tcp"
source_addresses = ["0.0.0.0/0"]
destination_addresses = [binarylane_server.test.private_ipv4_addresses.0]
destination_ports = ["80"] # Updated port
action = "accept"
},
]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values updated
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "server_id"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.description", "Allow HTTP"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.protocol", "tcp"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.source_addresses.0", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.#", "1"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.destination_ports.0", "80"),
resource.TestCheckResourceAttr("binarylane_server_firewall_rules.test", "firewall_rules.0.action", "accept"),
),
},
// Delete testing automatically occurs in TestCase
},
})
}
+634 -49
View File
@@ -4,21 +4,39 @@ import (
"context"
"fmt"
"net/http"
"slices"
"strconv"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"time"
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
"github.com/hashicorp/terraform-plugin-framework-validators/int32validator"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &serverResource{}
_ resource.ResourceWithConfigure = &serverResource{}
// _ resource.ResourceWithImportState = &serverResource{}
_ resource.Resource = &serverResource{}
_ resource.ResourceWithConfigure = &serverResource{}
_ resource.ResourceWithImportState = &serverResource{}
_ resource.ResourceWithModifyPlan = &serverResource{}
)
// Helper function to simplify the provider implementation.
@@ -30,6 +48,17 @@ type serverResource struct {
bc *BinarylaneClient
}
type serverModel struct {
resources.ServerModel
PublicIpv4Count types.Int32 `tfsdk:"public_ipv4_count"`
PublicIpv4Addresses types.List `tfsdk:"public_ipv4_addresses"`
PrivateIPv4Addresses types.List `tfsdk:"private_ipv4_addresses"`
Permalink types.String `tfsdk:"permalink"`
Password types.String `tfsdk:"password"`
PasswordChangeSupported types.Bool `tfsdk:"password_change_supported"`
Timeouts timeouts.Value `tfsdk:"timeouts"`
}
func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -38,7 +67,7 @@ func (d *serverResource) Configure(_ context.Context, req resource.ConfigureRequ
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
@@ -57,27 +86,220 @@ func (r *serverResource) Schema(ctx context.Context, _ resource.SchemaRequest, r
resp.Schema.Description = "Provides a Binary Lane Server resource. This can be used to create and delete servers."
// Overrides
pw := resp.Schema.Attributes["password"]
resp.Schema.Attributes["password"] = &schema.StringAttribute{
Description: pw.GetDescription(),
MarkdownDescription: pw.GetMarkdownDescription(),
Optional: true,
Computed: true,
Sensitive: true, // Add sensitive to password
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
}
image := resp.Schema.Attributes["image"]
resp.Schema.Attributes["image"] = &schema.StringAttribute{
Description: image.GetDescription(),
MarkdownDescription: image.GetMarkdownDescription(),
Required: image.IsRequired(),
Optional: image.IsOptional(),
Computed: image.IsComputed(),
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
}
backups := resp.Schema.Attributes["backups"]
resp.Schema.Attributes["backups"] = &schema.BoolAttribute{
Description: backups.GetDescription(),
MarkdownDescription: backups.GetMarkdownDescription(),
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false), // Add default to backups
Optional: backups.IsOptional(),
Computed: backups.IsComputed(),
Default: booldefault.StaticBool(false), // Add default to backups
PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()}, // TODO (#37)
}
user_data := resp.Schema.Attributes["user_data"]
resp.Schema.Attributes["user_data"] = &schema.StringAttribute{
Description: user_data.GetDescription(),
MarkdownDescription: user_data.GetMarkdownDescription(),
Optional: true, // Optional as not all servers have an initialization script
Computed: false, // User defined
}
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Optional: vpcId.IsOptional(),
Computed: false, // VPC ID is not computed, defined at creation
PlanModifiers: []planmodifier.Int64{int64planmodifier.RequiresReplace()},
}
portBlocking := resp.Schema.Attributes["port_blocking"]
resp.Schema.Attributes["port_blocking"] = &schema.BoolAttribute{
Description: portBlocking.GetDescription(),
MarkdownDescription: portBlocking.GetMarkdownDescription(),
Optional: portBlocking.IsOptional(),
Computed: portBlocking.IsComputed(),
Default: booldefault.StaticBool(true), // Add default to port_blocking
PlanModifiers: []planmodifier.Bool{boolplanmodifier.RequiresReplace()},
}
sshKeys := resp.Schema.Attributes["ssh_keys"]
resp.Schema.Attributes["ssh_keys"] = &schema.ListAttribute{
ElementType: types.Int64Type,
Description: sshKeys.GetMarkdownDescription(),
MarkdownDescription: sshKeys.GetDescription(),
Optional: sshKeys.IsOptional(),
Computed: false, // SSH keys are not computed, defined at creation
Validators: []validator.List{
listvalidator.ValueInt64sAre(int64validator.AtLeast(1)),
},
}
// Additional attributes
pwDescription :=
"If this is provided the specified or default remote user's account password will be set to this value. " +
"Only valid if the server supports password change actions. If omitted and the server supports password " +
"change actions a random password will be generated and emailed to the account email address."
resp.Schema.Attributes["password"] = &schema.StringAttribute{
Description: pwDescription,
MarkdownDescription: pwDescription,
Optional: true, // Password optional, if not set will be emailed to user
Computed: false, // Computed must be false to allow server to be created without password
Sensitive: true, // Mark password as sensitive
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
}
publicIpv4CountDescription := "The number of public IPv4 addresses to assign to the server. If this is not provided, the " +
"server will be created with the default number of public IPv4 addresses."
resp.Schema.Attributes["public_ipv4_count"] = &schema.Int32Attribute{
Description: publicIpv4CountDescription,
MarkdownDescription: publicIpv4CountDescription,
Required: true,
Optional: false,
Computed: false,
// Default: int32default.StaticInt32(0), // TODO: Uncomment with 1.0 release (see issue #30)
Validators: []validator.Int32{
int32validator.AtLeast(0),
},
}
publicIpv4AddressesDescription := "The public IPv4 addresses assigned to the server."
resp.Schema.Attributes["public_ipv4_addresses"] = &schema.ListAttribute{
Description: publicIpv4AddressesDescription,
MarkdownDescription: publicIpv4AddressesDescription,
ElementType: types.StringType,
// read only
Optional: false,
Required: false,
Computed: true,
}
privateIpv4AddressesDescription := "The private IPv4 addresses assigned to the server."
resp.Schema.Attributes["private_ipv4_addresses"] = &schema.ListAttribute{
Description: privateIpv4AddressesDescription,
MarkdownDescription: privateIpv4AddressesDescription,
ElementType: types.StringType,
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.List{
listplanmodifier.UseStateForUnknown(),
},
}
resp.Schema.Attributes["permalink"] = &schema.StringAttribute{
Description: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
MarkdownDescription: "A randomly generated two-word identifier assigned to servers in regions that support this feature",
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
}
pwChangeDescription := "If this is true then the `password` attribute can be changed with Terraform. " +
"If this is false then the `password` attribute can only be replaced with a null/empty value, which will clear " +
"the root/administrator password allowing the password to be changed via the web console."
resp.Schema.Attributes["password_change_supported"] = &schema.BoolAttribute{
Description: pwChangeDescription,
MarkdownDescription: pwChangeDescription,
// read only
Optional: false,
Required: false,
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
}
resp.Schema.Attributes["timeouts"] =
timeouts.Attributes(ctx, timeouts.Opts{
Create: true,
Update: true,
})
}
func (r *serverResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
// Creation or destruction plan
if req.Plan.Raw.IsNull() || req.State.Raw.IsNull() {
return
}
var plan, state serverModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// When IP count is changed, plan should show addition/removal of public IPs
plannedPublicIpV4Count := int(plan.PublicIpv4Count.ValueInt32())
plannedPublicIpAddresses := make([]attr.Value, plannedPublicIpV4Count)
stateIpV4Addresses := []*string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &stateIpV4Addresses, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
for i := range plannedPublicIpAddresses {
if i < len(stateIpV4Addresses) {
plannedPublicIpAddresses[i] = types.StringValue(*stateIpV4Addresses[i])
} else {
plannedPublicIpAddresses[i] = types.StringUnknown()
}
}
plan.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, plannedPublicIpAddresses)
resp.Diagnostics.Append(diags...)
// Add warning if rebuild is required
attrsRequiringRebuild := attrsRequiringRebuild(&plan, &state)
if len(attrsRequiringRebuild) > 0 {
resp.Diagnostics.AddWarning(
"Server Rebuild Required",
fmt.Sprintf(
"Server %d will lose all data if this Terraform plan is applied, because of modified attribute(s): %s",
state.Id.ValueInt64(),
strings.Join(attrsRequiringRebuild, ", "),
),
)
}
// Save data into Terraform state
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
@@ -86,20 +308,40 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
return
}
timeout, diags := data.Timeouts.Create(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Create API call logic
tflog.Debug(ctx, fmt.Sprintf("Creating server: name=%s", data.Name.ValueString()))
sshKeys := []int{}
diags = data.SshKeys.ElementsAs(ctx, &sshKeys, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
body := binarylane.CreateServerRequest{
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
Name: data.Name.ValueStringPointer(),
Image: data.Image.ValueString(),
Region: data.Region.ValueString(),
Size: data.Size.ValueString(),
UserData: data.UserData.ValueStringPointer(),
VpcId: data.VpcId.ValueInt64Pointer(),
PortBlocking: data.PortBlocking.ValueBoolPointer(),
SshKeys: &sshKeys,
Options: &binarylane.SizeOptionsRequest{
Ipv4Addresses: data.PublicIpv4Count.ValueInt32Pointer(),
},
}
if data.Password.IsNull() {
data.Password = types.StringNull()
} else {
body.Password = data.Password.ValueStringPointer()
ctx = tflog.MaskMessageStrings(ctx, data.Password.String())
}
serverResp, err := r.bc.client.PostServersWithResponse(ctx, body)
@@ -110,92 +352,343 @@ func (r *serverResource) Create(ctx context.Context, req resource.CreateRequest,
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating server",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body),
)
return
}
// Wait for server to be ready
var createActionId int64
for _, action := range *serverResp.JSON200.Links.Actions {
if *action.Rel == "create" {
createActionId = *action.Id
break
}
}
if createActionId == 0 {
resp.Diagnostics.AddError(
"Unable to wait for server to be created, links.actions with rel=create missing from response",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", serverResp.Status(), data.Name.ValueString(), serverResp.Body))
return
}
assignInt64(serverResp.JSON200.Server.Id, &data.Id)
assignStr(serverResp.JSON200.Server.Name, &data.Name)
assignStr(serverResp.JSON200.Server.Image.Slug, &data.Image)
assignStr(serverResp.JSON200.Server.Region.Slug, &data.Region)
assignStr(serverResp.JSON200.Server.Size.Slug, &data.Size)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
// assignBool(&serverResp.JSON200.Server.Networks.PortBlocking, &plan.PortBlocking)
if resp.Diagnostics.HasError() {
return
err = r.waitForServerAction(ctx, *serverResp.JSON200.Server.Id, createActionId, "create")
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be created", err.Error())
}
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
publicIpv4Addresses := []string{}
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
diags := req.State.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
tflog.Debug(ctx, fmt.Sprintf("Reading server: name=%s", data.Id.String()))
tflog.Debug(ctx, fmt.Sprintf("Reading server: id=%s, name=%s", data.Id.String(), data.Name.ValueString()))
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server: name=%s", data.Id.String()),
fmt.Sprintf("Error reading server: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading server",
fmt.Sprintf("Received %s reading server: name=%s. Details: %s", serverResp.Status(), data.Id.String(), serverResp.Body))
fmt.Sprintf("Unexpected HTTP status code %s reading server: name=%s, id=%s", serverResp.Status(), data.Name.ValueString(), data.Id.String()),
string(serverResp.Body),
)
return
}
assignInt64(serverResp.JSON200.Server.Id, &data.Id)
assignStr(serverResp.JSON200.Server.Name, &data.Name)
data.Id = types.Int64Value(*serverResp.JSON200.Server.Id)
data.Name = types.StringValue(*serverResp.JSON200.Server.Name)
data.Image = types.StringValue(*serverResp.JSON200.Server.Image.Slug)
data.Region = types.StringValue(*serverResp.JSON200.Server.Region.Slug)
data.Size = types.StringValue(*serverResp.JSON200.Server.Size.Slug)
data.Backups = types.BoolValue(serverResp.JSON200.Server.NextBackupWindow != nil)
data.PortBlocking = types.BoolValue(serverResp.JSON200.Server.Networks.PortBlocking)
data.VpcId = types.Int64PointerValue(serverResp.JSON200.Server.VpcId)
data.Permalink = types.StringValue(*serverResp.JSON200.Server.Permalink)
data.PasswordChangeSupported = types.BoolValue(*serverResp.JSON200.Server.PasswordChangeSupported)
publicIpv4Addresses := []string{}
privateIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
} else {
privateIpv4Addresses = append(privateIpv4Addresses, v4address.IpAddress)
}
}
data.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PrivateIPv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, privateIpv4Addresses)
resp.Diagnostics.Append(diags...)
data.PublicIpv4Count = types.Int32Value(int32(len(publicIpv4Addresses)))
if resp.Diagnostics.HasError() {
return
}
// Get user data script
userDataResp, err := r.bc.client.GetServersServerIdUserDataWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading server user data: id=%s, name=%s", data.Id.String(), data.Name.ValueString()),
err.Error(),
)
return
}
if userDataResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
fmt.Sprintf("Unexpected HTTP status %d reading server user data: name=%s, id=%s", userDataResp.StatusCode(), data.Name.ValueString(), data.Id.String()),
string(userDataResp.Body),
)
return
}
data.UserData = types.StringPointerValue(userDataResp.JSON200.UserData)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *serverResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data resources.ServerModel
var plan, state serverModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
// TODO
timeout, diags := plan.Timeouts.Update(ctx, 20*time.Minute)
resp.Diagnostics.Append(diags...)
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
rebuildNeeded := len(attrsRequiringRebuild(&plan, &state)) > 0
// Rename
if !plan.Name.Equal(state.Name) && !rebuildNeeded {
renameResp, err := r.bc.client.PostServersServerIdActionsRenameWithResponse(
ctx,
state.Id.ValueInt64(),
binarylane.PostServersServerIdActionsRenameJSONRequestBody{
Type: "rename",
Name: plan.Name.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error renaming server: server_id=%s", state.Id.String()),
err.Error(),
)
return
}
if renameResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code renaming server",
fmt.Sprintf("Received %s renaming server: server_id=%s. Details: %s", renameResp.Status(), state.Id.String(), renameResp.Body))
return
}
if *renameResp.JSON200.Action.Status == "errored" {
resp.Diagnostics.AddError(
"Unexpected response with \"errored\" status when renaming server",
fmt.Sprintf("Received %s renaming server: server_id=%s. Details: %s", renameResp.Status(), state.Id.String(), renameResp.Body))
return
}
// TODO - Currently, the API does not support polling for the rename to complete, because the action ID returns a 404 response (see #13)
state.Name = types.StringValue(plan.Name.ValueString())
}
// Resize operation
if !plan.Size.Equal(state.Size) || !plan.PublicIpv4Count.Equal(state.PublicIpv4Count) || !plan.Image.Equal(state.Image) {
resizeReq := &binarylane.PostServersServerIdActionsResizeJSONRequestBody{
Type: "resize",
}
if !plan.Size.Equal(state.Size) {
resizeReq.Size = plan.Size.ValueStringPointer()
state.Size = plan.Size
}
if !plan.PublicIpv4Count.Equal(state.PublicIpv4Count) {
resizeReq.Options = &binarylane.ChangeSizeOptionsRequest{
Ipv4Addresses: plan.PublicIpv4Count.ValueInt32Pointer(),
}
if plan.PublicIpv4Count.ValueInt32() < state.PublicIpv4Count.ValueInt32() {
currentIps := []string{}
diags := state.PublicIpv4Addresses.ElementsAs(ctx, &currentIps, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
removedIps := currentIps[plan.PublicIpv4Count.ValueInt32():state.PublicIpv4Count.ValueInt32()]
resizeReq.Options.Ipv4AddressesToRemove = &removedIps
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
state.PublicIpv4Count = plan.PublicIpv4Count
state.PublicIpv4Addresses = plan.PublicIpv4Addresses
}
if !plan.Image.Equal(state.Image) {
resizeReq.ChangeImage = &binarylane.ChangeImage{
Image: plan.Image.ValueStringPointer(),
}
state.Image = plan.Image
}
tflog.Info(ctx, fmt.Sprintf("Resizing server: server_id=%s", state.Id.String()))
resizeResp, err := r.bc.client.PostServersServerIdActionsResizeWithResponse(
ctx,
state.Id.ValueInt64(),
*resizeReq,
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error resizing server: server_id=%s", state.Id.String()),
err.Error(),
)
return
}
if resizeResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code resizing server",
fmt.Sprintf("Received %s resizing server: server_id=%s. Details: %s", resizeResp.Status(), state.Id.String(), resizeResp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *resizeResp.JSON200.Action.Id, "resize")
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be resized", err.Error())
return
}
// Success
if state.PublicIpv4Addresses.IsUnknown() || listContainsUnknown(ctx, state.PublicIpv4Addresses) {
// New IPs may have been allocated, so we need to check the server again
serverResp, err := r.bc.client.GetServersServerIdWithResponse(ctx, state.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError("Error checking IPs for server after resize", err.Error())
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError("Unexpected HTTP status code checking IPs for server after resize",
fmt.Sprintf("Received %s checking IPs for server after resize: name=%s. Details: %s", serverResp.Status(), state.Name.ValueString(), serverResp.Body))
return
}
publicIpv4Addresses := []string{}
for _, v4address := range serverResp.JSON200.Server.Networks.V4 {
if v4address.Type == "public" {
publicIpv4Addresses = append(publicIpv4Addresses, v4address.IpAddress)
}
}
state.PublicIpv4Addresses, diags = types.ListValueFrom(ctx, types.StringType, publicIpv4Addresses)
resp.Diagnostics.Append(diags...)
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
// Rebuild operation
if !plan.SshKeys.Equal(state.SshKeys) || !plan.UserData.IsNull() && !plan.UserData.Equal(state.UserData) {
var rebuildReq *binarylane.PostServersServerIdActionsRebuildJSONRequestBody
sshKeys := []int{}
diags := plan.SshKeys.ElementsAs(ctx, &sshKeys, false)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
rebuildReq = &binarylane.PostServersServerIdActionsRebuildJSONRequestBody{
Type: "rebuild",
Options: &binarylane.ImageOptions{
Name: plan.Name.ValueStringPointer(),
Password: plan.Password.ValueStringPointer(),
UserData: plan.UserData.ValueStringPointer(),
SshKeys: &sshKeys,
},
}
rebuildResp, err := r.bc.client.PostServersServerIdActionsRebuildWithResponse(
ctx,
state.Id.ValueInt64(),
*rebuildReq,
)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error rebuilding server: server_id=%s", state.Id.String()),
err.Error(),
)
return
}
if rebuildResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code rebuilding server",
fmt.Sprintf("Received %s rebuilding server: server_id=%s. Details: %s", rebuildResp.Status(), state.Id.String(), rebuildResp.Body))
return
}
err = r.waitForServerAction(ctx, state.Id.ValueInt64(), *rebuildResp.JSON200.Action.Id, "rebuild")
if err != nil {
resp.Diagnostics.AddError("Error waiting for server to be rebuilt", err.Error())
return
}
state.Name = plan.Name
state.Password = plan.Password
state.UserData = plan.UserData
state.SshKeys = plan.SshKeys
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
}
func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resources.ServerModel
var data serverModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
@@ -224,3 +717,95 @@ func (r *serverResource) Delete(ctx context.Context, req resource.DeleteRequest,
return
}
}
func (r *serverResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
// Import by ID
id, err := strconv.ParseInt(req.ID, 10, 32)
if err == nil {
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
resp.Diagnostics.Append(diags...)
return
}
// Import by name
name := req.ID
params := binarylane.GetServersParams{
Hostname: &name,
}
serverResp, err := r.bc.client.GetServersWithResponse(ctx, &params)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error getting server: hostname=%s", name), err.Error())
return
}
if serverResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code getting server",
fmt.Sprintf("Received %s reading server: hostname=%s. Details: %s", serverResp.Status(), name,
serverResp.Body))
return
}
servers := *serverResp.JSON200.Servers
idx := slices.IndexFunc(servers, func(s binarylane.Server) bool { return *s.Name == name })
if idx == -1 {
resp.Diagnostics.AddError(
"Could not find server by hostname",
fmt.Sprintf("Error finding server: hostname=%s", name),
)
return
}
server := servers[idx]
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(*server.Id))
resp.Diagnostics.Append(diags...)
}
func (r *serverResource) waitForServerAction(ctx context.Context, serverId int64, actionId int64, actionType string) error {
var lastReadyResp *binarylane.GetServersServerIdActionsActionIdResponse
for {
select {
case <-ctx.Done():
if lastReadyResp == nil {
return fmt.Errorf("timed out waiting for server %d to %s", serverId, actionType)
} else {
return fmt.Errorf("timed out waiting for server %d to %s, last response was status=%s, body: %s", serverId, actionType, lastReadyResp.Status(), lastReadyResp.Body)
}
default:
readyResp, err := r.bc.client.GetServersServerIdActionsActionIdWithResponse(ctx, serverId, actionId)
if err != nil {
return fmt.Errorf("unexpected error waiting for server %d to %s: %w", serverId, actionType, err)
}
if readyResp.StatusCode() == http.StatusOK && *readyResp.JSON200.Action.Status == binarylane.Errored {
return fmt.Errorf("server %d failed to %s with error: %s", serverId, actionType, *readyResp.JSON200.Action.ResultData)
}
if readyResp.StatusCode() == http.StatusOK && readyResp.JSON200.Action.CompletedAt != nil {
return nil
}
lastReadyResp = readyResp
tflog.Debug(ctx, fmt.Sprintf("waiting for server %d to %s: last response was status=%s, details: %s", serverId, actionType, readyResp.Status(), readyResp.Body))
}
time.Sleep(time.Second * 5)
}
}
func attrsRequiringRebuild(plan *serverModel, state *serverModel) []string {
attrs := []string{}
if !plan.SshKeys.Equal(state.SshKeys) {
attrs = append(attrs, "ssh_keys")
}
if !plan.Image.Equal(state.Image) {
attrs = append(attrs, "image")
}
if !plan.UserData.IsNull() && !plan.UserData.Equal(state.UserData) {
attrs = append(attrs, "user_data")
}
return attrs
}
+239
View File
@@ -0,0 +1,239 @@
package provider
import (
"context"
"crypto/rand"
"encoding/base64"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestServerResource(t *testing.T) {
// Must assign a password to the server or Binary Lane will send emails
pw_bytes := make([]byte, 12)
_, err := rand.Read(pw_bytes)
if err != nil {
t.Errorf("Failed to generate password: %s", err)
return
}
password := base64.URLEncoding.EncodeToString(pw_bytes)
sshPublicKeyInitial := GeneratePublicKey(t)
sshPublicKeyUpdated := GeneratePublicKey(t)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-server-resource"
ip_range = "10.240.0.0/16"
}
resource "binarylane_ssh_key" "initial" {
name = "tf-test-server-resource-initial"
public_key = "` + sshPublicKeyInitial + `"
default = true
}
resource "binarylane_ssh_key" "updated" {
name = "tf-test-server-resource-updated"
public_key = "` + sshPublicKeyUpdated + `"
default = true
}
resource "binarylane_server" "test" {
name = "tf-test-server-resource"
region = "per"
image = "debian-11"
size = "std-min"
password = "` + password + `"
vpc_id = binarylane_vpc.test.id
public_ipv4_count = 1
ssh_keys = [binarylane_ssh_key.initial.id]
user_data = <<EOT
#cloud-config
echo "Hello World" > /var/tmp/output.txt
EOT
}
data "binarylane_server" "test" {
depends_on = [binarylane_server.test]
id = binarylane_server.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttrSet("binarylane_server.test", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-11"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "1"),
resource.TestCheckResourceAttr("binarylane_server.test", "password", password),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
`),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "1"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "private_ipv4_addresses.0"),
resource.TestCheckResourceAttr("binarylane_server.test", "port_blocking", "true"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"),
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.initial", "id"),
resource.TestCheckResourceAttrSet("binarylane_server.test", "permalink"),
// Verify data source values
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "id", "binarylane_server.test", "id"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "name", "tf-test-server-resource"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "region", "per"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "image", "debian-11"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "size", "std-min"),
resource.TestCheckResourceAttrPair("data.binarylane_server.test", "permalink", "binarylane_server.test", "permalink"),
resource.TestCheckResourceAttr("data.binarylane_server.test", "user_data", `#cloud-config
echo "Hello World" > /var/tmp/output.txt
`),
),
},
// Test import by ID
{
ResourceName: "binarylane_server.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "timeouts"},
},
// Test import by name
{
ResourceName: "binarylane_server.test",
ImportState: true,
ImportStateId: "tf-test-server-resource",
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password", "ssh_keys", "timeouts"},
},
// Update and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-server-resource"
ip_range = "10.240.0.0/16"
}
resource "binarylane_ssh_key" "initial" {
name = "tf-test-server-resource-initial"
public_key = "` + sshPublicKeyInitial + `"
default = true
}
resource "binarylane_ssh_key" "updated" {
name = "tf-test-server-resource-updated"
public_key = "` + sshPublicKeyUpdated + `"
default = true
}
resource "binarylane_server" "test" {
name = "tf-test-server-resource-2"
region = "per"
image = "debian-12"
size = "std-1vcpu"
password = "` + password + `"
vpc_id = binarylane_vpc.test.id
public_ipv4_count = 0
ssh_keys = [binarylane_ssh_key.updated.id]
user_data = <<EOT
#cloud-config
echo "Hello Whitespace" > /var/tmp/output.txt
EOT
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("binarylane_server.test", "name", "tf-test-server-resource-2"),
resource.TestCheckResourceAttr("binarylane_server.test", "size", "std-1vcpu"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_count", "0"),
resource.TestCheckResourceAttr("binarylane_server.test", "public_ipv4_addresses.#", "0"),
resource.TestCheckResourceAttr("binarylane_server.test", "image", "debian-12"),
resource.TestCheckResourceAttr("binarylane_server.test", "ssh_keys.#", "1"),
resource.TestCheckResourceAttrPair("binarylane_server.test", "ssh_keys.0", "binarylane_ssh_key.updated", "id"),
resource.TestCheckResourceAttr("binarylane_server.test", "user_data", // test extra whitespace
`#cloud-config
echo "Hello Whitespace" > /var/tmp/output.txt
`),
),
},
},
})
}
func init() {
resource.AddTestSweepers("server", &resource.Sweeper{
Name: "server",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
ctx := context.Background()
var page int32 = 1
perPage := int32(200)
nextPage := true
for nextPage {
params := binarylane.GetServersParams{
Page: &page,
PerPage: &perPage,
}
listResp, err := client.GetServersWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting servers for test sweep: %w", err)
}
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting servers in test sweep: %s", listResp.Body)
}
servers := *listResp.JSON200.Servers
for _, s := range servers {
if strings.HasPrefix(*s.Name, "tf-test-") {
reason := "Terraform deletion"
params := binarylane.DeleteServersServerIdParams{
Reason: &reason,
}
deleteResp, err := client.DeleteServersServerIdWithResponse(ctx, *s.Id, &params)
if err != nil {
return fmt.Errorf("Error deleting server %d during test sweep: %w", *s.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting server %d in test sweep: %s", deleteResp.StatusCode(), *s.Id, deleteResp.Body)
}
log.Println("Deleted server during test sweep:", *s.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
return nil
},
})
}
+46 -9
View File
@@ -7,11 +7,15 @@ import (
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var _ datasource.DataSource = (*sshKeyDataSource)(nil)
// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &sshKeyDataSource{}
_ datasource.DataSourceWithConfigure = &sshKeyDataSource{}
)
func NewSshKeyDataSource() datasource.DataSource {
return &sshKeyDataSource{}
@@ -21,22 +25,55 @@ type sshKeyDataSource struct {
bc *BinarylaneClient
}
func (d *sshKeyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func (d *sshKeyDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_ssh_key"
}
func (d *sshKeyDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
},
ds, err := convertResourceSchemaToDataSourceSchema(
resources.SshKeyResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"id"},
},
)
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
// resp.Schema.Description = "TODO"
// Additional attributes
fingerprintDescription := "The fingerprint of the SSH key."
resp.Schema.Attributes["fingerprint"] = &schema.StringAttribute{
Description: fingerprintDescription,
MarkdownDescription: fingerprintDescription,
Optional: false,
Required: false,
Computed: true,
}
}
func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data resources.SshKeyModel
var data sshKeyModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
@@ -62,11 +99,11 @@ func (d *sshKeyDataSource) Read(ctx context.Context, req datasource.ReadRequest,
return
}
// Example data value setting
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = types.StringValue(*sshResp.JSON200.SshKey.PublicKey)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
+143 -27
View File
@@ -4,20 +4,24 @@ import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &sshKeyResource{}
_ resource.ResourceWithConfigure = &sshKeyResource{}
// _ resource.ResourceWithImportState = &serverResource{}
_ resource.Resource = &sshKeyResource{}
_ resource.ResourceWithConfigure = &sshKeyResource{}
_ resource.ResourceWithImportState = &sshKeyResource{}
)
func NewSshKeyResource() resource.Resource {
@@ -28,6 +32,11 @@ type sshKeyResource struct {
bc *BinarylaneClient
}
type sshKeyModel struct {
resources.SshKeyModel
Fingerprint types.String `tfsdk:"fingerprint"`
}
func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
@@ -36,7 +45,7 @@ func (d *sshKeyResource) Configure(_ context.Context, req resource.ConfigureRequ
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
@@ -52,21 +61,39 @@ func (r *sshKeyResource) Metadata(ctx context.Context, req resource.MetadataRequ
func (r *sshKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.SshKeyResourceSchema(ctx)
resp.Schema.Description = "TODO"
// resp.Schema.Description = "TODO"
// Overrides
defaultAttr := resp.Schema.Attributes["default"]
default_ := resp.Schema.Attributes["default"]
resp.Schema.Attributes["default"] = schema.BoolAttribute{
Optional: true,
Computed: true,
Description: defaultAttr.GetDescription(),
MarkdownDescription: defaultAttr.GetMarkdownDescription(),
Description: default_.GetDescription(),
MarkdownDescription: default_.GetMarkdownDescription(),
Default: booldefault.StaticBool(false), // Add default to backups
}
public_key := resp.Schema.Attributes["public_key"]
resp.Schema.Attributes["public_key"] = schema.StringAttribute{
Required: true,
Description: public_key.GetDescription(),
MarkdownDescription: public_key.GetMarkdownDescription(),
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
}
// Additional attributes
fingerprintDescription := "The fingerprint of the SSH key."
resp.Schema.Attributes["fingerprint"] = &schema.StringAttribute{
Description: fingerprintDescription,
MarkdownDescription: fingerprintDescription,
Optional: false,
Required: false,
Computed: true,
}
}
func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data resources.SshKeyModel
var data sshKeyModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
@@ -75,7 +102,6 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
return
}
// Create API call logic
sshResp, err := r.bc.client.PostAccountKeysWithResponse(ctx, binarylane.SshKeyRequest{
Name: data.Name.ValueString(),
Default: data.Default.ValueBoolPointer(),
@@ -90,14 +116,15 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating server",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
"Unexpected HTTP status code creating SSH key",
fmt.Sprintf("Received %s creating SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
)
return
}
// Set data values
data.Id = types.Int64Value(*sshResp.JSON200.SshKey.Id)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
if resp.Diagnostics.HasError() {
return
@@ -108,11 +135,13 @@ func (r *sshKeyResource) Create(ctx context.Context, req resource.CreateRequest,
}
func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data resources.SshKeyModel
var data sshKeyModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
data.PublicKey = types.StringValue(data.PublicKey.ValueString())
if resp.Diagnostics.HasError() {
return
}
@@ -128,8 +157,8 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating server",
fmt.Sprintf("Received %s creating new server: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
"Unexpected HTTP status code getting SSH key",
fmt.Sprintf("Received %s getting SSH key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
)
return
}
@@ -139,43 +168,65 @@ func (r *sshKeyResource) Read(ctx context.Context, req resource.ReadRequest, res
data.Default = types.BoolValue(*sshResp.JSON200.SshKey.Default)
data.Name = types.StringValue(*sshResp.JSON200.SshKey.Name)
data.PublicKey = types.StringValue(*sshResp.JSON200.SshKey.PublicKey)
data.Fingerprint = types.StringValue(*sshResp.JSON200.SshKey.Fingerprint)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *sshKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data resources.SshKeyModel
var plan, state sshKeyModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
// TODO
sshResp, err := r.bc.client.PutAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()), binarylane.UpdateSshKeyRequest{
Name: plan.Name.ValueString(),
Default: plan.Default.ValueBoolPointer(),
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating SSH Key: name=%s", plan.Name.ValueString()),
err.Error(),
)
return
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating SSH Key",
fmt.Sprintf("Received %s updating new SSH Key: name=%s. Details: %s", sshResp.Status(), plan.Name.ValueString(), sshResp.Body),
)
return
}
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
}
func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data resources.SshKeyModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
var state sshKeyModel
// Read Terraform prior state into the model
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
sshResp, err := r.bc.client.DeleteAccountKeysKeyIdWithResponse(ctx, int(data.Id.ValueInt64()))
sshResp, err := r.bc.client.DeleteAccountKeysKeyIdWithResponse(ctx, int(state.Id.ValueInt64()))
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting SSH Key: name=%s", data.Name.ValueString()),
fmt.Sprintf("Error deleting SSH Key: name=%s", state.Name.ValueString()),
err.Error(),
)
return
@@ -183,8 +234,73 @@ func (r *sshKeyResource) Delete(ctx context.Context, req resource.DeleteRequest,
if sshResp.StatusCode() != http.StatusNoContent {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting SSH Key",
fmt.Sprintf("Received %s deleting SSH Key: name=%s. Details: %s", sshResp.Status(), data.Name.ValueString(), sshResp.Body),
fmt.Sprintf("Received %s deleting SSH Key: name=%s. Details: %s", sshResp.Status(), state.Name.ValueString(), sshResp.Body),
)
return
}
}
func (r *sshKeyResource) ImportState(
ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse,
) {
// Import by ID
id, err := strconv.ParseInt(req.ID, 10, 32)
if err == nil {
diags := resp.State.SetAttribute(ctx, path.Root("id"), int32(id))
resp.Diagnostics.Append(diags...)
return
}
// Import by name
var page int32 = 1
perPage := int32(200)
var sshKey binarylane.SshKey
var nextPage bool = true
for nextPage { // Need to paginate because the API does not support filtering by fingerprint
params := binarylane.GetAccountKeysParams{
Page: &page,
PerPage: &perPage,
}
sshResp, err := r.bc.client.GetAccountKeysWithResponse(ctx, &params)
if err != nil {
resp.Diagnostics.AddError(fmt.Sprintf("Error getting SSH key for import: fingerprint=%s", req.ID), err.Error())
return
}
if sshResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code getting SSH key for import",
fmt.Sprintf("Received %s getting SSH key for import: fingerprint=%s. Details: %s", sshResp.Status(), req.ID,
sshResp.Body))
return
}
sshKeys := sshResp.JSON200.SshKeys
for _, key := range sshKeys {
if *key.Fingerprint == req.ID {
sshKey = key
nextPage = false
break
}
}
if sshResp.JSON200.Links == nil || sshResp.JSON200.Links.Pages == nil || sshResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
if sshKey.Id == nil {
resp.Diagnostics.AddError(
"Could not find SSH key by fingerprint",
fmt.Sprintf("Error finding SSH key: fingerprint=%s", req.ID),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), *sshKey.Id)
resp.Diagnostics.Append(diags...)
}
+172
View File
@@ -0,0 +1,172 @@
package provider
import (
"context"
"crypto/ed25519"
"encoding/base64"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestSshKeyResource(t *testing.T) {
publicKey := GeneratePublicKey(t)
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_ssh_key" "test" {
name = "tf-test-key-resource-test"
public_key = "` + publicKey + `"
}
data "binarylane_ssh_key" "test" {
depends_on = [binarylane_ssh_key.test]
id = binarylane_ssh_key.test.id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify resource values
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "public_key", publicKey),
resource.TestCheckResourceAttr("binarylane_ssh_key.test", "default", "false"),
resource.TestCheckResourceAttrSet("binarylane_ssh_key.test", "id"),
// Verify data source values
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "public_key"), // Ideally would check this is identical, but whitespace is not preserved
resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "false"),
resource.TestCheckResourceAttrSet("data.binarylane_ssh_key.test", "id"),
),
},
// ImportState testing
{
ResourceName: "binarylane_ssh_key.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{}, // nothing to ignore
},
{
ResourceName: "binarylane_ssh_key.test",
ImportStateIdFunc: ImportByFingerprint,
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{}, // nothing to ignore
},
// TODO: Update and Read testing
// {
// Config: providerConfig + `
// resource "binarylane_ssh_key" "test" {
// name = "tf-test-key-resource-test"
// public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJCsuosklP0T4fJcQDgkeVh7dQu+eV+vev1CfwdUkj7h test@company.internal"
// default = true
// }
// `,
// Check: resource.ComposeAggregateTestCheckFunc(
// // Verify resource values
// resource.TestCheckResourceAttr("binarylane_ssh_key.test", "name", "tf-test-key-resource-test"),
// resource.TestCheckResourceAttr("data.binarylane_ssh_key.test", "default", "true"),
// ),
// },
// Delete testing automatically occurs in TestCase
},
})
}
func GeneratePublicKey(t *testing.T) string {
t.Helper()
pub, _, err := ed25519.GenerateKey(nil)
if err != nil {
t.Fatalf("failed to generate key: %v", err)
}
encoded := base64.StdEncoding.EncodeToString(pub)
return fmt.Sprintf("ssh-ed25519 %s test@company.internal", encoded)
}
func ImportByFingerprint(state *terraform.State) (fingerprint string, err error) {
resourceName := "binarylane_ssh_key.test"
var rawState map[string]string
for _, m := range state.Modules {
if len(m.Resources) > 0 {
if v, ok := m.Resources[resourceName]; ok {
rawState = v.Primary.Attributes
}
}
}
if rawState == nil {
return "", fmt.Errorf("resource not found: %s", resourceName)
}
return rawState["fingerprint"], nil
}
func init() {
resource.AddTestSweepers("ssh_key", &resource.Sweeper{
Name: "ssh_key",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
ctx := context.Background()
var page int32 = 1
perPage := int32(200)
nextPage := true
for nextPage {
params := binarylane.GetAccountKeysParams{
Page: &page,
PerPage: &perPage,
}
listResp, err := client.GetAccountKeysWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting SSH keys for test sweep: %w", err)
}
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting SSH keys for test sweep: %s", listResp.Body)
}
keys := listResp.JSON200.SshKeys
for _, k := range keys {
if strings.HasPrefix(*k.Name, "tf-test-") {
deleteResp, err := client.DeleteAccountKeysKeyIdWithResponse(ctx, int(*k.Id))
if err != nil {
return fmt.Errorf("Error deleting SSH key %d for test sweep: %w", *k.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting SSH key %d for test sweep: %s", deleteResp.StatusCode(), *k.Id, deleteResp.Body)
}
log.Println("Deleted SSH key for test sweep:", *k.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
return nil
},
})
}
+11
View File
@@ -0,0 +1,11 @@
package provider
import (
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)
func TestMain(m *testing.M) {
resource.TestMain(m)
}
+167 -24
View File
@@ -1,36 +1,179 @@
package provider
import (
"context"
"fmt"
"slices"
"github.com/hashicorp/terraform-plugin-framework/attr"
d_schema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
r_schema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
func assignStr(source *string, target *basetypes.StringValue) {
if source != nil {
*target = types.StringValue(*source)
}
type AttributeConfig struct {
RequiredAttributes *[]string
OptionalAttributes *[]string
ExcludedAttributes *[]string
}
// func assignInt(source *int, target *basetypes.Int64Value) {
// if source != nil {
// *target = types.Int64Value(int64(*source))
// }
// }
func assignInt64(source *int64, target *basetypes.Int64Value) {
if source != nil {
*target = types.Int64Value(int64(*source))
func convertResourceSchemaToDataSourceSchema(rs r_schema.Schema, cfg AttributeConfig) (*d_schema.Schema, error) {
ds := d_schema.Schema{
Attributes: make(map[string]d_schema.Attribute, len(rs.Attributes)),
Blocks: make(map[string]d_schema.Block),
Description: rs.GetDescription(),
MarkdownDescription: rs.GetMarkdownDescription(),
DeprecationMessage: rs.GetDeprecationMessage(),
}
for name, attribute := range rs.Attributes {
if cfg.ExcludedAttributes != nil && slices.Contains(*cfg.ExcludedAttributes, name) {
continue
}
required := cfg.RequiredAttributes != nil && slices.Contains(*cfg.RequiredAttributes, name)
optional := cfg.OptionalAttributes != nil && slices.Contains(*cfg.OptionalAttributes, name)
switch attribute.GetType() {
case types.BoolType:
ds.Attributes[name] = d_schema.BoolAttribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.DynamicType:
ds.Attributes[name] = d_schema.DynamicAttribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.Float32Type:
ds.Attributes[name] = d_schema.Float32Attribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.Float64Type:
ds.Attributes[name] = d_schema.Float64Attribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.Int32Type:
ds.Attributes[name] = d_schema.Int32Attribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.Int64Type:
ds.Attributes[name] = d_schema.Int64Attribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.NumberType:
ds.Attributes[name] = d_schema.NumberAttribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
case types.StringType:
ds.Attributes[name] = d_schema.StringAttribute{
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
default:
if listType, isList := attribute.GetType().(types.ListType); isList {
ds.Attributes[name] = d_schema.ListAttribute{
ElementType: listType.ElemType,
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
}
continue
}
if objType, isObject := attribute.(r_schema.SingleNestedAttribute); isObject {
attributeTypes := make(map[string]attr.Type, len(objType.GetAttributes()))
for name, attribute := range objType.GetAttributes() {
attributeTypes[name] = attribute.GetType()
}
ds.Attributes[name] = d_schema.ObjectAttribute{
CustomType: objType.CustomType,
Description: attribute.GetDescription(),
Required: required,
Optional: optional,
Computed: !required,
Sensitive: attribute.IsSensitive(),
MarkdownDescription: attribute.GetMarkdownDescription(),
DeprecationMessage: attribute.GetDeprecationMessage(),
AttributeTypes: attributeTypes,
}
continue
}
return nil, fmt.Errorf("failed to convert resource schema attribute to data source schema attribute: name=%s, type=%s", name, attribute.GetType())
}
}
return &ds, nil
}
// func assignFloat(source *float32, target *basetypes.Float64Value) {
// if source != nil {
// *target = types.Float64Value(float64(*source))
// }
// }
func listContainsUnknown(ctx context.Context, list types.List) bool {
// If the whole list is unknown, return true
if list.IsUnknown() {
return true
}
// func assignBool(source *bool, target *basetypes.BoolValue) {
// if source != nil {
// *target = types.BoolValue(bool(*source))
// }
// }
// Get elements as generic attr.Value to check individual unknown status
var elements []attr.Value
diags := list.ElementsAs(ctx, &elements, false)
if diags.HasError() {
return true // Assume unknown in case of errors
}
// Check if any element is unknown
for _, elem := range elements {
if elem.IsUnknown() {
return true
}
}
return false
}
+98
View File
@@ -0,0 +1,98 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
)
var (
_ datasource.DataSource = &sshKeyDataSource{}
_ datasource.DataSourceWithConfigure = &sshKeyDataSource{}
)
func NewVpcDataSource() datasource.DataSource {
return &vpcDataSource{}
}
type vpcDataSource struct {
bc *BinarylaneClient
}
type vpcDataSourceModel struct {
resources.VpcModel
}
func (d *vpcDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
func (d *vpcDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc"
}
func (d *vpcDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(
resources.VpcResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"id"},
},
)
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
// resp.Schema.Description = "TODO"
}
func (d *vpcDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data vpcDataSourceModel
// Read Terraform configuration data into the model
diags := req.Config.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := d.bc.client.GetVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
+231
View File
@@ -0,0 +1,231 @@
package provider
import (
"context"
"fmt"
"net/http"
"reflect"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)
var (
_ resource.Resource = &vpcResource{}
_ resource.ResourceWithConfigure = &vpcResource{}
_ resource.ResourceWithImportState = &vpcResource{}
)
func NewVpcResource() resource.Resource {
return &vpcResource{}
}
type vpcResource struct {
bc *BinarylaneClient
}
type vpcResourceModel struct {
resources.VpcModel
}
func (r *vpcResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
r.bc = &bc
}
func (r *vpcResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc"
}
func (r *vpcResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.VpcResourceSchema(ctx)
// resp.Schema.Description = "TODO"
// Overrides
id := resp.Schema.Attributes["id"]
resp.Schema.Attributes["id"] = &schema.Int64Attribute{
Description: id.GetDescription(),
MarkdownDescription: id.GetMarkdownDescription(),
// read only
Optional: false,
Required: false,
Computed: true,
}
}
func (r *vpcResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data vpcResourceModel
// Read Terraform plan data into the model
diags := req.Plan.Get(ctx, &data)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
tflog.Debug(ctx, fmt.Sprintf("Creating VPC: name=%s", data.Name.ValueString()))
body := binarylane.CreateVpcRequest{
Name: data.Name.ValueString(),
IpRange: data.IpRange.ValueStringPointer(),
}
vpcResp, err := r.bc.client.PostVpcsWithResponse(ctx, body)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC: name=%s", data.Name.ValueString()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC",
fmt.Sprintf("Received %s creating new VPC: name=%s. Details: %s", vpcResp.Status(), data.Name.ValueString(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data vpcResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := r.bc.client.GetVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
data.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
data.IpRange = types.StringValue(*vpcResp.JSON200.Vpc.IpRange)
data.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state vpcResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
patchRequest := binarylane.PatchVpcRequest{}
if !plan.Name.Equal(state.Name) {
patchRequest.Name = plan.Name.ValueStringPointer()
}
if reflect.ValueOf(plan).NumField() == 0 {
return
}
tflog.Debug(ctx, fmt.Sprintf("Updating VPC: id=%d", state.Id.ValueInt64()))
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, state.Id.ValueInt64(), patchRequest)
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating VPC: id=%d", state.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating VPC",
fmt.Sprintf("Received %s updating VPC: id=%d. Details: %s", vpcResp.Status(), state.Id.ValueInt64(), vpcResp.Body))
return
}
state.Id = types.Int64Value(*vpcResp.JSON200.Vpc.Id)
state.Name = types.StringValue(*vpcResp.JSON200.Vpc.Name)
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *vpcResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data vpcResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
tflog.Debug(ctx, fmt.Sprintf("Deleting VPC: id=%d", data.Id.ValueInt64()))
vpcResp, err := r.bc.client.DeleteVpcsVpcIdWithResponse(ctx, data.Id.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting VPC: id=%d", data.Id.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusNoContent {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting VPC",
fmt.Sprintf("Received %s deleting VPC: id=%d. Details: %s", vpcResp.Status(), data.Id.ValueInt64(), vpcResp.Body))
return
}
}
func (r *vpcResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC",
fmt.Sprintf("Could not import VPC, unable to parse ID (should be an integer): %s ", err.Error()),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("id"), id)
resp.Diagnostics.Append(diags...)
}
+183
View File
@@ -0,0 +1,183 @@
package provider
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"terraform-provider-binarylane/internal/binarylane"
"testing"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
func TestVpcResource(t *testing.T) {
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-vpc"
ip_range = "10.240.0.0/16"
}
data "binarylane_vpc" "test" {
depends_on = [binarylane_vpc.test]
id = binarylane_vpc.test.id
}
resource "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc.test.id
route_entries = [
{
description = "test"
destination = "0.0.0.0/0"
router = "10.240.0.1"
}
]
}
data "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc_route_entries.test.vpc_id
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify binarylane_vpc resource values
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify binarylane_vpc data source values
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "name", "tf-test-vpc"),
resource.TestCheckResourceAttr("data.binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("data.binarylane_vpc.test", "id"),
// Verify binarylane_vpc_route_entries resource values
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
// Verify binarylane_vpc_route_entries data source values
resource.TestCheckResourceAttrSet("data.binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.1"),
resource.TestCheckResourceAttr("data.binarylane_vpc_route_entries.test", "route_entries.0.description", "test"),
),
},
// ImportState testing
{
ResourceName: "binarylane_vpc.test",
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "binarylane_vpc_route_entries.test",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "vpc_id",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
resourceState := s.RootModule().Resources["binarylane_vpc_route_entries.test"]
return resourceState.Primary.Attributes["vpc_id"], nil
},
},
// Update and Read testing
{
Config: providerConfig + `
resource "binarylane_vpc" "test" {
name = "tf-test-vpc-renamed"
ip_range = "10.240.0.0/16"
}
resource "binarylane_vpc_route_entries" "test" {
vpc_id = binarylane_vpc.test.id
route_entries = [
{
description = "test-renamed"
destination = "0.0.0.0/0"
router = "10.240.0.2"
}
]
}
`,
Check: resource.ComposeAggregateTestCheckFunc(
// Verify binarylane_vpc resource values updated
resource.TestCheckResourceAttr("binarylane_vpc.test", "name", "tf-test-vpc-renamed"), // Updated name
resource.TestCheckResourceAttr("binarylane_vpc.test", "ip_range", "10.240.0.0/16"),
resource.TestCheckResourceAttrSet("binarylane_vpc.test", "id"),
// Verify binarylane_vpc_route_entries resource values updated
resource.TestCheckResourceAttrSet("binarylane_vpc_route_entries.test", "vpc_id"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.#", "1"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.destination", "0.0.0.0/0"),
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.router", "10.240.0.2"), // Updated router
resource.TestCheckResourceAttr("binarylane_vpc_route_entries.test", "route_entries.0.description", "test-renamed"), // Updated description
),
},
// Delete testing automatically occurs in TestCase
},
})
}
func init() {
resource.AddTestSweepers("vpc", &resource.Sweeper{
Name: "vpc",
F: func(_ string) error {
client, err := binarylane.NewClientWithDefaultConfig()
if err != nil {
return fmt.Errorf("Error creating Binary Lane API client: %w", err)
}
ctx := context.Background()
var page int32 = 1
perPage := int32(200)
nextPage := true
for nextPage {
params := binarylane.GetVpcsParams{
Page: &page,
PerPage: &perPage,
}
listResp, err := client.GetVpcsWithResponse(ctx, &params)
if err != nil {
return fmt.Errorf("Error getting VPCs for test sweep: %w", err)
}
if listResp.StatusCode() != http.StatusOK {
return fmt.Errorf("Unexpected status code getting VPCs for test sweep: %s", listResp.Body)
}
vpcs := listResp.JSON200.Vpcs
for _, vpc := range *vpcs {
if strings.HasPrefix(*vpc.Name, "tf-test-") {
deleteResp, err := client.DeleteVpcsVpcIdWithResponse(ctx, *vpc.Id)
if err != nil {
return fmt.Errorf("Error deleting VPC %d in test sweep: %w", *vpc.Id, err)
}
if deleteResp.StatusCode() != http.StatusNoContent {
return fmt.Errorf("Unexpected status %d deleting VPC %d for test sweep: %s", deleteResp.StatusCode(), *vpc.Id, deleteResp.Body)
}
log.Println("Deleted VPC for test sweep:", *vpc.Id)
}
}
if listResp.JSON200.Links == nil || listResp.JSON200.Links.Pages == nil || listResp.JSON200.Links.Pages.Next == nil {
nextPage = false
break
}
page++
}
return nil
},
})
}
@@ -0,0 +1,114 @@
package provider
import (
"context"
"fmt"
"net/http"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
)
var (
_ datasource.DataSource = &vpcRouteEntriesDataSource{}
_ datasource.DataSourceWithConfigure = &vpcRouteEntriesDataSource{}
)
func NewVpcRouteEntriesDataSource() datasource.DataSource {
return &vpcRouteEntriesDataSource{}
}
type vpcRouteEntriesDataSource struct {
bc *BinarylaneClient
}
type vpcRouteEntriesDataSourceModel struct {
resources.VpcRouteEntriesModel
}
func (d *vpcRouteEntriesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc_route_entries"
}
func (d *vpcRouteEntriesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
ds, err := convertResourceSchemaToDataSourceSchema(
resources.VpcRouteEntriesResourceSchema(ctx),
AttributeConfig{
RequiredAttributes: &[]string{"vpc_id"},
},
)
if err != nil {
resp.Diagnostics.AddError("Failed to convert resource schema to data source schema", err.Error())
return
}
resp.Schema = *ds
// resp.Schema.Description = "TODO"
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
}
}
func (d *vpcRouteEntriesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data vpcRouteEntriesDataSourceModel
// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := d.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC",
fmt.Sprintf("Received %s reading VPC: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntries
// Example data value setting
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (d *vpcRouteEntriesDataSource) Configure(
_ context.Context,
req datasource.ConfigureRequest,
resp *datasource.ConfigureResponse,
) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Data Source Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData))
return
}
d.bc = &bc
}
@@ -0,0 +1,278 @@
package provider
import (
"context"
"fmt"
"net/http"
"strconv"
"terraform-provider-binarylane/internal/binarylane"
"terraform-provider-binarylane/internal/resources"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)
var (
_ resource.Resource = &vpcRouteEntriesResource{}
_ resource.ResourceWithConfigure = &vpcRouteEntriesResource{}
_ resource.ResourceWithImportState = &vpcRouteEntriesResource{}
)
func NewVpcRouteEntriesResource() resource.Resource {
return &vpcRouteEntriesResource{}
}
type vpcRouteEntriesResource struct {
bc *BinarylaneClient
}
type vpcRouteEntriesResourceModel struct {
resources.VpcRouteEntriesModel
}
func (r *vpcRouteEntriesResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_vpc_route_entries"
}
func (r *vpcRouteEntriesResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = resources.VpcRouteEntriesResourceSchema(ctx)
// resp.Schema.Description = "TODO"
// Overrides
vpcId := resp.Schema.Attributes["vpc_id"]
resp.Schema.Attributes["vpc_id"] = &schema.Int64Attribute{
Description: vpcId.GetDescription(),
MarkdownDescription: vpcId.GetMarkdownDescription(),
Required: true, // VPC ID is required to define the route entries
Validators: []validator.Int64{
int64validator.AtLeast(1),
},
}
}
func (r *vpcRouteEntriesResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Create API call logic
routeEntries := []binarylane.RouteEntryRequest{}
diags := data.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error creating VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code creating VPC route entries",
fmt.Sprintf("Received %s creating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntriesState
// Save data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcRouteEntriesResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Read API call logic
vpcResp, err := r.bc.client.GetVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64())
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error reading VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code reading VPC route entries",
fmt.Sprintf("Received %s reading VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntries, routeEntriesDiags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(routeEntriesDiags...)
if resp.Diagnostics.HasError() {
return
}
data.RouteEntries = routeEntries
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
func (r *vpcRouteEntriesResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan, state vpcRouteEntriesResourceModel
// Read Terraform plan data into the model
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
// Update API call logic
routeEntries := []binarylane.RouteEntryRequest{}
diags := plan.VpcRouteEntriesModel.RouteEntries.ElementsAs(ctx, &routeEntries, true)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, state.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error updating VPC route entries: vpc_id=%d", state.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code updating VPC route entries",
fmt.Sprintf("Received %s updating VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), state.VpcId.ValueInt64(), vpcResp.Body))
return
}
routeEntriesState, diags := GetRouteEntriesState(ctx, vpcResp.JSON200.Vpc.RouteEntries)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
state.RouteEntries = routeEntriesState
// Save updated data into Terraform state
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *vpcRouteEntriesResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data vpcRouteEntriesResourceModel
// Read Terraform prior state data into the model
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}
// Delete API call logic
var routeEntries []binarylane.RouteEntryRequest
vpcResp, err := r.bc.client.PatchVpcsVpcIdWithResponse(ctx, data.VpcId.ValueInt64(), binarylane.PatchVpcRequest{
RouteEntries: &routeEntries,
})
if err != nil {
resp.Diagnostics.AddError(
fmt.Sprintf("Error deleting VPC route entries: vpc_id=%d", data.VpcId.ValueInt64()),
err.Error(),
)
return
}
if vpcResp.StatusCode() != http.StatusOK {
resp.Diagnostics.AddError(
"Unexpected HTTP status code deleting VPC route entries",
fmt.Sprintf("Received %s deleting VPC route entries: vpc_id=%d. Details: %s", vpcResp.Status(), data.VpcId.ValueInt64(), vpcResp.Body))
return
}
}
func (d *vpcRouteEntriesResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
bc, ok := req.ProviderData.(BinarylaneClient)
if !ok {
resp.Diagnostics.AddError(
"Unexpected Resource Configure Type",
fmt.Sprintf("Expected *BinarylaneClient, got: %T.", req.ProviderData),
)
return
}
d.bc = &bc
}
func GetRouteEntriesState(ctx context.Context, routeEntries *[]binarylane.RouteEntry) (basetypes.ListValue, diag.Diagnostics) {
var diags diag.Diagnostics
routeEntriesValue := resources.RouteEntriesValue{}
var routeEntriesValues []resources.RouteEntriesValue
if *routeEntries == nil || len(*routeEntries) == 0 {
routeEntriesValues = []resources.RouteEntriesValue{}
} else {
for _, route := range *routeEntries {
r := resources.NewRouteEntriesValueMust(routeEntriesValue.AttributeTypes(ctx), map[string]attr.Value{
"description": types.StringValue(*route.Description),
"destination": types.StringValue(*route.Destination),
"router": types.StringValue(*route.Router),
})
routeEntriesValues = append(routeEntriesValues, r)
}
}
routeEntriesListValue, diag := types.ListValueFrom(ctx, routeEntriesValue.Type(ctx), routeEntriesValues)
diags.Append(diag...)
if diags.HasError() {
return basetypes.NewListUnknown(routeEntriesValue.Type(ctx)), diags
}
return routeEntriesListValue, diags
}
func (r *vpcRouteEntriesResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
id, err := strconv.ParseInt(req.ID, 10, 64)
if err != nil {
resp.Diagnostics.AddError(
"Error importing VPC route entries",
"Could not import VPC route entries, unexpected error (ID should be an integer): "+err.Error(),
)
return
}
diags := resp.State.SetAttribute(ctx, path.Root("vpc_id"), id)
resp.Diagnostics.Append(diags...)
}
@@ -0,0 +1,819 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"regexp"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func LoadBalancerResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"forwarding_rules": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"entry_protocol": schema.StringAttribute{
Required: true,
Description: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
MarkdownDescription: "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
Validators: []validator.String{
stringvalidator.OneOf(
"http",
"https",
),
},
},
},
CustomType: ForwardingRulesType{
ObjectType: types.ObjectType{
AttrTypes: ForwardingRulesValue{}.AttributeTypes(ctx),
},
},
},
Optional: true,
Computed: true,
Description: "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
MarkdownDescription: "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule.",
},
"health_check": schema.SingleNestedAttribute{
Attributes: map[string]schema.Attribute{
"path": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Leave null to accept the default '/' path.",
MarkdownDescription: "Leave null to accept the default '/' path.",
Validators: []validator.String{
stringvalidator.RegexMatches(regexp.MustCompile("/[A-Za-z0-9/.?=&+%_-]*"), ""),
},
},
"protocol": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Leave null to accept the default HTTP protocol.",
MarkdownDescription: "Leave null to accept the default HTTP protocol.",
},
},
CustomType: HealthCheckType{
ObjectType: types.ObjectType{
AttrTypes: HealthCheckValue{}.AttributeTypes(ctx),
},
},
Optional: true,
Computed: true,
Description: "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.",
MarkdownDescription: "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules.",
},
"id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The ID of the load balancer to fetch.",
MarkdownDescription: "The ID of the load balancer to fetch.",
},
"name": schema.StringAttribute{
Required: true,
Description: "The hostname of the load balancer.",
MarkdownDescription: "The hostname of the load balancer.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"region": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Leave null to create an anycast load balancer.",
MarkdownDescription: "Leave null to create an anycast load balancer.",
},
"server_ids": schema.ListAttribute{
ElementType: types.Int64Type,
Optional: true,
Computed: true,
Description: "A list of server IDs to assign to this load balancer.",
MarkdownDescription: "A list of server IDs to assign to this load balancer.",
},
},
}
}
type LoadBalancerModel struct {
ForwardingRules types.List `tfsdk:"forwarding_rules"`
HealthCheck HealthCheckValue `tfsdk:"health_check"`
Id types.Int64 `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Region types.String `tfsdk:"region"`
ServerIds types.List `tfsdk:"server_ids"`
}
var _ basetypes.ObjectTypable = ForwardingRulesType{}
type ForwardingRulesType struct {
basetypes.ObjectType
}
func (t ForwardingRulesType) Equal(o attr.Type) bool {
other, ok := o.(ForwardingRulesType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t ForwardingRulesType) String() string {
return "ForwardingRulesType"
}
func (t ForwardingRulesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
entryProtocolAttribute, ok := attributes["entry_protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`entry_protocol is missing from object`)
return nil, diags
}
entryProtocolVal, ok := entryProtocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`entry_protocol expected to be basetypes.StringValue, was: %T`, entryProtocolAttribute))
}
if diags.HasError() {
return nil, diags
}
return ForwardingRulesValue{
EntryProtocol: entryProtocolVal,
state: attr.ValueStateKnown,
}, diags
}
func NewForwardingRulesValueNull() ForwardingRulesValue {
return ForwardingRulesValue{
state: attr.ValueStateNull,
}
}
func NewForwardingRulesValueUnknown() ForwardingRulesValue {
return ForwardingRulesValue{
state: attr.ValueStateUnknown,
}
}
func NewForwardingRulesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (ForwardingRulesValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing ForwardingRulesValue Attribute Value",
"While creating a ForwardingRulesValue value, a missing attribute value was detected. "+
"A ForwardingRulesValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid ForwardingRulesValue Attribute Type",
"While creating a ForwardingRulesValue value, an invalid attribute value was detected. "+
"A ForwardingRulesValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("ForwardingRulesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra ForwardingRulesValue Attribute Value",
"While creating a ForwardingRulesValue value, an extra attribute value was detected. "+
"A ForwardingRulesValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra ForwardingRulesValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewForwardingRulesValueUnknown(), diags
}
entryProtocolAttribute, ok := attributes["entry_protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`entry_protocol is missing from object`)
return NewForwardingRulesValueUnknown(), diags
}
entryProtocolVal, ok := entryProtocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`entry_protocol expected to be basetypes.StringValue, was: %T`, entryProtocolAttribute))
}
if diags.HasError() {
return NewForwardingRulesValueUnknown(), diags
}
return ForwardingRulesValue{
EntryProtocol: entryProtocolVal,
state: attr.ValueStateKnown,
}, diags
}
func NewForwardingRulesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) ForwardingRulesValue {
object, diags := NewForwardingRulesValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewForwardingRulesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t ForwardingRulesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewForwardingRulesValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewForwardingRulesValueUnknown(), nil
}
if in.IsNull() {
return NewForwardingRulesValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewForwardingRulesValueMust(ForwardingRulesValue{}.AttributeTypes(ctx), attributes), nil
}
func (t ForwardingRulesType) ValueType(ctx context.Context) attr.Value {
return ForwardingRulesValue{}
}
var _ basetypes.ObjectValuable = ForwardingRulesValue{}
type ForwardingRulesValue struct {
EntryProtocol basetypes.StringValue `tfsdk:"entry_protocol"`
state attr.ValueState
}
func (v ForwardingRulesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 1)
var val tftypes.Value
var err error
attrTypes["entry_protocol"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 1)
val, err = v.EntryProtocol.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["entry_protocol"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v ForwardingRulesValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v ForwardingRulesValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v ForwardingRulesValue) String() string {
return "ForwardingRulesValue"
}
func (v ForwardingRulesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"entry_protocol": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"entry_protocol": v.EntryProtocol,
})
return objVal, diags
}
func (v ForwardingRulesValue) Equal(o attr.Value) bool {
other, ok := o.(ForwardingRulesValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.EntryProtocol.Equal(other.EntryProtocol) {
return false
}
return true
}
func (v ForwardingRulesValue) Type(ctx context.Context) attr.Type {
return ForwardingRulesType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v ForwardingRulesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"entry_protocol": basetypes.StringType{},
}
}
var _ basetypes.ObjectTypable = HealthCheckType{}
type HealthCheckType struct {
basetypes.ObjectType
}
func (t HealthCheckType) Equal(o attr.Type) bool {
other, ok := o.(HealthCheckType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t HealthCheckType) String() string {
return "HealthCheckType"
}
func (t HealthCheckType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
pathAttribute, ok := attributes["path"]
if !ok {
diags.AddError(
"Attribute Missing",
`path is missing from object`)
return nil, diags
}
pathVal, ok := pathAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`path expected to be basetypes.StringValue, was: %T`, pathAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return nil, diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
if diags.HasError() {
return nil, diags
}
return HealthCheckValue{
Path: pathVal,
Protocol: protocolVal,
state: attr.ValueStateKnown,
}, diags
}
func NewHealthCheckValueNull() HealthCheckValue {
return HealthCheckValue{
state: attr.ValueStateNull,
}
}
func NewHealthCheckValueUnknown() HealthCheckValue {
return HealthCheckValue{
state: attr.ValueStateUnknown,
}
}
func NewHealthCheckValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (HealthCheckValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing HealthCheckValue Attribute Value",
"While creating a HealthCheckValue value, a missing attribute value was detected. "+
"A HealthCheckValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid HealthCheckValue Attribute Type",
"While creating a HealthCheckValue value, an invalid attribute value was detected. "+
"A HealthCheckValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("HealthCheckValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra HealthCheckValue Attribute Value",
"While creating a HealthCheckValue value, an extra attribute value was detected. "+
"A HealthCheckValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra HealthCheckValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewHealthCheckValueUnknown(), diags
}
pathAttribute, ok := attributes["path"]
if !ok {
diags.AddError(
"Attribute Missing",
`path is missing from object`)
return NewHealthCheckValueUnknown(), diags
}
pathVal, ok := pathAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`path expected to be basetypes.StringValue, was: %T`, pathAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return NewHealthCheckValueUnknown(), diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
if diags.HasError() {
return NewHealthCheckValueUnknown(), diags
}
return HealthCheckValue{
Path: pathVal,
Protocol: protocolVal,
state: attr.ValueStateKnown,
}, diags
}
func NewHealthCheckValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) HealthCheckValue {
object, diags := NewHealthCheckValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewHealthCheckValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t HealthCheckType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewHealthCheckValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewHealthCheckValueUnknown(), nil
}
if in.IsNull() {
return NewHealthCheckValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewHealthCheckValueMust(HealthCheckValue{}.AttributeTypes(ctx), attributes), nil
}
func (t HealthCheckType) ValueType(ctx context.Context) attr.Value {
return HealthCheckValue{}
}
var _ basetypes.ObjectValuable = HealthCheckValue{}
type HealthCheckValue struct {
Path basetypes.StringValue `tfsdk:"path"`
Protocol basetypes.StringValue `tfsdk:"protocol"`
state attr.ValueState
}
func (v HealthCheckValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 2)
var val tftypes.Value
var err error
attrTypes["path"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["protocol"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 2)
val, err = v.Path.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["path"] = val
val, err = v.Protocol.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["protocol"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v HealthCheckValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v HealthCheckValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v HealthCheckValue) String() string {
return "HealthCheckValue"
}
func (v HealthCheckValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"path": basetypes.StringType{},
"protocol": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"path": v.Path,
"protocol": v.Protocol,
})
return objVal, diags
}
func (v HealthCheckValue) Equal(o attr.Value) bool {
other, ok := o.(HealthCheckValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Path.Equal(other.Path) {
return false
}
if !v.Protocol.Equal(other.Protocol) {
return false
}
return true
}
func (v HealthCheckValue) Type(ctx context.Context) attr.Type {
return HealthCheckType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v HealthCheckValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"path": basetypes.StringType{},
"protocol": basetypes.StringType{},
}
}
@@ -0,0 +1,813 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func ServerFirewallRulesResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"firewall_rules": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"action": schema.StringAttribute{
Required: true,
Description: "The action to take when there is a match on this rule.",
MarkdownDescription: "The action to take when there is a match on this rule.",
Validators: []validator.String{
stringvalidator.OneOf(
"drop",
"accept",
),
},
},
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
MarkdownDescription: "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
Validators: []validator.String{
stringvalidator.LengthBetween(0, 250),
},
},
"destination_addresses": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Description: "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
MarkdownDescription: "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
},
"destination_ports": schema.ListAttribute{
ElementType: types.StringType,
Optional: true,
Computed: true,
Description: "The destination ports to match for this rule. Leave null or empty to match on all ports.",
MarkdownDescription: "The destination ports to match for this rule. Leave null or empty to match on all ports.",
},
"protocol": schema.StringAttribute{
Required: true,
Description: "The protocol to match for this rule.",
MarkdownDescription: "The protocol to match for this rule.",
Validators: []validator.String{
stringvalidator.OneOf(
"all",
"icmp",
"tcp",
"udp",
),
},
},
"source_addresses": schema.ListAttribute{
ElementType: types.StringType,
Required: true,
Description: "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
MarkdownDescription: "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
Validators: []validator.List{
listvalidator.SizeAtLeast(1),
},
},
},
CustomType: FirewallRulesType{
ObjectType: types.ObjectType{
AttrTypes: FirewallRulesValue{}.AttributeTypes(ctx),
},
},
},
Required: true,
Description: "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules.",
MarkdownDescription: "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules.",
},
"server_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The ID of the server for which firewall rules should be listed.",
MarkdownDescription: "The ID of the server for which firewall rules should be listed.",
},
},
}
}
type ServerFirewallRulesModel struct {
FirewallRules types.List `tfsdk:"firewall_rules"`
ServerId types.Int64 `tfsdk:"server_id"`
}
var _ basetypes.ObjectTypable = FirewallRulesType{}
type FirewallRulesType struct {
basetypes.ObjectType
}
func (t FirewallRulesType) Equal(o attr.Type) bool {
other, ok := o.(FirewallRulesType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t FirewallRulesType) String() string {
return "FirewallRulesType"
}
func (t FirewallRulesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
actionAttribute, ok := attributes["action"]
if !ok {
diags.AddError(
"Attribute Missing",
`action is missing from object`)
return nil, diags
}
actionVal, ok := actionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`action expected to be basetypes.StringValue, was: %T`, actionAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return nil, diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAddressesAttribute, ok := attributes["destination_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_addresses is missing from object`)
return nil, diags
}
destinationAddressesVal, ok := destinationAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_addresses expected to be basetypes.ListValue, was: %T`, destinationAddressesAttribute))
}
destinationPortsAttribute, ok := attributes["destination_ports"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_ports is missing from object`)
return nil, diags
}
destinationPortsVal, ok := destinationPortsAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_ports expected to be basetypes.ListValue, was: %T`, destinationPortsAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return nil, diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
sourceAddressesAttribute, ok := attributes["source_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`source_addresses is missing from object`)
return nil, diags
}
sourceAddressesVal, ok := sourceAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`source_addresses expected to be basetypes.ListValue, was: %T`, sourceAddressesAttribute))
}
if diags.HasError() {
return nil, diags
}
return FirewallRulesValue{
Action: actionVal,
Description: descriptionVal,
DestinationAddresses: destinationAddressesVal,
DestinationPorts: destinationPortsVal,
Protocol: protocolVal,
SourceAddresses: sourceAddressesVal,
state: attr.ValueStateKnown,
}, diags
}
func NewFirewallRulesValueNull() FirewallRulesValue {
return FirewallRulesValue{
state: attr.ValueStateNull,
}
}
func NewFirewallRulesValueUnknown() FirewallRulesValue {
return FirewallRulesValue{
state: attr.ValueStateUnknown,
}
}
func NewFirewallRulesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (FirewallRulesValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing FirewallRulesValue Attribute Value",
"While creating a FirewallRulesValue value, a missing attribute value was detected. "+
"A FirewallRulesValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid FirewallRulesValue Attribute Type",
"While creating a FirewallRulesValue value, an invalid attribute value was detected. "+
"A FirewallRulesValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("FirewallRulesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra FirewallRulesValue Attribute Value",
"While creating a FirewallRulesValue value, an extra attribute value was detected. "+
"A FirewallRulesValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra FirewallRulesValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewFirewallRulesValueUnknown(), diags
}
actionAttribute, ok := attributes["action"]
if !ok {
diags.AddError(
"Attribute Missing",
`action is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
actionVal, ok := actionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`action expected to be basetypes.StringValue, was: %T`, actionAttribute))
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAddressesAttribute, ok := attributes["destination_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_addresses is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
destinationAddressesVal, ok := destinationAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_addresses expected to be basetypes.ListValue, was: %T`, destinationAddressesAttribute))
}
destinationPortsAttribute, ok := attributes["destination_ports"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination_ports is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
destinationPortsVal, ok := destinationPortsAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination_ports expected to be basetypes.ListValue, was: %T`, destinationPortsAttribute))
}
protocolAttribute, ok := attributes["protocol"]
if !ok {
diags.AddError(
"Attribute Missing",
`protocol is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
protocolVal, ok := protocolAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`protocol expected to be basetypes.StringValue, was: %T`, protocolAttribute))
}
sourceAddressesAttribute, ok := attributes["source_addresses"]
if !ok {
diags.AddError(
"Attribute Missing",
`source_addresses is missing from object`)
return NewFirewallRulesValueUnknown(), diags
}
sourceAddressesVal, ok := sourceAddressesAttribute.(basetypes.ListValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`source_addresses expected to be basetypes.ListValue, was: %T`, sourceAddressesAttribute))
}
if diags.HasError() {
return NewFirewallRulesValueUnknown(), diags
}
return FirewallRulesValue{
Action: actionVal,
Description: descriptionVal,
DestinationAddresses: destinationAddressesVal,
DestinationPorts: destinationPortsVal,
Protocol: protocolVal,
SourceAddresses: sourceAddressesVal,
state: attr.ValueStateKnown,
}, diags
}
func NewFirewallRulesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) FirewallRulesValue {
object, diags := NewFirewallRulesValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewFirewallRulesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t FirewallRulesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewFirewallRulesValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewFirewallRulesValueUnknown(), nil
}
if in.IsNull() {
return NewFirewallRulesValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewFirewallRulesValueMust(FirewallRulesValue{}.AttributeTypes(ctx), attributes), nil
}
func (t FirewallRulesType) ValueType(ctx context.Context) attr.Value {
return FirewallRulesValue{}
}
var _ basetypes.ObjectValuable = FirewallRulesValue{}
type FirewallRulesValue struct {
Action basetypes.StringValue `tfsdk:"action"`
Description basetypes.StringValue `tfsdk:"description"`
DestinationAddresses basetypes.ListValue `tfsdk:"destination_addresses"`
DestinationPorts basetypes.ListValue `tfsdk:"destination_ports"`
Protocol basetypes.StringValue `tfsdk:"protocol"`
SourceAddresses basetypes.ListValue `tfsdk:"source_addresses"`
state attr.ValueState
}
func (v FirewallRulesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 6)
var val tftypes.Value
var err error
attrTypes["action"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["destination_addresses"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["destination_ports"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
attrTypes["protocol"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["source_addresses"] = basetypes.ListType{
ElemType: types.StringType,
}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 6)
val, err = v.Action.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["action"] = val
val, err = v.Description.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["description"] = val
val, err = v.DestinationAddresses.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination_addresses"] = val
val, err = v.DestinationPorts.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination_ports"] = val
val, err = v.Protocol.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["protocol"] = val
val, err = v.SourceAddresses.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["source_addresses"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v FirewallRulesValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v FirewallRulesValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v FirewallRulesValue) String() string {
return "FirewallRulesValue"
}
func (v FirewallRulesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
var destinationAddressesVal basetypes.ListValue
switch {
case v.DestinationAddresses.IsUnknown():
destinationAddressesVal = types.ListUnknown(types.StringType)
case v.DestinationAddresses.IsNull():
destinationAddressesVal = types.ListNull(types.StringType)
default:
var d diag.Diagnostics
destinationAddressesVal, d = types.ListValue(types.StringType, v.DestinationAddresses.Elements())
diags.Append(d...)
}
if diags.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
var destinationPortsVal basetypes.ListValue
switch {
case v.DestinationPorts.IsUnknown():
destinationPortsVal = types.ListUnknown(types.StringType)
case v.DestinationPorts.IsNull():
destinationPortsVal = types.ListNull(types.StringType)
default:
var d diag.Diagnostics
destinationPortsVal, d = types.ListValue(types.StringType, v.DestinationPorts.Elements())
diags.Append(d...)
}
if diags.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
var sourceAddressesVal basetypes.ListValue
switch {
case v.SourceAddresses.IsUnknown():
sourceAddressesVal = types.ListUnknown(types.StringType)
case v.SourceAddresses.IsNull():
sourceAddressesVal = types.ListNull(types.StringType)
default:
var d diag.Diagnostics
sourceAddressesVal, d = types.ListValue(types.StringType, v.SourceAddresses.Elements())
diags.Append(d...)
}
if diags.HasError() {
return types.ObjectUnknown(map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}), diags
}
attributeTypes := map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"action": v.Action,
"description": v.Description,
"destination_addresses": destinationAddressesVal,
"destination_ports": destinationPortsVal,
"protocol": v.Protocol,
"source_addresses": sourceAddressesVal,
})
return objVal, diags
}
func (v FirewallRulesValue) Equal(o attr.Value) bool {
other, ok := o.(FirewallRulesValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Action.Equal(other.Action) {
return false
}
if !v.Description.Equal(other.Description) {
return false
}
if !v.DestinationAddresses.Equal(other.DestinationAddresses) {
return false
}
if !v.DestinationPorts.Equal(other.DestinationPorts) {
return false
}
if !v.Protocol.Equal(other.Protocol) {
return false
}
if !v.SourceAddresses.Equal(other.SourceAddresses) {
return false
}
return true
}
func (v FirewallRulesValue) Type(ctx context.Context) attr.Type {
return FirewallRulesType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v FirewallRulesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"action": basetypes.StringType{},
"description": basetypes.StringType{},
"destination_addresses": basetypes.ListType{
ElemType: types.StringType,
},
"destination_ports": basetypes.ListType{
ElemType: types.StringType,
},
"protocol": basetypes.StringType{},
"source_addresses": basetypes.ListType{
ElemType: types.StringType,
},
}
}
+37 -12
View File
@@ -17,8 +17,8 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
"backups": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n",
MarkdownDescription: "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n",
Description: "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n",
MarkdownDescription: "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n",
},
"id": schema.Int64Attribute{
Optional: true,
@@ -37,11 +37,11 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
Description: "The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.",
MarkdownDescription: "The hostname of your server, such as vps01.yourcompany.com. If not provided, the server will be created with a random name.",
},
"password": schema.StringAttribute{
"port_blocking": schema.BoolAttribute{
Optional: true,
Computed: true,
Description: "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n",
MarkdownDescription: "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n",
Description: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.",
MarkdownDescription: "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts.",
},
"region": schema.StringAttribute{
Required: true,
@@ -59,16 +59,41 @@ func ServerResourceSchema(ctx context.Context) schema.Schema {
stringvalidator.LengthAtLeast(1),
},
},
"ssh_keys": schema.ListAttribute{
ElementType: types.Int64Type,
Optional: true,
Computed: true,
Description: "This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.\n",
MarkdownDescription: "This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.\n",
},
"user_data": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
MarkdownDescription: "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
Validators: []validator.String{
stringvalidator.LengthAtMost(65536),
},
},
"vpc_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "Leave null to use default (public) network for the selected region.",
MarkdownDescription: "Leave null to use default (public) network for the selected region.",
},
},
}
}
type ServerModel struct {
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Name types.String `tfsdk:"name"`
Password types.String `tfsdk:"password"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
Backups types.Bool `tfsdk:"backups"`
Id types.Int64 `tfsdk:"id"`
Image types.String `tfsdk:"image"`
Name types.String `tfsdk:"name"`
PortBlocking types.Bool `tfsdk:"port_blocking"`
Region types.String `tfsdk:"region"`
Size types.String `tfsdk:"size"`
SshKeys types.List `tfsdk:"ssh_keys"`
UserData types.String `tfsdk:"user_data"`
VpcId types.Int64 `tfsdk:"vpc_id"`
}
+45
View File
@@ -0,0 +1,45 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func VpcResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The target vpc id.",
MarkdownDescription: "The target vpc id.",
},
"ip_range": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.",
MarkdownDescription: "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like.",
},
"name": schema.StringAttribute{
Required: true,
Description: "A name to help identify this VPC.",
MarkdownDescription: "A name to help identify this VPC.",
Validators: []validator.String{
stringvalidator.LengthBetween(0, 250),
},
},
},
}
}
type VpcModel struct {
Id types.Int64 `tfsdk:"id"`
IpRange types.String `tfsdk:"ip_range"`
Name types.String `tfsdk:"name"`
}
@@ -0,0 +1,510 @@
// Code generated by terraform-plugin-framework-generator DO NOT EDIT.
package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"strings"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
)
func VpcRouteEntriesResourceSchema(ctx context.Context) schema.Schema {
return schema.Schema{
Attributes: map[string]schema.Attribute{
"route_entries": schema.ListNestedAttribute{
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"description": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "An optional description for the route.",
MarkdownDescription: "An optional description for the route.",
Validators: []validator.String{
stringvalidator.LengthAtMost(250),
},
},
"destination": schema.StringAttribute{
Required: true,
Description: "The destination address for this route entry. This may be in CIDR format.",
MarkdownDescription: "The destination address for this route entry. This may be in CIDR format.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"router": schema.StringAttribute{
Required: true,
Description: "The server that will receive traffic sent to the destination property in this VPC.",
MarkdownDescription: "The server that will receive traffic sent to the destination property in this VPC.",
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
},
CustomType: RouteEntriesType{
ObjectType: types.ObjectType{
AttrTypes: RouteEntriesValue{}.AttributeTypes(ctx),
},
},
},
Optional: true,
Computed: true,
Description: "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save.",
MarkdownDescription: "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save.",
},
"vpc_id": schema.Int64Attribute{
Optional: true,
Computed: true,
Description: "The target vpc id.",
MarkdownDescription: "The target vpc id.",
},
},
}
}
type VpcRouteEntriesModel struct {
RouteEntries types.List `tfsdk:"route_entries"`
VpcId types.Int64 `tfsdk:"vpc_id"`
}
var _ basetypes.ObjectTypable = RouteEntriesType{}
type RouteEntriesType struct {
basetypes.ObjectType
}
func (t RouteEntriesType) Equal(o attr.Type) bool {
other, ok := o.(RouteEntriesType)
if !ok {
return false
}
return t.ObjectType.Equal(other.ObjectType)
}
func (t RouteEntriesType) String() string {
return "RouteEntriesType"
}
func (t RouteEntriesType) ValueFromObject(ctx context.Context, in basetypes.ObjectValue) (basetypes.ObjectValuable, diag.Diagnostics) {
var diags diag.Diagnostics
attributes := in.Attributes()
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return nil, diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAttribute, ok := attributes["destination"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination is missing from object`)
return nil, diags
}
destinationVal, ok := destinationAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination expected to be basetypes.StringValue, was: %T`, destinationAttribute))
}
routerAttribute, ok := attributes["router"]
if !ok {
diags.AddError(
"Attribute Missing",
`router is missing from object`)
return nil, diags
}
routerVal, ok := routerAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router expected to be basetypes.StringValue, was: %T`, routerAttribute))
}
if diags.HasError() {
return nil, diags
}
return RouteEntriesValue{
Description: descriptionVal,
Destination: destinationVal,
Router: routerVal,
state: attr.ValueStateKnown,
}, diags
}
func NewRouteEntriesValueNull() RouteEntriesValue {
return RouteEntriesValue{
state: attr.ValueStateNull,
}
}
func NewRouteEntriesValueUnknown() RouteEntriesValue {
return RouteEntriesValue{
state: attr.ValueStateUnknown,
}
}
func NewRouteEntriesValue(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) (RouteEntriesValue, diag.Diagnostics) {
var diags diag.Diagnostics
// Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/521
ctx := context.Background()
for name, attributeType := range attributeTypes {
attribute, ok := attributes[name]
if !ok {
diags.AddError(
"Missing RouteEntriesValue Attribute Value",
"While creating a RouteEntriesValue value, a missing attribute value was detected. "+
"A RouteEntriesValue must contain values for all attributes, even if null or unknown. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Expected Type: %s", name, attributeType.String()),
)
continue
}
if !attributeType.Equal(attribute.Type(ctx)) {
diags.AddError(
"Invalid RouteEntriesValue Attribute Type",
"While creating a RouteEntriesValue value, an invalid attribute value was detected. "+
"A RouteEntriesValue must use a matching attribute type for the value. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Expected Type: %s\n", name, attributeType.String())+
fmt.Sprintf("RouteEntriesValue Attribute Name (%s) Given Type: %s", name, attribute.Type(ctx)),
)
}
}
for name := range attributes {
_, ok := attributeTypes[name]
if !ok {
diags.AddError(
"Extra RouteEntriesValue Attribute Value",
"While creating a RouteEntriesValue value, an extra attribute value was detected. "+
"A RouteEntriesValue must not contain values beyond the expected attribute types. "+
"This is always an issue with the provider and should be reported to the provider developers.\n\n"+
fmt.Sprintf("Extra RouteEntriesValue Attribute Name: %s", name),
)
}
}
if diags.HasError() {
return NewRouteEntriesValueUnknown(), diags
}
descriptionAttribute, ok := attributes["description"]
if !ok {
diags.AddError(
"Attribute Missing",
`description is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
descriptionVal, ok := descriptionAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`description expected to be basetypes.StringValue, was: %T`, descriptionAttribute))
}
destinationAttribute, ok := attributes["destination"]
if !ok {
diags.AddError(
"Attribute Missing",
`destination is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
destinationVal, ok := destinationAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`destination expected to be basetypes.StringValue, was: %T`, destinationAttribute))
}
routerAttribute, ok := attributes["router"]
if !ok {
diags.AddError(
"Attribute Missing",
`router is missing from object`)
return NewRouteEntriesValueUnknown(), diags
}
routerVal, ok := routerAttribute.(basetypes.StringValue)
if !ok {
diags.AddError(
"Attribute Wrong Type",
fmt.Sprintf(`router expected to be basetypes.StringValue, was: %T`, routerAttribute))
}
if diags.HasError() {
return NewRouteEntriesValueUnknown(), diags
}
return RouteEntriesValue{
Description: descriptionVal,
Destination: destinationVal,
Router: routerVal,
state: attr.ValueStateKnown,
}, diags
}
func NewRouteEntriesValueMust(attributeTypes map[string]attr.Type, attributes map[string]attr.Value) RouteEntriesValue {
object, diags := NewRouteEntriesValue(attributeTypes, attributes)
if diags.HasError() {
// This could potentially be added to the diag package.
diagsStrings := make([]string, 0, len(diags))
for _, diagnostic := range diags {
diagsStrings = append(diagsStrings, fmt.Sprintf(
"%s | %s | %s",
diagnostic.Severity(),
diagnostic.Summary(),
diagnostic.Detail()))
}
panic("NewRouteEntriesValueMust received error(s): " + strings.Join(diagsStrings, "\n"))
}
return object
}
func (t RouteEntriesType) ValueFromTerraform(ctx context.Context, in tftypes.Value) (attr.Value, error) {
if in.Type() == nil {
return NewRouteEntriesValueNull(), nil
}
if !in.Type().Equal(t.TerraformType(ctx)) {
return nil, fmt.Errorf("expected %s, got %s", t.TerraformType(ctx), in.Type())
}
if !in.IsKnown() {
return NewRouteEntriesValueUnknown(), nil
}
if in.IsNull() {
return NewRouteEntriesValueNull(), nil
}
attributes := map[string]attr.Value{}
val := map[string]tftypes.Value{}
err := in.As(&val)
if err != nil {
return nil, err
}
for k, v := range val {
a, err := t.AttrTypes[k].ValueFromTerraform(ctx, v)
if err != nil {
return nil, err
}
attributes[k] = a
}
return NewRouteEntriesValueMust(RouteEntriesValue{}.AttributeTypes(ctx), attributes), nil
}
func (t RouteEntriesType) ValueType(ctx context.Context) attr.Value {
return RouteEntriesValue{}
}
var _ basetypes.ObjectValuable = RouteEntriesValue{}
type RouteEntriesValue struct {
Description basetypes.StringValue `tfsdk:"description"`
Destination basetypes.StringValue `tfsdk:"destination"`
Router basetypes.StringValue `tfsdk:"router"`
state attr.ValueState
}
func (v RouteEntriesValue) ToTerraformValue(ctx context.Context) (tftypes.Value, error) {
attrTypes := make(map[string]tftypes.Type, 3)
var val tftypes.Value
var err error
attrTypes["description"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["destination"] = basetypes.StringType{}.TerraformType(ctx)
attrTypes["router"] = basetypes.StringType{}.TerraformType(ctx)
objectType := tftypes.Object{AttributeTypes: attrTypes}
switch v.state {
case attr.ValueStateKnown:
vals := make(map[string]tftypes.Value, 3)
val, err = v.Description.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["description"] = val
val, err = v.Destination.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["destination"] = val
val, err = v.Router.ToTerraformValue(ctx)
if err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
vals["router"] = val
if err := tftypes.ValidateValue(objectType, vals); err != nil {
return tftypes.NewValue(objectType, tftypes.UnknownValue), err
}
return tftypes.NewValue(objectType, vals), nil
case attr.ValueStateNull:
return tftypes.NewValue(objectType, nil), nil
case attr.ValueStateUnknown:
return tftypes.NewValue(objectType, tftypes.UnknownValue), nil
default:
panic(fmt.Sprintf("unhandled Object state in ToTerraformValue: %s", v.state))
}
}
func (v RouteEntriesValue) IsNull() bool {
return v.state == attr.ValueStateNull
}
func (v RouteEntriesValue) IsUnknown() bool {
return v.state == attr.ValueStateUnknown
}
func (v RouteEntriesValue) String() string {
return "RouteEntriesValue"
}
func (v RouteEntriesValue) ToObjectValue(ctx context.Context) (basetypes.ObjectValue, diag.Diagnostics) {
var diags diag.Diagnostics
attributeTypes := map[string]attr.Type{
"description": basetypes.StringType{},
"destination": basetypes.StringType{},
"router": basetypes.StringType{},
}
if v.IsNull() {
return types.ObjectNull(attributeTypes), diags
}
if v.IsUnknown() {
return types.ObjectUnknown(attributeTypes), diags
}
objVal, diags := types.ObjectValue(
attributeTypes,
map[string]attr.Value{
"description": v.Description,
"destination": v.Destination,
"router": v.Router,
})
return objVal, diags
}
func (v RouteEntriesValue) Equal(o attr.Value) bool {
other, ok := o.(RouteEntriesValue)
if !ok {
return false
}
if v.state != other.state {
return false
}
if v.state != attr.ValueStateKnown {
return true
}
if !v.Description.Equal(other.Description) {
return false
}
if !v.Destination.Equal(other.Destination) {
return false
}
if !v.Router.Equal(other.Router) {
return false
}
return true
}
func (v RouteEntriesValue) Type(ctx context.Context) attr.Type {
return RouteEntriesType{
basetypes.ObjectType{
AttrTypes: v.AttributeTypes(ctx),
},
}
}
func (v RouteEntriesValue) AttributeTypes(ctx context.Context) map[string]attr.Type {
return map[string]attr.Type{
"description": basetypes.StringType{},
"destination": basetypes.StringType{},
"router": basetypes.StringType{},
}
}
+409 -27
View File
@@ -1,38 +1,126 @@
{
"datasources": [
"provider": {
"name": "binarylane"
},
"resources": [
{
"name": "server",
"name": "load_balancer",
"schema": {
"attributes": [
{
"name": "forwarding_rules",
"list_nested": {
"computed_optional_required": "computed_optional",
"nested_object": {
"attributes": [
{
"name": "entry_protocol",
"string": {
"computed_optional_required": "required",
"description": "The protocol that traffic must match for this load balancer to forward traffic according to this rule.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"http\",\n\"https\",\n)"
}
}
]
}
}
]
},
"description": "The rules that control which traffic the load balancer will forward to servers in the pool. Leave null to accept a default \"HTTP\" only forwarding rule."
}
},
{
"name": "health_check",
"single_nested": {
"computed_optional_required": "computed_optional",
"attributes": [
{
"name": "path",
"string": {
"computed_optional_required": "computed_optional",
"description": "Leave null to accept the default '/' path.",
"validators": [
{
"custom": {
"imports": [
{
"path": "regexp"
},
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.RegexMatches(regexp.MustCompile(\"/[A-Za-z0-9/.?=\u0026+%_-]*\"), \"\")"
}
}
]
}
},
{
"name": "protocol",
"string": {
"computed_optional_required": "computed_optional",
"description": "Leave null to accept the default HTTP protocol."
}
}
],
"description": "The rules that determine which servers are considered 'healthy' and in the server pool for the load balancer. Leave this null to accept appropriate defaults based on the forwarding_rules."
}
},
{
"name": "name",
"string": {
"computed_optional_required": "required",
"description": "The hostname of the load balancer.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
},
{
"name": "region",
"string": {
"computed_optional_required": "computed_optional",
"description": "Leave null to create an anycast load balancer."
}
},
{
"name": "server_ids",
"list": {
"computed_optional_required": "computed_optional",
"element_type": {
"int64": {}
},
"description": "A list of server IDs to assign to this load balancer."
}
},
{
"name": "id",
"int64": {
"computed_optional_required": "required",
"description": "The ID of the server to fetch."
"computed_optional_required": "computed_optional",
"description": "The ID of the load balancer to fetch."
}
}
]
}
},
{
"name": "ssh_key",
"schema": {
"attributes": [
{
"name": "id",
"int64": {
"computed_optional_required": "required",
"description": "The ID or fingerprint of the SSH Key to fetch."
}
}
]
}
}
],
"provider": {
"name": "binarylane"
},
"resources": [
{
"name": "server",
"schema": {
@@ -41,7 +129,7 @@
"name": "backups",
"bool": {
"computed_optional_required": "computed_optional",
"description": "If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.\n"
"description": "If true this will enable two daily backups for the server. `options.daily_backups` will override this value if provided. Setting this to false has no effect.\n"
}
},
{
@@ -59,10 +147,10 @@
}
},
{
"name": "password",
"string": {
"name": "port_blocking",
"bool": {
"computed_optional_required": "computed_optional",
"description": "If this is provided the specified or default remote user's account password will be set to this value. Only valid if the server supports password change actions (check server.password_change_supported via the servers endpoint). If omitted and the server supports password change actions a random password will be generated and emailed to the account email address.\n"
"description": "Port blocking of outgoing connections for email, SSH and Remote Desktop (TCP ports 22, 25, and 3389) is enabled by default for all new servers. If this is false port blocking will be disabled. Disabling port blocking is only available to reviewed accounts."
}
},
{
@@ -103,6 +191,42 @@
]
}
},
{
"name": "ssh_keys",
"list": {
"computed_optional_required": "computed_optional",
"element_type": {
"int64": {}
},
"description": "This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to disable deployment of default keys.\n"
}
},
{
"name": "user_data",
"string": {
"computed_optional_required": "computed_optional",
"description": "If provided this will be used to initialise the new server. This must be left null if the Image does not support UserData, see DistributionInfo.Features for more information.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtMost(65536)"
}
}
]
}
},
{
"name": "vpc_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "Leave null to use default (public) network for the selected region."
}
},
{
"name": "id",
"int64": {
@@ -113,6 +237,142 @@
]
}
},
{
"name": "server_firewall_rules",
"schema": {
"attributes": [
{
"name": "firewall_rules",
"list_nested": {
"computed_optional_required": "required",
"nested_object": {
"attributes": [
{
"name": "action",
"string": {
"computed_optional_required": "required",
"description": "The action to take when there is a match on this rule.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"drop\",\n\"accept\",\n)"
}
}
]
}
},
{
"name": "description",
"string": {
"computed_optional_required": "computed_optional",
"description": "A description to assist in identifying this rule. Commonly used to record the reason for the rule or the intent behind it, e.g. \"Block access to RDP\" or \"Allow access from HQ\".",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthBetween(0, 250)"
}
}
]
}
},
{
"name": "destination_addresses",
"list": {
"computed_optional_required": "required",
"element_type": {
"string": {}
},
"description": "The destination addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
}
],
"schema_definition": "listvalidator.SizeAtLeast(1)"
}
}
]
}
},
{
"name": "destination_ports",
"list": {
"computed_optional_required": "computed_optional",
"element_type": {
"string": {}
},
"description": "The destination ports to match for this rule. Leave null or empty to match on all ports."
}
},
{
"name": "protocol",
"string": {
"computed_optional_required": "required",
"description": "The protocol to match for this rule.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.OneOf(\n\"all\",\n\"icmp\",\n\"tcp\",\n\"udp\",\n)"
}
}
]
}
},
{
"name": "source_addresses",
"list": {
"computed_optional_required": "required",
"element_type": {
"string": {}
},
"description": "The source addresses to match for this rule. Each address may be an individual IPv4 address or a range in IPv4 CIDR notation.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
}
],
"schema_definition": "listvalidator.SizeAtLeast(1)"
}
}
]
}
}
]
},
"description": "A list of rules for the server. NB: that any existing rules that are not included will be removed. Submit an empty list to clear all rules."
}
},
{
"name": "server_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The ID of the server for which firewall rules should be listed."
}
}
]
}
},
{
"name": "ssh_key",
"schema": {
@@ -171,6 +431,128 @@
}
]
}
},
{
"name": "vpc",
"schema": {
"attributes": [
{
"name": "ip_range",
"string": {
"computed_optional_required": "computed_optional",
"description": "A private address range that you select during creation, such as the default value of 10.240.0.0/16. Because the virtual network is dedicated to your use, you may use whatever IP address range you like."
}
},
{
"name": "name",
"string": {
"computed_optional_required": "required",
"description": "A name to help identify this VPC.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthBetween(0, 250)"
}
}
]
}
},
{
"name": "id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The target vpc id."
}
}
]
}
},
{
"name": "vpc_route_entries",
"schema": {
"attributes": [
{
"name": "route_entries",
"list_nested": {
"computed_optional_required": "computed_optional",
"nested_object": {
"attributes": [
{
"name": "description",
"string": {
"computed_optional_required": "computed_optional",
"description": "An optional description for the route.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtMost(250)"
}
}
]
}
},
{
"name": "destination",
"string": {
"computed_optional_required": "required",
"description": "The destination address for this route entry. This may be in CIDR format.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
},
{
"name": "router",
"string": {
"computed_optional_required": "required",
"description": "The server that will receive traffic sent to the destination property in this VPC.",
"validators": [
{
"custom": {
"imports": [
{
"path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
}
],
"schema_definition": "stringvalidator.LengthAtLeast(1)"
}
}
]
}
}
]
},
"description": "Submit null to leave unaltered, submit an empty list to clear all route entries. It is not possible to PATCH individual route entries, to alter a route entry submit the entire list of route entries you wish to save."
}
},
{
"name": "vpc_id",
"int64": {
"computed_optional_required": "computed_optional",
"description": "The target vpc id."
}
}
]
}
}
],
"version": "0.1"
+74 -41
View File
@@ -29,36 +29,60 @@ resources:
server_id: id
overrides:
backups:
type: bool
description: >
If true this will enable two daily backups for the server. Options.daily_backups will override this value if provided. Setting this to false has no effect.
optional: true
default:
static: false
computed: false
If true this will enable two daily backups for the server. `options.daily_backups` will override this
value if provided. Setting this to false has no effect.
image:
type: string
description: The slug of the selected operating system.
example: "ubuntu-24.04"
password:
type: string
ssh_keys:
description: >
If this is provided the specified or default remote user's account password will be set to this value.
Only valid if the server supports password change actions (check server.password_change_supported via the
servers endpoint). If omitted and the server supports password change actions a random password will be
generated and emailed to the account email address.
sensitive: true
optional_required: optional
This is a list of SSH key ids. If this is null or not provided, any SSH keys that have been marked as
default will be deployed (assuming the operating system supports SSH Keys). Submit an empty list to
disable deployment of default keys.
ignores:
- ipv6
- licenses
- links
- options
- server
- ssh_keys
- vpc_id
- user_data
- port_blocking
- options
- password
load_balancer:
create:
path: /load_balancers
method: POST
read:
path: /load_balancers/{load_balancer_id}
method: GET
update:
path: /load_balancers/{load_balancer_id}
method: PUT
delete:
path: /load_balancers/{load_balancer_id}
method: DELETE
schema:
attributes:
aliases:
load_balancer_id: id
ignores:
- links
- load_balancer
server_firewall_rules:
create:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
read:
path: /servers/{server_id}/advanced_firewall_rules
method: GET
update:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
delete:
path: /servers/{server_id}/actions#ChangeAdvancedFirewallRules
method: POST
schema:
ignores:
- type
- action
ssh_key:
create:
path: /account/keys
@@ -75,33 +99,42 @@ resources:
key_id: id
ignores:
- ssh_key
data_sources:
server:
vpc:
create:
path: /vpcs
method: POST
read:
path: /servers/{server_id}
path: /vpcs/{vpc_id}
method: GET
update:
path: /vpcs/{vpc_id}
method: PATCH
delete:
path: /vpcs/{vpc_id}
method: DELETE
schema:
attributes:
aliases:
server_id: id
vpc_id: id
ignores:
- ipv6
- licenses
- links
- options
- server
- ssh_keys
- vpc_id
- user_data
- port_blocking
ssh_key:
- route_entries
- vpc
vpc_route_entries:
create:
path: /vpcs/{vpc_id}
method: PATCH
read:
path: /account/keys/{key_id}
path: /vpcs/{vpc_id}
method: GET
update:
path: /vpcs/{vpc_id}
method: PATCH
delete:
path: /vpcs/{vpc_id}
method: DELETE
schema:
attributes:
aliases:
key_id: id
ignores:
- ssh_key
- name
- vpc
# data_sources:
# Currently, don't need to generate data sources as they use the same schema as resources